Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-23 14:37:50 +03:00
commit 2f84def7cc
1244 changed files with 20573 additions and 7307 deletions

View file

@ -134,7 +134,6 @@ dependencies {
implementation(projects.core.datasource) implementation(projects.core.datasource)
implementation(projects.core.utils) implementation(projects.core.utils)
implementation(projects.core.decompose) implementation(projects.core.decompose)
implementation(projects.core.deepLinks)
implementation(projects.core.error.ext) implementation(projects.core.error.ext)
implementation(projects.libs.crypto) implementation(projects.libs.crypto)
implementation(projects.libs.auth) implementation(projects.libs.auth)
@ -281,6 +280,8 @@ dependencies {
implementation(tangemDeps.card.android) { implementation(tangemDeps.card.android) {
exclude(module = "joda-time") exclude(module = "joda-time")
} }
implementation(tangemDeps.hot.core)
implementation(tangemDeps.hot.android)
/** DI */ /** DI */
implementation(deps.hilt.android) implementation(deps.hilt.android)
@ -300,6 +301,7 @@ dependencies {
implementation(deps.zxing.qrCore) implementation(deps.zxing.qrCore)
implementation(deps.coil) implementation(deps.coil)
implementation(deps.coil.gif) implementation(deps.coil.gif)
implementation(deps.coil.svg)
implementation(deps.amplitude) implementation(deps.amplitude)
implementation(deps.kotsonGson) implementation(deps.kotsonGson)
implementation(deps.spongecastle.core) implementation(deps.spongecastle.core)

View file

@ -1,7 +1,10 @@
package com.tangem.common package com.tangem.common
import android.Manifest 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.espresso.intent.Intents
import androidx.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule
import com.kaspersky.components.alluresupport.interceptors.step.ScreenshotStepInterceptor 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.kaspresso.Kaspresso
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.tangem.common.allure.FailedStepScreenshotInterceptor import com.tangem.common.allure.FailedStepScreenshotInterceptor
import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.common.rules.ApiEnvironmentRule
import com.tangem.sdk.api.TangemSdkManager import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.tap.MainActivity import com.tangem.tap.MainActivity
import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidRule
import org.junit.Rule import org.junit.Rule
@ -35,17 +38,20 @@ abstract class BaseTestCase : TestCase(
) { ) {
@Inject @Inject
lateinit var tangemSdkManager: TangemSdkManager lateinit var apiConfigsManager: ApiConfigsManager
@Inject
lateinit var appPreferencesStore: AppPreferencesStore
private val hiltRule = HiltAndroidRule(this) 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.POST_NOTIFICATIONS,
Manifest.permission.CAMERA, Manifest.permission.CAMERA,
) )
val composeTestRule = createAndroidComposeRule<MainActivity>()
/**
* 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 @Rule
@JvmField @JvmField
@ -53,13 +59,22 @@ abstract class BaseTestCase : TestCase(
.outerRule(hiltRule) .outerRule(hiltRule)
.around(ApplicationInjectionExecutionRule()) .around(ApplicationInjectionExecutionRule())
.around(permissionRule) .around(permissionRule)
.around(apiEnvironmentRule)
.around(composeTestRule) .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( protected fun setupHooks(
additionalBeforeSection: () -> Unit = {}, additionalBeforeSection: () -> Unit = {},
additionalAfterSection: () -> Unit = {}, additionalAfterSection: () -> Unit = {},
) = before { ) = before {
hiltRule.inject() hiltRule.inject()
apiEnvironmentRule.setup(apiConfigsManager)
ActivityScenario.launch(MainActivity::class.java)
Intents.init() Intents.init()
additionalBeforeSection() additionalBeforeSection()
}.after { }.after {
@ -67,4 +82,21 @@ abstract class BaseTestCase : TestCase(
Intents.release() 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)
}
} }

View file

@ -0,0 +1,14 @@
package com.tangem.common.annotations
import com.tangem.datasource.api.common.config.ApiEnvironment
/**
* Annotation to specify the API environment for a class or function.
*
* @property environment the API environment to be used (defaults to [ApiEnvironment.MOCK])
*/
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ApiEnv(
val environment: ApiEnvironment = ApiEnvironment.MOCK,
)

View file

@ -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))
}

View file

@ -0,0 +1,78 @@
package com.tangem.common.rules
import androidx.test.platform.app.InstrumentationRegistry
import com.tangem.common.annotations.ApiEnv
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.api.common.config.managers.MutableApiConfigsManager
import com.tangem.wallet.test.BuildConfig
import kotlinx.coroutines.runBlocking
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import timber.log.Timber
/**
* A JUnit rule that sets up the API environment for tests based on annotations or instrumentation arguments.
*
* This rule allows tests to specify which API environment to use either through an annotation on the test method/class
* or via an instrumentation argument.
*
* This rule only finds and stores the required API environment. The actual environment setup is performed
* by calling the [setup] method with an appropriate [ApiConfigsManager] instance.
*/
class ApiEnvironmentRule : TestRule {
private var targetEnvironment: ApiEnvironment? = null
/**
* Sets up the API environment based on the provided [ApiConfigsManager].
* This method is typically called in the test setup phase to ensure the correct API environment is configured.
*
* @param apiConfigsManager the manager responsible for API configurations
*/
fun setup(apiConfigsManager: ApiConfigsManager) {
val mutableManager = requireNotNull(apiConfigsManager as? MutableApiConfigsManager) {
"MutableApiConfigsManager isn't available in build type ${BuildConfig.BUILD_TYPE}."
}
mutableManager.setupEnvironment()
}
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
targetEnvironment = determineEnvironment(description)
base.evaluate()
}
}
}
private fun determineEnvironment(description: Description): ApiEnvironment {
val instrumentationArgs = InstrumentationRegistry.getArguments()
val envArg = instrumentationArgs.getString(ENV_ARGUMENT)
if (!envArg.isNullOrEmpty()) return ApiEnvironment.valueOf(envArg.uppercase())
val methodAnnotation = description.getAnnotation(ApiEnv::class.java)
if (methodAnnotation != null) return methodAnnotation.environment
val classAnnotation = description.testClass.getAnnotation(ApiEnv::class.java)
if (classAnnotation != null) return classAnnotation.environment
return ApiEnvironment.MOCK
}
private fun MutableApiConfigsManager.setupEnvironment() {
val environment = requireNotNull(targetEnvironment) { "Target environment is null" }
runBlocking { changeEnvironment(environment) }
Timber.i("API environment set to: ${environment.name}")
}
private companion object {
const val ENV_ARGUMENT = "testEnvironment"
}
}

View file

@ -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<LazyListItemNode>(semanticsNode, semanticsProvider)

View file

@ -5,10 +5,7 @@ import com.kaspersky.kaspresso.testcases.api.scenario.Scenario
import com.kaspersky.kaspresso.testcases.core.testcontext.TestContext import com.kaspersky.kaspresso.testcases.core.testcontext.TestContext
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ProductType
import com.tangem.screens.DisclaimerTestScreen import com.tangem.screens.*
import com.tangem.screens.MainTestScreen
import com.tangem.screens.StoriesTestScreen
import com.tangem.screens.TestTopBar
import com.tangem.tap.domain.sdk.mocks.MockProvider import com.tangem.tap.domain.sdk.mocks.MockProvider
import io.github.kakaocup.compose.node.element.ComposeScreen import io.github.kakaocup.compose.node.element.ComposeScreen
@ -20,24 +17,24 @@ class OpenMainScreenScenario(
if (productType != null) { if (productType != null) {
MockProvider.setMocks(productType) MockProvider.setMocks(productType)
} }
ComposeScreen.onComposeScreen<DisclaimerTestScreen>(testRule) { ComposeScreen.onComposeScreen<DisclaimerPageObject>(testRule) {
step("Click on \"Accept\" button") { step("Click on \"Accept\" button") {
acceptButton.clickWithAssertion() acceptButton.clickWithAssertion()
} }
} }
ComposeScreen.onComposeScreen<StoriesTestScreen>(testRule) { ComposeScreen.onComposeScreen<StoriesPageObject>(testRule) {
step("Click on \"Scan\" button") { step("Click on \"Scan\" button") {
scanButton.clickWithAssertion() scanButton.clickWithAssertion()
} }
} }
ComposeScreen.onComposeScreen<MainTestScreen>(testRule) { ComposeScreen.onComposeScreen<MainScreenPageObject>(testRule) {
step("Make sure wallet screen is visible") { step("Make sure wallet screen is visible") {
assertIsDisplayed() assertIsDisplayed()
} }
} }
ComposeScreen.onComposeScreen<TestTopBar>(testRule) { ComposeScreen.onComposeScreen<MarketsTooltipPageObject>(testRule) {
step("Close Markets tooltip"){ step("Close Markets tooltip"){
performClick() contentContainer.performClick()
} }
} }
} }

View file

@ -1,25 +1,27 @@
package com.tangem.screens package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider 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 com.tangem.wallet.R
import io.github.kakaocup.compose.node.element.ComposeScreen 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.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString import io.github.kakaocup.kakao.common.utilities.getResourceString
class DetailsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<DetailsTestScreen>( ComposeScreen<DetailsPageObject>(
semanticsProvider = semanticsProvider, semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(TestTags.DETAILS_SCREEN) } viewBuilderAction = { hasTestTag(DetailsScreenTestTags.SCREEN_CONTAINER) }
) { ) {
val walletConnectButton: KNode = child { val walletConnectButton: KNode = child {
hasTestTag(TestTags.DETAILS_SCREEN_ITEM) hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
hasText(getResourceString(R.string.wallet_connect_title)) hasText(getResourceString(R.string.wallet_connect_title))
} }
private val walletBlock: KNode = child { private val walletBlock: KNode = child {
hasTestTag(TestTags.DETAILS_SCREEN_ITEM) hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
} }
val walletNameButton: KNode = walletBlock.child { val walletNameButton: KNode = walletBlock.child {
@ -32,20 +34,23 @@ class DetailsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
} }
val buyTangemButton: KNode = child { val buyTangemButton: KNode = child {
hasTestTag(TestTags.DETAILS_SCREEN_ITEM) hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
hasText(getResourceString(R.string.details_buy_wallet)) hasText(getResourceString(R.string.details_buy_wallet))
} }
val appSettingsButton: KNode = child { val appSettingsButton: KNode = child {
hasTestTag(TestTags.DETAILS_SCREEN_ITEM) hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
hasText(getResourceString(R.string.app_settings_title)) hasText(getResourceString(R.string.app_settings_title))
} }
val contactSupportButton: KNode = child { val contactSupportButton: KNode = child {
hasTestTag(TestTags.DETAILS_SCREEN_ITEM) hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
hasText(getResourceString(R.string.details_row_title_contact_to_support)) hasText(getResourceString(R.string.details_row_title_contact_to_support))
} }
val toSButton: KNode = child { val toSButton: KNode = child {
hasTestTag(TestTags.DETAILS_SCREEN_ITEM) hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
hasText(getResourceString(R.string.disclaimer_title)) hasText(getResourceString(R.string.disclaimer_title))
} }
} }
internal fun BaseTestCase.onDetailsScreen(function: DetailsPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,31 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.R
import com.tangem.core.ui.test.DialogTestTags
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<DialogPageObject>(semanticsProvider = semanticsProvider) {
val dialogContainer: KNode = child {
hasTestTag(DialogTestTags.DIALOG_CONTAINER)
}
val cancelButton: KNode = child {
hasTestTag(DialogTestTags.BUTTON)
hasText(getResourceString(R.string.common_cancel))
}
val hideButton: KNode = child {
hasTestTag(DialogTestTags.BUTTON)
hasText(getResourceString(R.string.token_details_hide_alert_hide))
}
}
internal fun BaseTestCase.onDialog(function: DialogPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,22 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.core.ui.test.DisclaimerScreenTestTags
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode
class DisclaimerPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<DisclaimerPageObject>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(DisclaimerScreenTestTags.SCREEN_CONTAINER) }
) {
val acceptButton: KNode = child {
hasTestTag(DisclaimerScreenTestTags.ACCEPT_BUTTON)
}
}
internal fun BaseTestCase.onDisclaimerScreen(function: DisclaimerPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -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<DisclaimerTestScreen>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(TestTags.DISCLAIMER_SCREEN_CONTAINER) }
) {
val acceptButton: KNode = child {
hasTestTag(TestTags.DISCLAIMER_SCREEN_ACCEPT_BUTTON)
}
}

View file

@ -0,0 +1,119 @@
package com.tangem.screens
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.hasLazyListItemPosition
import com.tangem.common.utils.LazyListItemNode
import com.tangem.core.ui.test.TokenElementsTestTags
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.core.ui.utils.LazyListItemPositionSemantics
import com.tangem.feature.wallet.impl.R
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasTestTag as withTestTag
import androidx.compose.ui.test.hasText as withText
class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<MainScreenPageObject>(semanticsProvider = semanticsProvider) {
private val lazyList = KLazyListNode(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(MainScreenTestTags.SCREEN_CONTAINER) },
itemTypeBuilder = {
itemType(::LazyListItemNode)
},
positionMatcher = { position ->
SemanticsMatcher.expectValue(
LazyListItemPositionSemantics,
position
)
}
)
val synchronizeAddressesButton: KNode = child {
hasText(getResourceString(R.string.common_generate_addresses))
}
/**
* Find token list item with title and address
*/
@OptIn(ExperimentalTestApi::class)
fun tokenWithTitleAndAddress(tokenTitle: String): KNode {
return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasText(tokenTitle)
}.child<KNode> {
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<LazyListItemNode> {
hasAnyDescendant(withTestTag(MainScreenTestTags.WALLET_LIST_ITEM))
}.child<KNode> {
hasTestTag(MainScreenTestTags.WALLET_BALANCE)
useUnmergedTree = true
}
}
@OptIn(ExperimentalTestApi::class)
fun organizeTokensButton(): KNode {
return lazyList.childWith<LazyListItemNode> {
hasTestTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON)
}.child<KNode> {
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<LazyListItemNode> {
hasTestTag(MainScreenTestTags.TOKEN_LIST_ITEM)
hasText(tokenTitle)
hasLazyListItemPosition(index)
}.child<KNode> {
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)

View file

@ -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<MainTestScreen>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(TestTags.MAIN_SCREEN) }
)

View file

@ -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<MarketsTooltipPageObject>(semanticsProvider = semanticsProvider) {
val contentContainer: KNode = child {
hasTestTag(MarketTooltipTestTags.CONTAINER)
}
}
internal fun BaseTestCase.onMarketsTooltipScreen(function: MarketsTooltipPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -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<OrganizeTokensPageObject>(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)

View file

@ -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<PopUpMenuPageObject>(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)

View file

@ -1,25 +1,27 @@
package com.tangem.screens package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider 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 com.tangem.wallet.R
import io.github.kakaocup.compose.node.element.ComposeScreen 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.KNode
import io.github.kakaocup.kakao.common.views.KView import io.github.kakaocup.kakao.common.views.KView
import io.github.kakaocup.kakao.text.KButton import io.github.kakaocup.kakao.text.KButton
class StoriesTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : class StoriesPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<StoriesTestScreen>( ComposeScreen<StoriesPageObject>(
semanticsProvider = semanticsProvider, semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(TestTags.STORIES_SCREEN) } viewBuilderAction = { hasTestTag(StoriesScreenTestTags.SCREEN_CONTAINER) }
) { ) {
val scanButton: KNode = child { val scanButton: KNode = child {
hasTestTag(TestTags.STORIES_SCREEN_SCAN_BUTTON) hasTestTag(StoriesScreenTestTags.SCAN_BUTTON)
} }
val orderButton: KNode = child { val orderButton: KNode = child {
hasTestTag(TestTags.STORIES_SCREEN_ORDER_BUTTON) hasTestTag(StoriesScreenTestTags.ORDER_BUTTON)
} }
val enableNFCAlert: KView = KView { val enableNFCAlert: KView = KView {
@ -29,4 +31,7 @@ class StoriesTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
val cancelButton: KButton = KButton { val cancelButton: KButton = KButton {
withId(android.R.id.button2) withId(android.R.id.button2)
} }
} }
internal fun BaseTestCase.onStoriesScreen(function: StoriesPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -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<TestTopBar>(
semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(TestTags.MAIN_SCREEN_TOP_BAR) }
) {
val moreButton: KNode = child {
hasTestTag(TestTags.MAIN_SCREEN_MORE_BUTTON)
}
}

View file

@ -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<TokenDetailsPageObject>(semanticsProvider = semanticsProvider) {
val screenContainer: KNode = child {
hasTestTag(TokenDetailsScreenTestTags.SCREEN_CONTAINER)
}
}
internal fun BaseTestCase.onTokenDetailsScreen(function: TokenDetailsPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -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<TokenDetailsTopBarPageObject>(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)

View file

@ -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<TopBarPageObject>(
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)

View file

@ -1,19 +1,21 @@
package com.tangem.screens package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider 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 com.tangem.wallet.R
import io.github.kakaocup.compose.node.element.ComposeScreen 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.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString import io.github.kakaocup.kakao.common.utilities.getResourceString
class WalletSettingsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : class WalletSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<WalletSettingsTestScreen>( ComposeScreen<WalletSettingsPageObject>(
semanticsProvider = semanticsProvider, semanticsProvider = semanticsProvider,
viewBuilderAction = { hasTestTag(TestTags.WALLET_SETTINGS_SCREEN) } viewBuilderAction = { hasTestTag(WalletSettingsScreenTestTags.SCREEN_CONTAINER) }
) { ) {
private val walletSettingsItem: KNode = child { private val walletSettingsItem: KNode = child {
hasTestTag(TestTags.WALLET_SETTINGS_SCREEN_ITEM) hasTestTag(WalletSettingsScreenTestTags.SCREEN_ITEM)
} }
val linkMoreCardsButton: KNode = walletSettingsItem.child { val linkMoreCardsButton: KNode = walletSettingsItem.child {
@ -28,4 +30,7 @@ class WalletSettingsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvi
val forgetWalletButton: KNode = walletSettingsItem.child { val forgetWalletButton: KNode = walletSettingsItem.child {
hasText(getResourceString(R.string.settings_forget_wallet)) hasText(getResourceString(R.string.settings_forget_wallet))
} }
} }
internal fun BaseTestCase.onWalletSettingsScreen(function: WalletSettingsPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -4,152 +4,151 @@ import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ProductType
import com.tangem.scenarios.OpenMainScreenScenario import com.tangem.scenarios.OpenMainScreenScenario
import com.tangem.screens.DetailsTestScreen import com.tangem.screens.onDetailsScreen
import com.tangem.screens.TestTopBar import com.tangem.screens.onTopBar
import com.tangem.screens.WalletSettingsTestScreen import com.tangem.screens.onWalletSettingsScreen
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.compose.node.element.ComposeScreen
import org.junit.Test import org.junit.Test
@HiltAndroidTest @HiltAndroidTest
class DetailsScreenTest : BaseTestCase() { class DetailsTest : BaseTestCase() {
@Test @Test
fun walletWithoutBackupDetails() = fun walletWithoutBackupDetailsTest() =
setupHooks().run { setupHooks().run {
scenario(OpenMainScreenScenario(composeTestRule)) scenario(OpenMainScreenScenario(composeTestRule))
ComposeScreen.onComposeScreen<TestTopBar>(composeTestRule) { onTopBar {
step("Open wallet details") { step("Open wallet details") {
moreButton.clickWithAssertion() moreButton.clickWithAssertion()
} }
} }
ComposeScreen.onComposeScreen<DetailsTestScreen>(composeTestRule) { onDetailsScreen {
step("Assert wallet connect button is visible") { step("Assert 'Wallet connect' button is visible") {
walletConnectButton.assertIsDisplayed() walletConnectButton.assertIsDisplayed()
} }
step("Assert scan card button is visible") { step("Assert 'Scan card' button is visible") {
scanCardButton.assertIsDisplayed() scanCardButton.assertIsDisplayed()
} }
step("Assert buy Tangem card button is visible") { step("Assert 'Buy Tangem card' button is visible") {
buyTangemButton.assertIsDisplayed() buyTangemButton.assertIsDisplayed()
} }
step("Assert app settings button is visible") { step("Assert 'App settings' button is visible") {
appSettingsButton.assertIsDisplayed() appSettingsButton.assertIsDisplayed()
} }
step("Assert contact support button is visible") { step("Assert 'Contact support' button is visible") {
contactSupportButton.assertIsDisplayed() contactSupportButton.assertIsDisplayed()
} }
step("Assert terms or service button is visible") { step("Assert 'Terms of service' button is visible") {
toSButton.assertIsDisplayed() toSButton.assertIsDisplayed()
} }
step("Open wallet settings screen") { step("Open 'Wallet settings' screen") {
walletNameButton.clickWithAssertion() walletNameButton.clickWithAssertion()
} }
} }
ComposeScreen.onComposeScreen<WalletSettingsTestScreen>(composeTestRule) { onWalletSettingsScreen {
step("Assert Link more cards button is visible") { step("Assert 'Link more cards' button is visible") {
linkMoreCardsButton.assertIsDisplayed() linkMoreCardsButton.assertIsDisplayed()
} }
step("Assert Card Settings button is visible") { step("Assert 'Card Settings' button is visible") {
cardSettingsButton.assertIsDisplayed() cardSettingsButton.assertIsDisplayed()
} }
step("Assert Referral program button is visible") { step("Assert 'Referral program' button is visible") {
referralProgramButton.assertIsDisplayed() referralProgramButton.assertIsDisplayed()
} }
step("Assert Forget wallet button is visible") { step("Assert 'Forget wallet' button is visible") {
forgetWalletButton.assertIsDisplayed() forgetWalletButton.assertIsDisplayed()
} }
} }
} }
@Test @Test
fun wallet2Details() = fun wallet2DetailsTest() =
setupHooks().run { setupHooks().run {
scenario(OpenMainScreenScenario(composeTestRule, ProductType.Wallet2)) scenario(OpenMainScreenScenario(composeTestRule, ProductType.Wallet2))
ComposeScreen.onComposeScreen<TestTopBar>(composeTestRule) { onTopBar {
step("Open wallet details") { step("Open wallet details") {
moreButton.clickWithAssertion() moreButton.clickWithAssertion()
} }
} }
ComposeScreen.onComposeScreen<DetailsTestScreen>(composeTestRule) { onDetailsScreen {
step("Assert wallet connect button is visible") { step("Assert 'Wallet connect' button is visible") {
walletConnectButton.assertIsDisplayed() walletConnectButton.assertIsDisplayed()
} }
step("Assert scan card button is visible") { step("Assert 'Scan card' button is visible") {
scanCardButton.assertIsDisplayed() scanCardButton.assertIsDisplayed()
} }
step("Assert buy Tangem card button is visible") { step("Assert 'Buy Tangem card' button is visible") {
buyTangemButton.assertIsDisplayed() buyTangemButton.assertIsDisplayed()
} }
step("Assert app settings button is visible") { step("Assert 'App settings' button is visible") {
appSettingsButton.assertIsDisplayed() appSettingsButton.assertIsDisplayed()
} }
step("Assert contact support button is visible") { step("Assert 'Contact support' button is visible") {
contactSupportButton.assertIsDisplayed() contactSupportButton.assertIsDisplayed()
} }
step("Assert terms or service button is visible") { step("Assert 'Terms or service' button is visible") {
toSButton.assertIsDisplayed() toSButton.assertIsDisplayed()
} }
step("Open wallet settings screen") { step("Open 'Wallet settings' screen") {
walletNameButton.clickWithAssertion() walletNameButton.clickWithAssertion()
} }
} }
ComposeScreen.onComposeScreen<WalletSettingsTestScreen>(composeTestRule) { onWalletSettingsScreen {
step("Assert Link more cards button does not exist") { step("Assert 'Link more cards' button does not exist") {
linkMoreCardsButton.assertIsNotDisplayed() linkMoreCardsButton.assertIsNotDisplayed()
} }
step("Assert Card Settings button is visible") { step("Assert 'Card Settings' button is visible") {
cardSettingsButton.assertIsDisplayed() cardSettingsButton.assertIsDisplayed()
} }
step("Assert Referral program button is visible") { step("Assert 'Referral program' button is visible") {
referralProgramButton.assertIsDisplayed() referralProgramButton.assertIsDisplayed()
} }
step("Assert Forget wallet button is visible") { step("Assert 'Forget wallet' button is visible") {
forgetWalletButton.assertIsDisplayed() forgetWalletButton.assertIsDisplayed()
} }
} }
} }
@Test @Test
fun noteDetails() = fun noteDetailsTest() =
setupHooks().run { setupHooks().run {
scenario(OpenMainScreenScenario(composeTestRule, ProductType.Note)) scenario(OpenMainScreenScenario(composeTestRule, ProductType.Note))
ComposeScreen.onComposeScreen<TestTopBar>(composeTestRule) { onTopBar {
step("Open wallet details") { step("Open wallet details") {
moreButton.clickWithAssertion() moreButton.clickWithAssertion()
} }
} }
ComposeScreen.onComposeScreen<DetailsTestScreen>(composeTestRule) { onDetailsScreen {
step("Assert wallet connect button does not exist") { step("Assert 'Wallet connect' button does not exist") {
walletConnectButton.assertIsNotDisplayed() walletConnectButton.assertIsNotDisplayed()
} }
step("Assert scan card button is visible") { step("Assert 'Scan card' button is visible") {
scanCardButton.assertIsDisplayed() scanCardButton.assertIsDisplayed()
} }
step("Assert buy Tangem card button is visible") { step("Assert 'Buy Tangem card' button is visible") {
buyTangemButton.assertIsDisplayed() buyTangemButton.assertIsDisplayed()
} }
step("Assert app settings button is visible") { step("Assert 'App settings' button is visible") {
appSettingsButton.assertIsDisplayed() appSettingsButton.assertIsDisplayed()
} }
step("Assert contact support button is visible") { step("Assert 'Contact support' button is visible") {
contactSupportButton.assertIsDisplayed() contactSupportButton.assertIsDisplayed()
} }
step("Assert terms or service button is visible") { step("Assert 'Terms or service' button is visible") {
toSButton.assertIsDisplayed() toSButton.assertIsDisplayed()
} }
step("Open wallet settings screen") { step("Open 'Wallet settings' screen") {
walletNameButton.clickWithAssertion() walletNameButton.clickWithAssertion()
} }
} }
ComposeScreen.onComposeScreen<WalletSettingsTestScreen>(composeTestRule) { onWalletSettingsScreen {
step("Assert Card Settings button is visible") { step("Assert 'Card Settings' button is visible") {
cardSettingsButton.assertIsDisplayed() cardSettingsButton.assertIsDisplayed()
} }
step("Assert Referral program button does not exist") { step("Assert 'Referral program' button does not exist") {
referralProgramButton.assertIsNotDisplayed() referralProgramButton.assertIsNotDisplayed()
} }
step("Assert Forget wallet button is visible") { step("Assert 'Forget wallet' button is visible") {
forgetWalletButton.assertIsDisplayed() forgetWalletButton.assertIsDisplayed()
} }
} }

View file

@ -0,0 +1,58 @@
package com.tangem.tests
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.scenarios.OpenMainScreenScenario
import com.tangem.screens.*
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class HideTokenTest : BaseTestCase() {
@AllureId("880")
@DisplayName("Hide token in 'Token details' screen by 'Hide' button in topBar menu")
@Test
fun hideWalletTokenByHideButtonTest() {
val tokenTitle = "Polygon"
val balance = "<$0.01"
setupHooks().run {
step("Open 'Main Screen'") {
scenario(OpenMainScreenScenario(composeTestRule))
}
step("Click on 'Synchronize addresses' button" ) {
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
}
step("Assert wallet balance = $balance") {
onMainScreen { walletBalance().assertTextContains(balance) }
}
step("Click on token with name: '$tokenTitle'") {
onMainScreen { tokenWithTitleAndAddress(tokenTitle).clickWithAssertion() }
}
step("Assert 'Token details screen' open") {
onTokenDetailsScreen { screenContainer.assertIsDisplayed() }
}
step("Click 'More button'") {
onTokenDetailsTopBar { moreButton.clickWithAssertion() }
}
step("Click 'Hide token' button") {
onPopUpMenu {
popUpContainer.assertIsDisplayed()
hideTokenButton.clickWithAssertion()
}
}
step("Click 'Hide' button in dialog") {
onDialog {
dialogContainer.assertIsDisplayed()
hideButton.clickWithAssertion()
}
}
step("Assert token: '$tokenTitle' is not displayed") {
onMainScreen { assertTokenDoesNotExist(tokenTitle) }
}
}
}
}

View file

@ -1,11 +1,8 @@
package com.tangem.tests package com.tangem.tests
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.scenarios.OpenMainScreenScenario import com.tangem.scenarios.OpenMainScreenScenario
import com.tangem.tap.MainActivity
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Rule
import org.junit.Test import org.junit.Test
@HiltAndroidTest @HiltAndroidTest

View file

@ -0,0 +1,206 @@
package com.tangem.tests
import androidx.compose.ui.test.onAllNodesWithText
import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.scenarios.OpenMainScreenScenario
import com.tangem.screens.onMainScreen
import com.tangem.screens.onOrganizeTokensScreen
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class OrganizeTokensTest : BaseTestCase() {
@AllureId("2755")
@DisplayName("Organize tokens: group tokens")
@Test
fun groupTokensTest() {
setupHooks().run {
val tokenTitle = "Ethereum"
val tokenNetwork = "Ethereum network"
step("Open 'Main Screen'") {
scenario(OpenMainScreenScenario(composeTestRule))
}
step("Click on 'Synchronize addresses' button" ) {
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
}
step("Click 'Organize tokens' button") {
onMainScreen { organizeTokensButton().clickWithAssertion() }
}
step("Assert 'Organize tokens' screen is opened") {
onOrganizeTokensScreen {
title.assertIsDisplayed()
tokenWithTitle(tokenTitle).assertIsDisplayed()
}
}
step("Click 'Group' button") {
onOrganizeTokensScreen { groupButton.clickWithAssertion() }
}
step("Assert tokens were grouped on 'Organize tokens' screen") {
onOrganizeTokensScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsDisplayed() }
}
step("Click 'Apply' button") {
onOrganizeTokensScreen { applyButton.clickWithAssertion() }
}
step("Assert tokens were grouped on 'Main screen'") {
onMainScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsDisplayed() }
}
step("Click 'Organize tokens' button") {
onMainScreen { organizeTokensButton().clickWithAssertion() }
}
step("Assert 'Organize tokens' screen is opened") {
onOrganizeTokensScreen {
title.assertIsDisplayed()
tokenWithTitle(tokenTitle).assertIsDisplayed()
}
}
step("Click 'Ungroup' button") {
onOrganizeTokensScreen { ungroupButton.clickWithAssertion() }
}
step("Assert tokens were ungrouped on 'Organize tokens' screen") {
onOrganizeTokensScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsNotDisplayed() }
}
step("Click 'Apply' button") {
onOrganizeTokensScreen { applyButton.clickWithAssertion() }
}
step("Assert tokens were ungrouped on 'Main screen'") {
onMainScreen { tokenNetworkGroupTitle(tokenNetwork).assertIsNotDisplayed() }
}
}
}
@AllureId("2752")
@DisplayName("Organize tokens: check position of tokens")
@Test
fun checkPositionOfTokensTest() {
setupHooks().run {
val ethereumTitle = "Ethereum"
val bitcoinTitle = "Bitcoin"
step("Open 'Main Screen'") {
scenario(OpenMainScreenScenario(composeTestRule))
}
step("Click on 'Synchronize addresses' button" ) {
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
}
step("Check positions of tokens on 'Main Screen'") {
onMainScreen {
tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed()
tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed()
}
}
step("Click 'Organize tokens' button") {
onMainScreen { organizeTokensButton().clickWithAssertion() }
}
step("Check positions of tokens on 'Organize tokens' screen") {
onOrganizeTokensScreen {
tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed()
tokenWithTitleAndPosition(ethereumTitle, 2).assertIsDisplayed()
}
}
}
}
@AllureId("2753")
@DisplayName("Organize tokens: check position of tokens")
// toDo: on test build there is not ability to drag element
// @Test
fun checkCustomTokensOrderTest() {
setupHooks().run {
val ethereumTitle = "Ethereum"
val bitcoinTitle = "Bitcoin"
step("Open 'Main Screen'") {
scenario(OpenMainScreenScenario(composeTestRule))
}
step("Click on 'Synchronize addresses' button" ) {
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
}
step("Check positions of tokens on 'Main Screen'") {
onMainScreen {
tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed()
tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed()
}
}
step("Click 'Organize tokens' button") {
onMainScreen { organizeTokensButton().clickWithAssertion() }
}
step("Drag $bitcoinTitle down on 'Organize tokens' screen") {
composeTestRule.waitUntil(timeoutMillis = 100_000) {
composeTestRule.onAllNodesWithText("Data loaded").fetchSemanticsNodes().isNotEmpty()
}
onOrganizeTokensScreen {
tokenWithTitleAndPosition(bitcoinTitle, 2).assertIsDisplayed()
}
}
step("Click 'Apply' button") {
onOrganizeTokensScreen { applyButton.clickWithAssertion() }
}
step("Check positions of tokens on 'Main Screen'") {
onMainScreen {
tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed()
tokenWithTitleAndPosition(ethereumTitle, 0).assertIsDisplayed()
}
}
}
}
@AllureId("2754")
@DisplayName("Organize tokens: sort by balance")
@Test
fun checkSortByBalanceTest() {
setupHooks().run {
val ethereumTitle = "Ethereum"
val bitcoinTitle = "Bitcoin"
val polygonTitle = "Polygon"
step("Open 'Main Screen'") {
scenario(OpenMainScreenScenario(composeTestRule))
}
step("Click on 'Synchronize addresses' button" ) {
onMainScreen { synchronizeAddressesButton.clickWithAssertion() }
}
step("Check positions of tokens on 'Main Screen'") {
onMainScreen {
tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed()
tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed()
tokenWithTitleAndPosition(polygonTitle, 2).assertIsDisplayed()
}
}
step("Click 'Organize tokens' button") {
onMainScreen { organizeTokensButton().clickWithAssertion() }
}
step("Check positions of tokens on 'Organize tokens' screen") {
onOrganizeTokensScreen {
tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed()
tokenWithTitleAndPosition(ethereumTitle, 2).assertIsDisplayed()
tokenWithTitleAndPosition(polygonTitle, 3).assertIsDisplayed()
}
}
step("Click 'By Balance' button") {
onOrganizeTokensScreen {
sortByBalanceButton.clickWithAssertion()
}
}
step("Check positions of tokens by balance on 'Organize tokens' screen") {
onOrganizeTokensScreen {
tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed()
tokenWithTitleAndPosition(polygonTitle, 2).assertIsDisplayed()
tokenWithTitleAndPosition(bitcoinTitle, 3).assertIsDisplayed()
}
}
step("Click 'Apply' button") {
onOrganizeTokensScreen { applyButton.clickWithAssertion() }
}
step("Check positions of tokens by balance on 'Organize tokens' screen") {
onMainScreen {
tokenWithTitleAndPosition(ethereumTitle, 0).assertIsDisplayed()
tokenWithTitleAndPosition(polygonTitle, 1).assertIsDisplayed()
tokenWithTitleAndPosition(bitcoinTitle, 2).assertIsDisplayed()
}
}
}
}
}

View file

@ -2,36 +2,35 @@ package com.tangem.tests
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.screens.DisclaimerTestScreen import com.tangem.screens.onDisclaimerScreen
import com.tangem.screens.MainTestScreen import com.tangem.screens.onMainScreen
import com.tangem.screens.StoriesTestScreen import com.tangem.screens.onStoriesScreen
import com.tangem.tap.domain.sdk.mocks.MockProvider import com.tangem.tap.domain.sdk.mocks.MockProvider
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.compose.node.element.ComposeScreen
import org.junit.Test import org.junit.Test
@HiltAndroidTest @HiltAndroidTest
class ScanErrorTest : BaseTestCase() { class ScanErrorTest : BaseTestCase() {
@Test @Test
fun goToMain() = fun goToMainTest() =
setupHooks().run { setupHooks().run {
ComposeScreen.onComposeScreen<DisclaimerTestScreen>(composeTestRule) { onDisclaimerScreen {
step("Click on \"Accept\" button") { step("Click on 'Accept' button") {
acceptButton.clickWithAssertion() acceptButton.clickWithAssertion()
} }
} }
ComposeScreen.onComposeScreen<StoriesTestScreen>(composeTestRule) { onStoriesScreen {
step("Click on \"Scan\" button emulating scan error") { step("Click on 'Scan' button emulating scan error") {
MockProvider.setEmulateError() MockProvider.setEmulateError()
scanButton.clickWithAssertion() scanButton.clickWithAssertion()
} }
step("Click on \"Scan\" button again without emulating error") { step("Click on 'Scan' button again without emulating error") {
MockProvider.resetEmulateError() MockProvider.resetEmulateError()
scanButton.clickWithAssertion() scanButton.clickWithAssertion()
} }
} }
ComposeScreen.onComposeScreen<MainTestScreen>(composeTestRule) { onMainScreen {
step("Make sure wallet screen is visible") { step("Make sure wallet screen is visible") {
assertIsDisplayed() assertIsDisplayed()
} }

View file

@ -1,31 +1,28 @@
package com.tangem.tests package com.tangem.tests
import android.content.Intent.ACTION_VIEW import android.content.Intent.ACTION_VIEW
import androidx.test.espresso.intent.matcher.UriMatchers
import com.tangem.common.BaseTestCase import com.tangem.common.BaseTestCase
import com.tangem.common.extensions.clickWithAssertion import com.tangem.common.extensions.clickWithAssertion
import com.tangem.screens.DisclaimerTestScreen import com.tangem.screens.onDisclaimerScreen
import com.tangem.screens.StoriesTestScreen import com.tangem.screens.onStoriesScreen
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.kakao.intent.KIntent import io.github.kakaocup.kakao.intent.KIntent
import org.hamcrest.Matchers
import org.junit.Test import org.junit.Test
@HiltAndroidTest @HiltAndroidTest
class StoriesTest : BaseTestCase() { class StoriesTest : BaseTestCase() {
@Test @Test
fun clickOnOrderButton() = fun clickOnOrderButtonTest() =
setupHooks().run { setupHooks().run {
ComposeScreen.onComposeScreen<DisclaimerTestScreen>(composeTestRule) { onDisclaimerScreen {
step("Click on \"Accept\" button") { step("Click on 'Accept' button") {
acceptButton.clickWithAssertion() acceptButton.clickWithAssertion()
} }
} }
ComposeScreen.onComposeScreen<StoriesTestScreen>(composeTestRule) { onStoriesScreen {
step("Click on \"Order\" button") { step("Click on 'Order' button") {
orderButton.clickWithAssertion() orderButton.clickWithAssertion()
} }
step("Assert: browser opened") { step("Assert: browser opened") {

View file

@ -39,8 +39,6 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles 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.analytics.handlers.BlockchainExceptionHandler
import com.tangem.tap.common.log.TangemAppLoggerInitializer import com.tangem.tap.common.log.TangemAppLoggerInitializer
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
@ -118,8 +116,6 @@ interface ApplicationEntryPoint {
fun getTransactionSignerFactory(): TransactionSignerFactory fun getTransactionSignerFactory(): TransactionSignerFactory
fun getOnrampFeatureToggles(): OnrampFeatureToggles
fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles
fun getOnboardingRepository(): OnboardingRepository fun getOnboardingRepository(): OnboardingRepository
@ -141,8 +137,6 @@ interface ApplicationEntryPoint {
fun getWorkerFactory(): HiltWorkerFactory fun getWorkerFactory(): HiltWorkerFactory
fun getOnlineCardVerifier(): OnlineCardVerifier
fun getColdUserWalletBuilderFactory(): ColdUserWalletBuilder.Factory fun getColdUserWalletBuilderFactory(): ColdUserWalletBuilder.Factory
fun getApiConfigsManager(): ApiConfigsManager fun getApiConfigsManager(): ApiConfigsManager

View file

@ -26,17 +26,13 @@ import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute 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.common.routing.entity.SerializableIntent
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.di.RootAppComponentContext 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.navigation.url.UrlOpener
import com.tangem.core.ui.UiDependencies
import com.tangem.data.balancehiding.DefaultDeviceFlipDetector import com.tangem.data.balancehiding.DefaultDeviceFlipDetector
import com.tangem.data.card.sdk.CardSdkOwner import com.tangem.data.card.sdk.CardSdkOwner
import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.apptheme.model.AppThemeMode
@ -53,6 +49,7 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.features.tester.api.TesterMenuLauncher
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
import com.tangem.google.GoogleServicesHelper import com.tangem.google.GoogleServicesHelper
import com.tangem.operations.backup.BackupService import com.tangem.operations.backup.BackupService
@ -69,7 +66,6 @@ import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandle
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
import com.tangem.tap.features.main.MainViewModel import com.tangem.tap.features.main.MainViewModel
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphAction import com.tangem.tap.proxy.redux.DaggerGraphAction
import com.tangem.tap.routing.component.RoutingComponent import com.tangem.tap.routing.component.RoutingComponent
import com.tangem.tap.routing.configurator.AppRouterConfig import com.tangem.tap.routing.configurator.AppRouterConfig
@ -103,9 +99,6 @@ val mainScope = CoroutineScope(mainCoroutineContext)
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject
lateinit var appStateHolder: AppStateHolder
/** Router for opening tester menu */ /** Router for opening tester menu */
@Inject @Inject
lateinit var cardSdkOwner: CardSdkOwner lateinit var cardSdkOwner: CardSdkOwner
@ -122,9 +115,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject @Inject
lateinit var walletConnectInteractor: WalletConnectInteractor lateinit var walletConnectInteractor: WalletConnectInteractor
@Inject
lateinit var deepLinksRegistry: DeepLinksRegistry
@Inject @Inject
lateinit var settingsRepository: SettingsRepository lateinit var settingsRepository: SettingsRepository
@ -143,9 +133,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject @Inject
lateinit var userWalletsListManager: UserWalletsListManager lateinit var userWalletsListManager: UserWalletsListManager
@Inject
lateinit var emailSender: EmailSender
@Inject @Inject
@RootAppComponentContext @RootAppComponentContext
internal lateinit var rootComponentContext: AppComponentContext internal lateinit var rootComponentContext: AppComponentContext
@ -174,15 +161,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject @Inject
lateinit var dispatchers: CoroutineDispatcherProvider lateinit var dispatchers: CoroutineDispatcherProvider
@Inject
internal lateinit var uiDependencies: UiDependencies
@Inject @Inject
internal lateinit var defaultDeviceFlipDetector: DefaultDeviceFlipDetector internal lateinit var defaultDeviceFlipDetector: DefaultDeviceFlipDetector
@Inject
internal lateinit var routingFeatureToggle: RoutingFeatureToggle
@Inject @Inject
internal lateinit var deeplinkFactory: DeepLinkFactory internal lateinit var deeplinkFactory: DeepLinkFactory
@ -192,6 +173,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject @Inject
internal lateinit var urlOpener: UrlOpener internal lateinit var urlOpener: UrlOpener
@Inject
internal lateinit var testerMenuLauncher: TesterMenuLauncher
internal val viewModel: MainViewModel by viewModels() internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode> private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
@ -244,13 +228,12 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
sendStakingUnsubmittedHashes() sendStakingUnsubmittedHashes()
checkGoogleServicesAvailability() 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(WindowObscurationObserver)
lifecycle.addObserver(defaultDeviceFlipDetector) lifecycle.addObserver(defaultDeviceFlipDetector)
if (BuildConfig.TESTER_MENU_ENABLED) {
lifecycle.addObserver(testerMenuLauncher.launchOnShakeObserver)
}
} }
private fun setRootContent() { private fun setRootContent() {
@ -481,7 +464,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
} }
} }
if (routingFeatureToggle.isDeepLinkNavigationEnabled && intent != null) { if (intent != null) {
handleDeepLink(intent = intent, isFromOnNewIntent = false) handleDeepLink(intent = intent, isFromOnNewIntent = false)
} }
@ -489,26 +472,22 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
} }
private fun handleDeepLink(intent: Intent, isFromOnNewIntent: Boolean) { private fun handleDeepLink(intent: Intent, isFromOnNewIntent: Boolean) {
if (routingFeatureToggle.isDeepLinkNavigationEnabled) { val deepLinkExtras = PayloadToDeeplinkConverter.convertBundle(intent.extras)?.toUri()
val deepLinkExtras = PayloadToDeeplinkConverter.convertBundle(intent.extras)?.toUri() val webLink = intent.getStringExtra(WEBLINK_KEY)
val webLink = intent.getStringExtra(WEBLINK_KEY)
val receivedDeepLink = intent.data ?: deepLinkExtras val receivedDeepLink = intent.data ?: deepLinkExtras
when { when {
receivedDeepLink != null -> { receivedDeepLink != null -> {
deeplinkFactory.handleDeeplink( deeplinkFactory.handleDeeplink(
deeplinkUri = receivedDeepLink, deeplinkUri = receivedDeepLink,
coroutineScope = lifecycleScope, coroutineScope = lifecycleScope,
isFromOnNewIntent = isFromOnNewIntent, isFromOnNewIntent = isFromOnNewIntent,
) )
} }
webLink?.uriValidate() == true -> { webLink?.uriValidate() == true -> {
urlOpener.openUrl(webLink) urlOpener.openUrl(webLink)
}
} }
} else {
deepLinksRegistry.launch(intent)
} }
} }

View file

@ -58,8 +58,6 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles 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.operations.attestation.api.TangemApiServiceSettings
import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.AnalyticsFactory
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
@ -79,7 +77,6 @@ import com.tangem.wallet.BuildConfig
import dagger.hilt.EntryPoints import dagger.hilt.EntryPoints
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.rekotlin.Store import org.rekotlin.Store
import kotlin.collections.set
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository as WalletConnect2Repository import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository as WalletConnect2Repository
lateinit var store: Store<AppState> lateinit var store: Store<AppState>
@ -186,9 +183,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
private val transactionSignerFactory: TransactionSignerFactory private val transactionSignerFactory: TransactionSignerFactory
get() = entryPoint.getTransactionSignerFactory() get() = entryPoint.getTransactionSignerFactory()
private val onrampFeatureToggles: OnrampFeatureToggles
get() = entryPoint.getOnrampFeatureToggles()
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles
get() = entryPoint.getOnboardingV2FeatureToggles() get() = entryPoint.getOnboardingV2FeatureToggles()
@ -224,9 +218,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
.setWorkerFactory(workerFactory) .setWorkerFactory(workerFactory)
.build() .build()
private val onlineCardVerifier: OnlineCardVerifier
get() = entryPoint.getOnlineCardVerifier()
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory
get() = entryPoint.getColdUserWalletBuilderFactory() get() = entryPoint.getColdUserWalletBuilderFactory()
@ -363,7 +354,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
shareManager = shareManager, shareManager = shareManager,
appRouter = appRouter, appRouter = appRouter,
transactionSignerFactory = transactionSignerFactory, transactionSignerFactory = transactionSignerFactory,
onrampFeatureToggles = onrampFeatureToggles,
environmentConfigStorage = environmentConfigStorage, environmentConfigStorage = environmentConfigStorage,
onboardingV2FeatureToggles = onboardingV2FeatureToggles, onboardingV2FeatureToggles = onboardingV2FeatureToggles,
onboardingRepository = onboardingRepository, onboardingRepository = onboardingRepository,
@ -372,7 +362,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
clipboardManager = clipboardManager, clipboardManager = clipboardManager,
settingsManager = settingsManager, settingsManager = settingsManager,
uiMessageSender = uiMessageSender, uiMessageSender = uiMessageSender,
onlineCardVerifier = onlineCardVerifier,
coldUserWalletBuilderFactory = coldUserWalletBuilderFactory, coldUserWalletBuilderFactory = coldUserWalletBuilderFactory,
userTokensResponseStore = userTokensResponseStore, userTokensResponseStore = userTokensResponseStore,
), ),

View file

@ -2,6 +2,7 @@ package com.tangem.tap.common.extensions
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
@ -21,6 +22,15 @@ fun Analytics.setContext(scanResponse: ScanResponse) {
addParamsInterceptor(LinkedCardContextInterceptor(scanResponse)) addParamsInterceptor(LinkedCardContextInterceptor(scanResponse))
} }
fun Analytics.setContext(userWallet: UserWallet) {
setUserId(userWallet.walletId.stringValue)
// TODO add product type for hot ([REDACTED_TASK_KEY])
if (userWallet is UserWallet.Cold) {
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
}
}
/** /**
* Erases the context * Erases the context
*/ */

View file

@ -4,8 +4,8 @@ import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.redux.StateDialog 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.AppState
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapError

View file

@ -3,16 +3,11 @@ package com.tangem.tap.common.extensions
import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchainsdk.utils.amountToCreateAccount import com.tangem.blockchainsdk.utils.amountToCreateAccount
import com.tangem.common.services.Result import com.tangem.common.services.Result
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.tap.common.TestActions import com.tangem.tap.common.TestActions
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.getFirstToken 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.proxy.redux.DaggerGraphState
import com.tangem.tap.store import com.tangem.tap.store
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -57,43 +52,4 @@ suspend fun WalletManager.safeUpdate(isDemoCard: Boolean): Result<Wallet> = 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<WalletAddressData> {
val listOfAddressData = mutableListOf<WalletAddressData>()
// 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
} }

View file

@ -6,6 +6,7 @@ import android.util.Log
import coil.ImageLoader import coil.ImageLoader
import coil.decode.GifDecoder import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder import coil.decode.ImageDecoderDecoder
import coil.decode.SvgDecoder
import coil.memory.MemoryCache import coil.memory.MemoryCache
import coil.request.CachePolicy import coil.request.CachePolicy
import coil.util.Logger import coil.util.Logger
@ -36,6 +37,7 @@ fun createCoilImageLoader(context: Context, logEnabled: Boolean = false): ImageL
} else { } else {
add(GifDecoder.Factory()) add(GifDecoder.Factory())
} }
add(SvgDecoder.Factory())
} }
.memoryCachePolicy(CachePolicy.ENABLED) .memoryCachePolicy(CachePolicy.ENABLED)
.memoryCache { .memoryCache {

View file

@ -30,21 +30,5 @@ sealed class GlobalAction : Action {
data class Success(val appCurrency: AppCurrency) : GlobalAction() 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() 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()
}
} }

View file

@ -2,23 +2,14 @@ package com.tangem.tap.common.redux.global
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.models.AnalyticsParam 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.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.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog import com.tangem.domain.redux.StateDialog
import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.* 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.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.proxy.redux.DaggerGraphState
import com.tangem.tap.scope import com.tangem.tap.scope
import com.tangem.tap.store import com.tangem.tap.store
@ -31,17 +22,17 @@ object GlobalMiddleware {
val handler = globalMiddlewareHandler val handler = globalMiddlewareHandler
} }
private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState -> private val globalMiddlewareHandler: Middleware<AppState> = { _, _ ->
{ nextDispatch -> { nextDispatch ->
{ action -> { action ->
handleAction(action, appState) handleAction(action)
nextDispatch(action) nextDispatch(action)
} }
} }
} }
@Suppress("LongMethod", "ComplexMethod") @Suppress("LongMethod", "ComplexMethod")
private fun handleAction(action: Action, appState: () -> AppState?) { private fun handleAction(action: Action) {
when (action) { when (action) {
is GlobalAction.ScanFailsCounter.ChooseBehavior -> { is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
when (action.result) { when (action.result) {
@ -51,42 +42,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
} }
} }
} }
is GlobalAction.RestoreAppCurrency -> { is GlobalAction.RestoreAppCurrency -> 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() }
}
} }
} }
@ -119,21 +75,4 @@ private fun restoreAppCurrency() {
store.dispatchWithMain(GlobalAction.RestoreAppCurrency.Success(currency)) 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,
),
)
} }

View file

@ -1,7 +1,6 @@
package com.tangem.tap.common.redux.global package com.tangem.tap.common.redux.global
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.utils.extensions.replaceBy
import org.rekotlin.Action import org.rekotlin.Action
@Suppress("LongMethod", "ComplexMethod") @Suppress("LongMethod", "ComplexMethod")
@ -26,24 +25,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.RestoreAppCurrency.Success -> { is GlobalAction.RestoreAppCurrency.Success -> {
globalState.copy(appCurrency = action.appCurrency) 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.IsSignWithRing -> globalState.copy(isLastSignWithRing = action.isSignWithRing)
is GlobalAction.ShowDialog -> { is GlobalAction.ShowDialog -> {
globalState.copy(dialog = action.stateDialog) globalState.copy(dialog = action.stateDialog)
@ -51,9 +32,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.HideDialog -> { is GlobalAction.HideDialog -> {
globalState.copy(dialog = null) globalState.copy(dialog = null)
} }
is GlobalAction.ExchangeManager.Init.Success -> {
globalState.copy(exchangeManager = action.exchangeManager)
}
else -> globalState else -> globalState
} }
} }

View file

@ -4,8 +4,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog import com.tangem.domain.redux.StateDialog
import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import org.rekotlin.StateType import org.rekotlin.StateType
data class GlobalState( data class GlobalState(
@ -16,7 +14,6 @@ data class GlobalState(
val appCurrency: AppCurrency = AppCurrency.Default, val appCurrency: AppCurrency = AppCurrency.Default,
val scanCardFailsCounter: Int = 0, val scanCardFailsCounter: Int = 0,
val dialog: StateDialog? = null, val dialog: StateDialog? = null,
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
val isLastSignWithRing: Boolean = false, val isLastSignWithRing: Boolean = false,
) : StateType ) : StateType
@ -24,6 +21,5 @@ typealias CryptoCurrencyName = String
data class OnboardingState( data class OnboardingState(
val onboardingStarted: Boolean = false, val onboardingStarted: Boolean = false,
val onboardingManager: OnboardingManager? = null,
val shouldResetOnCreate: Boolean = false, val shouldResetOnCreate: Boolean = false,
) )

View file

@ -1,8 +1,8 @@
package com.tangem.tap.common.redux.legacy package com.tangem.tap.common.redux.legacy
import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.redux.LegacyAction 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.dispatchWithMain
import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
@ -37,8 +37,7 @@ internal object LegacyMiddleware {
) )
store.dispatchWithMain( store.dispatchWithMain(
DetailsAction.PrepareScreen( DetailsAction.PrepareScreen(
// TODO [REDACTED_TASK_KEY] scanResponse = (selectedUserWallet as? UserWallet.Cold)?.scanResponse,
scanResponse = selectedUserWallet.requireColdWallet().scanResponse,
initializedAppSettingsState = initializedAppSettingsStateContent, initializedAppSettingsState = initializedAppSettingsStateContent,
), ),
) )
@ -67,7 +66,7 @@ internal object LegacyMiddleware {
?: AppThemeMode.DEFAULT, ?: AppThemeMode.DEFAULT,
isHidingEnabled = store.inject(DaggerGraphState::balanceHidingRepository) isHidingEnabled = store.inject(DaggerGraphState::balanceHidingRepository)
.getBalanceHidingSettings().isHidingEnabledInSettings, .getBalanceHidingSettings().isHidingEnabledInSettings,
needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() ?: false, needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() == true,
) )
} }
} }

View file

@ -16,16 +16,13 @@ import com.tangem.crypto.bip39.Wordlist
import com.tangem.data.card.sdk.CardSdkOwner import com.tangem.data.card.sdk.CardSdkOwner
import com.tangem.data.card.sdk.CardSdkProvider import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.datasource.api.common.config.ApiConfig 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.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.api.common.config.managers.MutableApiConfigsManager
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectMap
import com.tangem.datasource.utils.AddHeadersInterceptor import com.tangem.datasource.utils.AddHeadersInterceptor
import com.tangem.datasource.utils.RequestHeader import com.tangem.datasource.utils.RequestHeader
import com.tangem.operations.attestation.api.TangemApiServiceSettings import com.tangem.operations.attestation.api.TangemApiServiceSettings
import com.tangem.sdk.DefaultSessionViewDelegate import com.tangem.sdk.DefaultSessionViewDelegate
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
import com.tangem.sdk.extensions.* import com.tangem.sdk.extensions.*
import com.tangem.sdk.nfc.AndroidNfcAvailabilityProvider import com.tangem.sdk.nfc.AndroidNfcAvailabilityProvider
import com.tangem.sdk.nfc.NfcManager 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.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.info.AppInfoProvider import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider 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 kotlinx.coroutines.runBlocking
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -55,11 +45,9 @@ import javax.inject.Singleton
internal class DefaultCardSdkProvider @Inject constructor( internal class DefaultCardSdkProvider @Inject constructor(
private val analyticsExceptionHandler: AnalyticsExceptionHandler, private val analyticsExceptionHandler: AnalyticsExceptionHandler,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val cardSdkFeatureToggles: CardSdkFeatureToggles,
private val apiConfigsManager: ApiConfigsManager, private val apiConfigsManager: ApiConfigsManager,
appVersionProvider: AppVersionProvider, appVersionProvider: AppVersionProvider,
appInfoProvider: AppInfoProvider, appInfoProvider: AppInfoProvider,
appPreferencesStore: AppPreferencesStore,
) : CardSdkProvider, CardSdkOwner { ) : CardSdkProvider, CardSdkOwner {
private val observer = Observer() private val observer = Observer()
@ -70,26 +58,15 @@ internal class DefaultCardSdkProvider @Inject constructor(
get() = holder?.sdk ?: tryToRegisterWithForegroundActivity() get() = holder?.sdk ?: tryToRegisterWithForegroundActivity()
init { init {
if (BuildConfig.TESTER_MENU_ENABLED) { val mutableManager = apiConfigsManager as? MutableApiConfigsManager
appPreferencesStore.getObjectMap<ApiEnvironment>(PreferencesKeys.apiConfigsEnvironmentKey)
.map { mutableManager?.addListener(
when (it[ApiConfig.ID.Attestation.name]) { object : MutableApiConfigsManager.ApiConfigEnvChangeListener(id = ApiConfig.ID.TangemTech) {
ApiEnvironment.DEV, override fun onChange(environmentConfig: ApiEnvironmentConfig) {
ApiEnvironment.STAGE, holder?.sdk?.config?.tangemApiBaseUrl = environmentConfig.baseUrl
-> false
ApiEnvironment.PROD,
null,
-> true
}
} }
.distinctUntilChanged() },
.onEach { isProd -> )
holder?.let {
it.sdk.config.isTangemAttestationProdEnv = isProd
}
}
.launchIn(CoroutineScope(SupervisorJob() + dispatchers.main))
}
TangemApiServiceSettings.addInterceptors( TangemApiServiceSettings.addInterceptors(
AddHeadersInterceptor( AddHeadersInterceptor(
@ -187,10 +164,8 @@ internal class DefaultCardSdkProvider @Inject constructor(
keystoreManager = keystoreManager, keystoreManager = keystoreManager,
wordlist = Wordlist.getWordlist(activity), wordlist = Wordlist.getWordlist(activity),
config = config.apply { config = config.apply {
isNewOnlineAttestationEnabled = cardSdkFeatureToggles.isNewAttestationEnabled val apiConfig = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.TangemTech)
tangemApiBaseUrl = apiConfig.baseUrl
val apiConfig = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.Attestation)
isTangemAttestationProdEnv = apiConfig.environment == ApiEnvironment.PROD
}, },
) )

View file

@ -2,9 +2,9 @@ package com.tangem.tap.data
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.datasource.local.userwallet.UserWalletsStore 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.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.Flow
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull

View file

@ -9,7 +9,6 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.sdk.api.TangemSdkManager import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
import com.tangem.tap.network.exchangeServices.DefaultRampManager import com.tangem.tap.network.exchangeServices.DefaultRampManager
@ -51,17 +50,13 @@ internal object ActivityModule {
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
onrampFeatureToggles: OnrampFeatureToggles,
): RampStateManager { ): RampStateManager {
return DefaultRampManager( return DefaultRampManager(
exchangeService = appStateHolder.exchangeService,
buyService = Provider { requireNotNull(appStateHolder.buyService) },
sellService = Provider { requireNotNull(appStateHolder.sellService) }, sellService = Provider { requireNotNull(appStateHolder.sellService) },
expressServiceLoader = expressServiceLoader, expressServiceLoader = expressServiceLoader,
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
excludedBlockchains = excludedBlockchains,
dispatchers = dispatchers, dispatchers = dispatchers,
onrampFeatureToggles = onrampFeatureToggles, excludedBlockchains = excludedBlockchains,
) )
} }

View file

@ -7,8 +7,6 @@ import com.tangem.domain.managetokens.repository.ManageTokensRepository
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher 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.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -71,22 +69,18 @@ internal object ManageTokensDomainModule {
walletManagersFacade: WalletManagersFacade, walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
derivationsRepository: DerivationsRepository, derivationsRepository: DerivationsRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): SaveManagedTokensUseCase { ): SaveManagedTokensUseCase {
return SaveManagedTokensUseCase( return SaveManagedTokensUseCase(
customTokensRepository = customTokensRepository, customTokensRepository = customTokensRepository,
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
derivationsRepository = derivationsRepository, derivationsRepository = derivationsRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
) )
} }

View file

@ -10,8 +10,6 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher 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.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import dagger.Module import dagger.Module
@ -62,21 +60,17 @@ object MarketsDomainModule {
derivationsRepository: DerivationsRepository, derivationsRepository: DerivationsRepository,
marketsTokenRepository: MarketsTokenRepository, marketsTokenRepository: MarketsTokenRepository,
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): SaveMarketTokensUseCase { ): SaveMarketTokensUseCase {
return SaveMarketTokensUseCase( return SaveMarketTokensUseCase(
derivationsRepository = derivationsRepository, derivationsRepository = derivationsRepository,
marketsTokenRepository = marketsTokenRepository, marketsTokenRepository = marketsTokenRepository,
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
) )
} }

View file

@ -5,6 +5,8 @@ import com.tangem.domain.nft.*
import com.tangem.domain.nft.repository.NFTRepository import com.tangem.domain.nft.repository.NFTRepository
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier 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.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -33,9 +35,13 @@ internal object NFTDomainModule {
fun providesFetchNFTCollectionsUseCase( fun providesFetchNFTCollectionsUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
nftRepository: NFTRepository, nftRepository: NFTRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): FetchNFTCollectionsUseCase = FetchNFTCollectionsUseCase( ): FetchNFTCollectionsUseCase = FetchNFTCollectionsUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
nftRepository = nftRepository, nftRepository = nftRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
) )
@Provides @Provides
@ -43,9 +49,13 @@ internal object NFTDomainModule {
fun providesRefreshAllNFTUseCase( fun providesRefreshAllNFTUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
nftRepository: NFTRepository, nftRepository: NFTRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): RefreshAllNFTUseCase = RefreshAllNFTUseCase( ): RefreshAllNFTUseCase = RefreshAllNFTUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
nftRepository = nftRepository, nftRepository = nftRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
) )
@Provides @Provides
@ -118,8 +128,16 @@ internal object NFTDomainModule {
walletsRepository: WalletsRepository, walletsRepository: WalletsRepository,
nftRepository: NFTRepository, nftRepository: NFTRepository,
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): DisableWalletNFTUseCase { ): DisableWalletNFTUseCase {
return DisableWalletNFTUseCase(walletsRepository, nftRepository, currenciesRepository) return DisableWalletNFTUseCase(
walletsRepository = walletsRepository,
nftRepository = nftRepository,
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
} }
@Provides @Provides

View file

@ -94,12 +94,10 @@ internal object StakingDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideFetchStakingYieldBalanceUseCase( fun provideFetchStakingYieldBalanceUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver, stakingErrorResolver: StakingErrorResolver,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher, singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
): FetchStakingYieldBalanceUseCase { ): FetchStakingYieldBalanceUseCase {
return FetchStakingYieldBalanceUseCase( return FetchStakingYieldBalanceUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver, stakingErrorResolver = stakingErrorResolver,
singleYieldBalanceFetcher = singleYieldBalanceFetcher, singleYieldBalanceFetcher = singleYieldBalanceFetcher,
) )

View file

@ -3,10 +3,7 @@ package com.tangem.tap.di.domain
import com.tangem.domain.swap.SwapErrorResolver import com.tangem.domain.swap.SwapErrorResolver
import com.tangem.domain.swap.SwapRepositoryV2 import com.tangem.domain.swap.SwapRepositoryV2
import com.tangem.domain.swap.SwapTransactionRepository import com.tangem.domain.swap.SwapTransactionRepository
import com.tangem.domain.swap.usecase.GetSwapPairsUseCase import com.tangem.domain.swap.usecase.*
import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase
import com.tangem.domain.swap.usecase.GetSwapSupportedPairsUseCase
import com.tangem.domain.swap.usecase.SelectInitialPairUseCase
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -73,4 +70,30 @@ internal object SwapDomainModule {
swapErrorResolver = swapErrorResolver, 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,
)
}
} }

View file

@ -22,6 +22,7 @@ import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.CurrencyChecksRepository import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -40,18 +41,18 @@ internal object TokensDomainModule {
@Singleton @Singleton
fun provideAddCryptoCurrenciesUseCase( fun provideAddCryptoCurrenciesUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher, singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles, tokensFeatureToggles: TokensFeatureToggles,
): AddCryptoCurrenciesUseCase { ): AddCryptoCurrenciesUseCase {
return AddCryptoCurrenciesUseCase( return AddCryptoCurrenciesUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher, singleYieldBalanceFetcher = singleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles, tokensFeatureToggles = tokensFeatureToggles,
) )
} }
@ -60,19 +61,15 @@ internal object TokensDomainModule {
@Singleton @Singleton
fun provideFetchTokenListUseCase( fun provideFetchTokenListUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): FetchTokenListUseCase { ): FetchTokenListUseCase {
return FetchTokenListUseCase( return FetchTokenListUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
) )
} }
@ -107,8 +104,15 @@ internal object TokensDomainModule {
fun provideRemoveCurrencyUseCase( fun provideRemoveCurrencyUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
walletManagersFacade: WalletManagersFacade, walletManagersFacade: WalletManagersFacade,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): RemoveCurrencyUseCase { ): RemoveCurrencyUseCase {
return RemoveCurrencyUseCase(currenciesRepository, walletManagersFacade) return RemoveCurrencyUseCase(
currenciesRepository = currenciesRepository,
walletManagersFacade = walletManagersFacade,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
)
} }
@Provides @Provides
@ -145,6 +149,8 @@ internal object TokensDomainModule {
currencyChecksRepository: CurrencyChecksRepository, currencyChecksRepository: CurrencyChecksRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
baseCurrencyStatusOperations: BaseCurrencyStatusOperations, baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): GetCurrencyWarningsUseCase { ): GetCurrencyWarningsUseCase {
return GetCurrencyWarningsUseCase( return GetCurrencyWarningsUseCase(
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
@ -152,6 +158,8 @@ internal object TokensDomainModule {
dispatchers = dispatchers, dispatchers = dispatchers,
currencyChecksRepository = currencyChecksRepository, currencyChecksRepository = currencyChecksRepository,
currencyStatusOperations = baseCurrencyStatusOperations, currencyStatusOperations = baseCurrencyStatusOperations,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
) )
} }
@ -159,18 +167,18 @@ internal object TokensDomainModule {
@Singleton @Singleton
fun provideFetchCurrencyStatusUseCase( fun provideFetchCurrencyStatusUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher, singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
singleYieldBalanceFetcher: SingleYieldBalanceFetcher, singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles, tokensFeatureToggles: TokensFeatureToggles,
): FetchCurrencyStatusUseCase { ): FetchCurrencyStatusUseCase {
return FetchCurrencyStatusUseCase( return FetchCurrencyStatusUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
singleNetworkStatusFetcher = singleNetworkStatusFetcher, singleNetworkStatusFetcher = singleNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher,
singleYieldBalanceFetcher = singleYieldBalanceFetcher, singleYieldBalanceFetcher = singleYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles, tokensFeatureToggles = tokensFeatureToggles,
) )
} }
@ -179,26 +187,26 @@ internal object TokensDomainModule {
@Singleton @Singleton
fun provideFetchCardTokenListUseCase( fun provideFetchCardTokenListUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
stakingRepository: StakingRepository,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher, multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
tokensFeatureToggles: TokensFeatureToggles,
): FetchCardTokenListUseCase { ): FetchCardTokenListUseCase {
return FetchCardTokenListUseCase( return FetchCardTokenListUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
stakingRepository = stakingRepository,
multiNetworkStatusFetcher = multiNetworkStatusFetcher, multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles,
) )
} }
@Provides @Provides
@Singleton @Singleton
fun provideGetCryptoCurrencyUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrencyUseCase { fun provideGetCryptoCurrencyUseCase(
return GetCryptoCurrencyUseCase(currenciesRepository) currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): GetCryptoCurrencyUseCase {
return GetCryptoCurrencyUseCase(currenciesRepository, multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles)
} }
@Provides @Provides
@ -219,9 +227,16 @@ internal object TokensDomainModule {
@Singleton @Singleton
fun provideApplyTokenListSortingUseCase( fun provideApplyTokenListSortingUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): ApplyTokenListSortingUseCase { ): ApplyTokenListSortingUseCase {
return ApplyTokenListSortingUseCase(currenciesRepository, dispatchers) return ApplyTokenListSortingUseCase(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
dispatchers = dispatchers,
)
} }
@Provides @Provides
@ -280,9 +295,13 @@ internal object TokensDomainModule {
@Singleton @Singleton
fun provideIsCryptoCurrencyCoinCouldHideUseCase( fun provideIsCryptoCurrencyCoinCouldHideUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): IsCryptoCurrencyCoinCouldHideUseCase { ): IsCryptoCurrencyCoinCouldHideUseCase {
return IsCryptoCurrencyCoinCouldHideUseCase( return IsCryptoCurrencyCoinCouldHideUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
) )
} }
@ -300,9 +319,16 @@ internal object TokensDomainModule {
@Singleton @Singleton
fun provideGetBalanceNotEnoughForFeeWarningUseCase( fun provideGetBalanceNotEnoughForFeeWarningUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): GetBalanceNotEnoughForFeeWarningUseCase { ): GetBalanceNotEnoughForFeeWarningUseCase {
return GetBalanceNotEnoughForFeeWarningUseCase(currenciesRepository, dispatchers) return GetBalanceNotEnoughForFeeWarningUseCase(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
dispatchers = dispatchers,
)
} }
@Provides @Provides
@ -343,10 +369,14 @@ internal object TokensDomainModule {
fun provideRefreshMultiCurrencyWalletQuotesUseCase( fun provideRefreshMultiCurrencyWalletQuotesUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher, multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): RefreshMultiCurrencyWalletQuotesUseCase { ): RefreshMultiCurrencyWalletQuotesUseCase {
return RefreshMultiCurrencyWalletQuotesUseCase( return RefreshMultiCurrencyWalletQuotesUseCase(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
multiQuoteStatusFetcher = multiQuoteStatusFetcher, multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
) )
} }
@ -374,6 +404,7 @@ internal object TokensDomainModule {
singleQuoteStatusSupplier: SingleQuoteStatusSupplier, singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
singleYieldBalanceSupplier: SingleYieldBalanceSupplier, singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
): BaseCurrenciesStatusesOperations { ): BaseCurrenciesStatusesOperations {
return CachedCurrenciesStatusesOperations( return CachedCurrenciesStatusesOperations(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
@ -388,6 +419,7 @@ internal object TokensDomainModule {
singleYieldBalanceSupplier = singleYieldBalanceSupplier, singleYieldBalanceSupplier = singleYieldBalanceSupplier,
multiYieldBalanceFetcher = multiYieldBalanceFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher,
tokensFeatureToggles = tokensFeatureToggles, tokensFeatureToggles = tokensFeatureToggles,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
) )
} }
@ -406,6 +438,7 @@ internal object TokensDomainModule {
singleQuoteStatusSupplier: SingleQuoteStatusSupplier, singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
singleYieldBalanceSupplier: SingleYieldBalanceSupplier, singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher, multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
): BaseCurrencyStatusOperations { ): BaseCurrencyStatusOperations {
return CachedCurrenciesStatusesOperations( return CachedCurrenciesStatusesOperations(
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
@ -419,6 +452,7 @@ internal object TokensDomainModule {
singleQuoteStatusSupplier = singleQuoteStatusSupplier, singleQuoteStatusSupplier = singleQuoteStatusSupplier,
singleYieldBalanceSupplier = singleYieldBalanceSupplier, singleYieldBalanceSupplier = singleYieldBalanceSupplier,
multiYieldBalanceFetcher = multiYieldBalanceFetcher, multiYieldBalanceFetcher = multiYieldBalanceFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles, tokensFeatureToggles = tokensFeatureToggles,
) )
} }
@ -428,4 +462,26 @@ internal object TokensDomainModule {
fun provideGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase { fun provideGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
return GetCryptoCurrenciesUseCase(currenciesRepository) return GetCryptoCurrenciesUseCase(currenciesRepository)
} }
@Provides
@Singleton
fun provideWalletBalanceFetcher(
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesFetcher: MultiWalletCryptoCurrenciesFetcher,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
dispatchers: CoroutineDispatcherProvider,
): WalletBalanceFetcher {
return WalletBalanceFetcher(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesFetcher = multiWalletCryptoCurrenciesFetcher,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
dispatchers = dispatchers,
)
}
} }

View file

@ -4,11 +4,14 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier 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.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.usecase.* import com.tangem.domain.transaction.usecase.*
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.tap.domain.hot.TangemHotWalletSigner
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -41,6 +44,7 @@ internal object TransactionDomainModule {
transactionRepository: TransactionRepository, transactionRepository: TransactionRepository,
walletManagersFacade: WalletManagersFacade, walletManagersFacade: WalletManagersFacade,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher, singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
): SendTransactionUseCase { ): SendTransactionUseCase {
return SendTransactionUseCase( return SendTransactionUseCase(
demoConfig = DemoConfig(), demoConfig = DemoConfig(),
@ -48,6 +52,7 @@ internal object TransactionDomainModule {
transactionRepository = transactionRepository, transactionRepository = transactionRepository,
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
singleNetworkStatusFetcher = singleNetworkStatusFetcher, singleNetworkStatusFetcher = singleNetworkStatusFetcher,
getHotWalletSigner = tangemHotWalletSignerFactory::create,
) )
} }
@ -58,12 +63,16 @@ internal object TransactionDomainModule {
walletManagersFacade: WalletManagersFacade, walletManagersFacade: WalletManagersFacade,
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
singleNetworkStatusSupplier: SingleNetworkStatusSupplier, singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles: TokensFeatureToggles,
): AssociateAssetUseCase { ): AssociateAssetUseCase {
return AssociateAssetUseCase( return AssociateAssetUseCase(
cardSdkConfigRepository = cardSdkConfigRepository, cardSdkConfigRepository = cardSdkConfigRepository,
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
currenciesRepository = currenciesRepository, currenciesRepository = currenciesRepository,
singleNetworkStatusSupplier = singleNetworkStatusSupplier, singleNetworkStatusSupplier = singleNetworkStatusSupplier,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
tokensFeatureToggles = tokensFeatureToggles,
) )
} }

View file

@ -15,10 +15,7 @@ import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.* import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase 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.operations.attestation.CardArtworksProvider
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -191,28 +188,14 @@ internal object WalletsDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideGetCardImageUseCase( fun provideGetCardImageUseCase(cardArtworksProvider: CardArtworksProvider): GetCardImageUseCase {
onlineCardVerifier: OnlineCardVerifier, return GetCardImageUseCase(cardArtworksProvider = cardArtworksProvider)
cardArtworksProvider: CardArtworksProvider,
cardSdkFeatureToggles: CardSdkFeatureToggles,
): GetCardImageUseCase {
return GetCardImageUseCase(
verifier = onlineCardVerifier,
cardArtworksProvider = cardArtworksProvider,
cardSdkFeatureToggles = cardSdkFeatureToggles,
)
} }
@Provides @Provides
@Singleton @Singleton
fun providesIsWalletNFTEnabledSyncUseCase( fun providesIsWalletNFTEnabledSyncUseCase(walletsRepository: WalletsRepository): IsWalletNFTEnabledSyncUseCase {
walletsRepository: WalletsRepository, return IsWalletNFTEnabledSyncUseCase(walletsRepository = walletsRepository)
nftFeatureToggles: NFTFeatureToggles,
): IsWalletNFTEnabledSyncUseCase {
return IsWalletNFTEnabledSyncUseCase(
walletsRepository = walletsRepository,
nftFeatureToggles = nftFeatureToggles,
)
} }
@Provides @Provides

View file

@ -0,0 +1,24 @@
package com.tangem.tap.di.hot
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.tap.domain.hot.HotWalletPasswordRequester
import com.tangem.tap.features.hot.DefaultHotWalletPasswordRequester
import com.tangem.tap.features.hot.TangemHotSDKProxy
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface TangemHotSdkModule {
@Binds
@Singleton
fun bindTangemHotSdk(proxy: TangemHotSDKProxy): TangemHotSdk
@Binds
@Singleton
fun bindHotWalletPasswordRequester(impl: DefaultHotWalletPasswordRequester): HotWalletPasswordRequester
}

View file

@ -1,9 +1,7 @@
package com.tangem.tap.di.routing package com.tangem.tap.di.routing
import com.tangem.common.routing.AppRouter import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.analytics.api.AnalyticsExceptionHandler 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.ProxyAppRouter
import com.tangem.tap.routing.configurator.AppRouterConfig import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.tap.routing.configurator.MutableAppRouterConfig import com.tangem.tap.routing.configurator.MutableAppRouterConfig
@ -33,10 +31,4 @@ internal object AppRouterModule {
@Provides @Provides
@Singleton @Singleton
fun provideAppRouterConfigurator(): AppRouterConfig = MutableAppRouterConfig() fun provideAppRouterConfigurator(): AppRouterConfig = MutableAppRouterConfig()
@Provides
@Singleton
fun provideRoutingFeatureToggle(featureTogglesManager: FeatureTogglesManager): RoutingFeatureToggle {
return RoutingFeatureToggle(featureTogglesManager)
}
} }

View file

@ -4,8 +4,7 @@ import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.Wallet
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.store import com.tangem.tap.store
@ -35,14 +34,15 @@ class TapWalletManager(
} }
private suspend fun loadUserWalletData(userWallet: UserWallet) { private suspend fun loadUserWalletData(userWallet: UserWallet) {
Analytics.setContext(userWallet.requireColdWallet().scanResponse) // [REDACTED_TASK_KEY] Analytics.setContext(userWallet)
val scanResponse = userWallet.scanResponse
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) if (userWallet is UserWallet.Cold) {
val scanResponse = userWallet.scanResponse
withMainContext { tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
// Order is important withMainContext {
store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) // Order is important
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
}
} }
} }
} }

View file

@ -17,9 +17,9 @@ import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.sdk.api.TangemSdkManager import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter
@ -50,7 +50,7 @@ internal class DefaultDerivationsRepository(
networkFactory.create( networkFactory.create(
blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null,
extraDerivationPath = null, extraDerivationPath = null,
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] userWallet = userWallet,
) )
}, },
) )
@ -61,7 +61,11 @@ internal class DefaultDerivationsRepository(
userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
} }
userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY] if (userWallet is UserWallet.Hot) {
return
}
userWallet.requireColdWallet()
if (!userWallet.scanResponse.card.settings.isHDWalletAllowed) { if (!userWallet.scanResponse.card.settings.isHDWalletAllowed) {
Timber.d("Nothing to derive") Timber.d("Nothing to derive")
@ -84,14 +88,18 @@ internal class DefaultDerivationsRepository(
): Boolean { ): Boolean {
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
if (userWallet is UserWallet.Hot) {
return false
}
val derivations = val derivations =
MissedDerivationsFinder(scanResponse = userWallet.requireColdWallet().scanResponse) // TODO [REDACTED_TASK_KEY] MissedDerivationsFinder(scanResponse = userWallet.requireColdWallet().scanResponse)
.findByNetworks( .findByNetworks(
networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) -> networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) ->
networkFactory.create( networkFactory.create(
blockchain = Blockchain.fromNetworkId(backendId) ?: return@mapNotNull null, blockchain = Blockchain.fromNetworkId(backendId) ?: return@mapNotNull null,
extraDerivationPath = extraDerivationPath, extraDerivationPath = extraDerivationPath,
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY] userWallet = userWallet,
) )
}, },
) )

View file

@ -13,7 +13,7 @@ import com.tangem.common.core.UserCodeRequestPolicy
import com.tangem.domain.card.ResetCardUseCase import com.tangem.domain.card.ResetCardUseCase
import com.tangem.domain.card.ResetCardUserCodeParams import com.tangem.domain.card.ResetCardUserCodeParams
import com.tangem.domain.card.models.ResetCardError 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 import com.tangem.sdk.api.TangemSdkManager
internal class DefaultResetCardUseCase( internal class DefaultResetCardUseCase(

View file

@ -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<CardVerifyAndGetInfo.Response.Item>? = 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()
}
}

View file

@ -0,0 +1,113 @@
package com.tangem.tap.domain.hot
import com.tangem.common.core.TangemSdkError
import com.tangem.features.hotwallet.HotWalletPasswordRequester
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.hot.sdk.exception.WrongPasswordException
import com.tangem.hot.sdk.model.*
import javax.inject.Inject
class HotWalletAccessor @Inject constructor(
private val tangemHotSdk: TangemHotSdk,
private val hotWalletPasswordRequester: HotWalletPasswordRequester,
) {
suspend fun signHashes(hotWalletId: HotWalletId, dataToSign: List<DataToSign>): List<SignedData> {
val auth = when (hotWalletId.authType) {
HotWalletId.AuthType.NoPassword -> HotAuth.NoAuth
HotWalletId.AuthType.Password -> requestPassword(false)
HotWalletId.AuthType.Biometry -> HotAuth.Biometry
}
return runCatchingSdkErrors(hotWalletId, auth) {
tangemHotSdk.signHashes(
unlockHotWallet = UnlockHotWallet(
walletId = hotWalletId,
auth = it,
),
dataToSign = dataToSign,
).also {
hotWalletPasswordRequester.dismiss()
}
}
}
private suspend fun <T> runCatchingSdkErrors(
hotWalletId: HotWalletId,
auth: HotAuth,
block: suspend (auth: HotAuth) -> T,
): T {
return runCatchingWrongPassInternal(
originalAuth = auth,
auth = auth,
block = { blockAuth ->
block(blockAuth).also {
// TODO [REDACTED_TASK_KEY] if user has biometry enabled, we set it as the new auth method
if (blockAuth is HotAuth.Password /*&& has biometry enabled */) {
tangemHotSdk.changeAuth(
unlockHotWallet = UnlockHotWallet(
walletId = hotWalletId,
auth = blockAuth,
),
auth = HotAuth.Biometry,
)
}
}
},
)
}
private suspend fun <T> runCatchingWrongPassInternal(
originalAuth: HotAuth,
auth: HotAuth,
block: suspend (auth: HotAuth) -> T,
): T = runCatching {
block(auth)
}.getOrElse { exception ->
if (auth is HotAuth.Biometry && exception.isBiometryError()) {
// fallback to password if biometry fails
val passAuth = requestPassword(true)
return@getOrElse runCatchingWrongPassInternal(
originalAuth = originalAuth,
auth = passAuth,
block = block,
)
}
if (exception !is WrongPasswordException) {
throw exception
}
// If the exception is a wrong password, we need to request the password again
hotWalletPasswordRequester.wrongPassword()
val passResult = requestPassword(originalAuth is HotAuth.Biometry)
runCatchingWrongPassInternal(
originalAuth = originalAuth,
auth = passResult,
block = block,
)
}
private suspend fun requestPassword(hasBiometry: Boolean): HotAuth {
return hotWalletPasswordRequester.requestPassword(hasBiometry).toAuth() ?: throw TangemSdkError.UserCancelled()
}
private fun Throwable.isBiometryError(): Boolean {
return this is TangemSdkError.AuthenticationFailed ||
this is TangemSdkError.AuthenticationCanceled ||
this is TangemSdkError.AuthenticationLockout ||
this is TangemSdkError.AuthenticationUnavailable ||
this is TangemSdkError.AuthenticationAlreadyInProgress ||
this is TangemSdkError.AuthenticationNotInitialized ||
this is TangemSdkError.AuthenticationPermanentLockout
}
private fun HotWalletPasswordRequester.Result.toAuth() = when (this) {
HotWalletPasswordRequester.Result.UseBiometry -> HotAuth.Biometry
HotWalletPasswordRequester.Result.Dismiss -> null
is HotWalletPasswordRequester.Result.EnteredPassword -> this.password
}
}

View file

@ -0,0 +1,9 @@
package com.tangem.tap.domain.hot
import com.tangem.hot.sdk.model.HotAuth
import com.tangem.hot.sdk.model.HotWalletId
interface HotWalletPasswordRequester {
suspend fun requestPassword(hotWalletId: HotWalletId): HotAuth.Password
}

View file

@ -0,0 +1,78 @@
package com.tangem.tap.domain.hot
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.map
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.hot.sdk.model.DataToSign
import com.tangem.operations.sign.SignData
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
class TangemHotSigner @AssistedInject constructor(
@Assisted private val userWallet: UserWallet.Hot,
private val hotWalletAccessor: HotWalletAccessor,
) : TransactionSigner {
override suspend fun sign(hash: ByteArray, publicKey: Wallet.PublicKey): CompletionResult<ByteArray> {
return sign(listOf(hash), publicKey).map { it.first() }
}
override suspend fun sign(
hashes: List<ByteArray>,
publicKey: Wallet.PublicKey,
): CompletionResult<List<ByteArray>> {
val wallet = userWallet.wallets.orEmpty().firstOrNull { it.publicKey == publicKey.seedKey }
?: return CompletionResult.Failure(
TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")),
)
val result = hotWalletAccessor.signHashes(
hotWalletId = userWallet.hotWalletId,
dataToSign = listOf(
DataToSign(
curve = wallet.curve,
hashes = hashes,
derivationPath = publicKey.derivationPath,
),
),
)
return CompletionResult.Success(result.map { it.signatures }.flatten())
}
override suspend fun multiSign(
dataToSign: List<SignData>,
publicKey: Wallet.PublicKey,
): CompletionResult<Map<ByteArray, ByteArray>> {
val result = hotWalletAccessor.signHashes(
hotWalletId = userWallet.hotWalletId,
dataToSign = dataToSign.map { signData ->
val wallet = userWallet.wallets.orEmpty().firstOrNull { it.publicKey == signData.publicKey }
?: return CompletionResult.Failure(
TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")),
)
DataToSign(
curve = wallet.curve,
hashes = listOf(signData.hash),
derivationPath = signData.derivationPath,
)
},
)
return CompletionResult.Success(
result.mapIndexed { index, data ->
dataToSign[index].publicKey to data.signatures.first()
}.toMap(),
)
}
@AssistedFactory
interface Factory {
fun create(@Assisted userWallet: UserWallet.Hot): TangemHotSigner
}
}

View file

@ -0,0 +1,98 @@
package com.tangem.tap.domain.hot
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.map
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.hot.sdk.model.DataToSign
import com.tangem.operations.sign.SignData
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import timber.log.Timber
class TangemHotWalletSigner @AssistedInject constructor(
@Assisted private val userWallet: UserWallet.Hot,
private val hotWalletAccessor: HotWalletAccessor,
) : TransactionSigner {
override suspend fun sign(hash: ByteArray, publicKey: Wallet.PublicKey): CompletionResult<ByteArray> {
return sign(listOf(hash), publicKey).map { it.first() }
}
override suspend fun sign(
hashes: List<ByteArray>,
publicKey: Wallet.PublicKey,
): CompletionResult<List<ByteArray>> {
val wallet = userWallet.wallets.orEmpty().firstOrNull { it.publicKey.contentEquals(publicKey.seedKey) }
?: return CompletionResult.Failure(
TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")),
)
val result = runCatching {
hotWalletAccessor.signHashes(
hotWalletId = userWallet.hotWalletId,
dataToSign = listOf(
DataToSign(
curve = wallet.curve,
hashes = hashes,
derivationPath = publicKey.derivationPath,
),
),
)
}.getOrElse {
Timber.e(it)
return if (it is TangemSdkError) {
CompletionResult.Failure(it)
} else {
CompletionResult.Failure(TangemSdkError.ExceptionError(it))
}
}
return CompletionResult.Success(result.map { it.signatures }.flatten())
}
override suspend fun multiSign(
dataToSign: List<SignData>,
publicKey: Wallet.PublicKey,
): CompletionResult<Map<ByteArray, ByteArray>> {
val result = runCatching {
hotWalletAccessor.signHashes(
hotWalletId = userWallet.hotWalletId,
dataToSign = dataToSign.map { signData ->
val wallet =
userWallet.wallets.orEmpty().firstOrNull { it.publicKey.contentEquals(signData.publicKey) }
?: return CompletionResult.Failure(
TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")),
)
DataToSign(
curve = wallet.curve,
hashes = listOf(signData.hash),
derivationPath = signData.derivationPath,
)
},
)
}.getOrElse {
Timber.e(it)
return if (it is TangemSdkError) {
CompletionResult.Failure(it)
} else {
CompletionResult.Failure(TangemSdkError.ExceptionError(it))
}
}
return CompletionResult.Success(
result.mapIndexed { index, data ->
dataToSign[index].publicKey to data.signatures.first()
}.toMap(),
)
}
@AssistedFactory
interface Factory {
fun create(@Assisted userWallet: UserWallet.Hot): TangemHotWalletSigner
}
}

View file

@ -23,10 +23,10 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse 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.VisaActivationInput
import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.operations.ScanTask import com.tangem.operations.ScanTask
import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.derivation.DerivationTaskResponse

View file

@ -17,10 +17,10 @@ import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse 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.VisaActivationInput
import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.preflightread.PreflightReadFilter import com.tangem.operations.preflightread.PreflightReadFilter
import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletResponse

View file

@ -177,6 +177,13 @@ object WalletMockContent : MockContent {
parentFingerprint = byteArrayOf(0, 0, 0, 0), parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 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 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), 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), 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),

View file

@ -4,8 +4,8 @@ import com.tangem.common.card.Card
import com.tangem.common.core.SessionEnvironment import com.tangem.common.core.SessionEnvironment
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.domain.models.scan.CardDTO 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.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.preflightread.PreflightReadFilter import com.tangem.operations.preflightread.PreflightReadFilter
/** /**

View file

@ -9,8 +9,8 @@ import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.domain.common.DerivationStyleProvider import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.models.scan.CardDTO 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.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.demo.DemoHelper
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext

View file

@ -4,7 +4,7 @@ import com.tangem.common.CompletionResult
import com.tangem.common.core.CardSession import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.CompletionCallback 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.PreflightReadMode
import com.tangem.operations.PreflightReadTask import com.tangem.operations.PreflightReadTask
import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter

View file

@ -7,9 +7,6 @@ internal class DefaultTokensFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager, private val featureTogglesManager: FeatureTogglesManager,
) : TokensFeatureToggles { ) : TokensFeatureToggles {
override val isStakingLoadingRefactoringEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_LOADING_REFACTORING_ENABLED")
override val isWalletBalanceFetcherEnabled: Boolean override val isWalletBalanceFetcherEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "WALLET_BALANCE_FETCHER_ENABLED") get() = featureTogglesManager.isFeatureEnabled(name = "WALLET_BALANCE_FETCHER_ENABLED")
} }

View file

@ -1,13 +1,12 @@
package com.tangem.tap.domain.userWalletList.implementation package com.tangem.tap.domain.userWalletList.implementation
import com.tangem.common.* 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.UserWalletsListError
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType 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.model.UserWalletEncryptionKey
import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository
@ -213,8 +212,7 @@ internal class BiometricUserWalletsListManager(
changeSelectedUserWallet: Boolean, changeSelectedUserWallet: Boolean,
canOverridePublicInfo: Boolean, canOverridePublicInfo: Boolean,
): CompletionResult<Unit> { ): CompletionResult<Unit> {
userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY] val encryptionKey = userWallet.encryptionKey
val encryptionKey = userWallet.scanResponse.card.encryptionKey
?.let { UserWalletEncryptionKey(userWallet.walletId, it) } ?.let { UserWalletEncryptionKey(userWallet.walletId, it) }
?: return CompletionResult.Success(Unit) // No encryption key, no need to save ?: return CompletionResult.Success(Unit) // No encryption key, no need to save

View file

@ -4,9 +4,9 @@ import com.tangem.common.CompletionResult
import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.get 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.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi

View file

@ -2,10 +2,10 @@ package com.tangem.tap.domain.userWalletList.implementation
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.common.catching 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.UserWalletsListError
import com.tangem.domain.wallets.legacy.UserWalletsListManager 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.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.userWalletList.model package com.tangem.tap.domain.userWalletList.model
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class UserWalletEncryptionKey( internal data class UserWalletEncryptionKey(

View file

@ -2,31 +2,44 @@ package com.tangem.tap.domain.userWalletList.model
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import com.tangem.domain.models.MobileWallet
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse 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.visa.model.VisaCardActivationStatus
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.hot.sdk.model.HotWalletId
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class UserWalletSensitiveInformation( internal data class UserWalletSensitiveInformation(
// Cold
@Json(name = "wallets") @Json(name = "wallets")
val wallets: List<CardDTO.Wallet>, val wallets: List<CardDTO.Wallet>?,
@Json(name = "visaCardActivationStatus") @Json(name = "visaCardActivationStatus")
val visaCardActivationStatus: VisaCardActivationStatus? = null, val visaCardActivationStatus: VisaCardActivationStatus? = null,
// Hot
@Json(name = "mobileWallets")
val mobileWallets: List<MobileWallet>? = null,
) )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class UserWalletPublicInformation( internal data class UserWalletPublicInformation(
// Common
@Json(name = "name") @Json(name = "name")
val name: String, val name: String,
@Json(name = "walletId") @Json(name = "walletId")
val walletId: UserWalletId, val walletId: UserWalletId,
// Cold
@Json(name = "cardsInWallet") @Json(name = "cardsInWallet")
val cardsInWallet: Set<String>, val cardsInWallet: Set<String>,
@Json(name = "scanResponse") @Json(name = "scanResponse")
val scanResponse: ScanResponse, val scanResponse: ScanResponse?,
@Json(name = "isMultiCurrency") @Json(name = "isMultiCurrency")
val isMultiCurrency: Boolean, val isMultiCurrency: Boolean,
@Json(name = "hasBackupError") @Json(name = "hasBackupError")
val hasBackupError: Boolean = false, val hasBackupError: Boolean = false,
// Hot
@Json(name = "hotWalletId")
val hotWalletId: HotWalletId? = null,
@Json(name = "backedUp")
val backedUp: Boolean? = null,
) )

View file

@ -1,6 +1,6 @@
package com.tangem.tap.domain.userWalletList.repository package com.tangem.tap.domain.userWalletList.repository
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
internal interface SelectedUserWalletRepository { internal interface SelectedUserWalletRepository {
suspend fun get(): UserWalletId? suspend fun get(): UserWalletId?

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.userWalletList.repository package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.CompletionResult 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 import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
internal interface UserWalletsKeysRepository { internal interface UserWalletsKeysRepository {

View file

@ -1,8 +1,8 @@
package com.tangem.tap.domain.userWalletList.repository package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
internal interface UserWalletsPublicInformationRepository { internal interface UserWalletsPublicInformationRepository {

View file

@ -1,8 +1,8 @@
package com.tangem.tap.domain.userWalletList.repository package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation

View file

@ -10,8 +10,8 @@ import com.tangem.common.services.secure.SecureStorage
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic 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.legacy.UserWalletsListError
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.userWalletList.repository.implementation package com.tangem.tap.domain.userWalletList.repository.implementation
import com.tangem.common.services.secure.SecureStorage 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.tap.domain.userWalletList.repository.SelectedUserWalletRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext

View file

@ -7,8 +7,8 @@ import com.tangem.common.CompletionResult
import com.tangem.common.catching import com.tangem.common.catching
import com.tangem.common.flatMap import com.tangem.common.flatMap
import com.tangem.common.services.secure.SecureStorage import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository
import com.tangem.tap.domain.userWalletList.utils.publicInformation import com.tangem.tap.domain.userWalletList.utils.publicInformation

View file

@ -7,8 +7,8 @@ import com.tangem.common.CompletionResult
import com.tangem.common.catching import com.tangem.common.catching
import com.tangem.common.services.secure.SecureStorage import com.tangem.common.services.secure.SecureStorage
import com.tangem.crypto.operations.AESCipherOperations import com.tangem.crypto.operations.AESCipherOperations
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation

View file

@ -1,7 +1,8 @@
package com.tangem.tap.domain.userWalletList.utils package com.tangem.tap.domain.userWalletList.utils
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.models.UserWalletId 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.UserWalletPublicInformation
import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation
@ -10,8 +11,12 @@ internal val UserWallet.sensitiveInformation: UserWalletSensitiveInformation
is UserWallet.Cold -> UserWalletSensitiveInformation( is UserWallet.Cold -> UserWalletSensitiveInformation(
wallets = scanResponse.card.wallets, wallets = scanResponse.card.wallets,
visaCardActivationStatus = scanResponse.visaCardActivationStatus, 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 internal val UserWallet.publicInformation: UserWalletPublicInformation
@ -28,19 +33,39 @@ internal val UserWallet.publicInformation: UserWalletPublicInformation
visaCardActivationStatus = null, visaCardActivationStatus = null,
), ),
hasBackupError = hasBackupError, 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 { internal fun UserWalletPublicInformation.toUserWallet(): UserWallet {
return UserWallet.Cold( return if (hotWalletId != null) {
name = name, UserWallet.Hot(
walletId = walletId, name = name,
cardsInWallet = cardsInWallet, walletId = walletId,
scanResponse = scanResponse, hotWalletId = hotWalletId,
isMultiCurrency = isMultiCurrency, wallets = null,
hasBackupError = hasBackupError, backedUp = backedUp!!,
) )
} else {
UserWallet.Cold(
name = name,
walletId = walletId,
cardsInWallet = cardsInWallet,
scanResponse = scanResponse!!,
isMultiCurrency = isMultiCurrency,
hasBackupError = hasBackupError,
)
}
} }
internal fun List<UserWalletPublicInformation>.toUserWallets(): List<UserWallet> { internal fun List<UserWalletPublicInformation>.toUserWallets(): List<UserWallet> {
@ -53,13 +78,15 @@ internal fun UserWallet.updateWith(sensitiveInformation: UserWalletSensitiveInfo
copy( copy(
scanResponse = scanResponse.copy( scanResponse = scanResponse.copy(
card = scanResponse.card.copy( card = scanResponse.card.copy(
wallets = sensitiveInformation.wallets, wallets = sensitiveInformation.wallets!!,
), ),
visaCardActivationStatus = sensitiveInformation.visaCardActivationStatus, 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)
} }

View file

@ -1,11 +1,16 @@
package com.tangem.tap.domain.userWalletList.utils package com.tangem.tap.domain.userWalletList.utils
import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.calculateSha256
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.common.extensions.calculateHmacSha256 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? internal val UserWallet.encryptionKey: ByteArray?
get() = findPublicKey(wallets)?.let { calculateEncryptionKey(it) } 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 { private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray {
val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray() val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray()
@ -15,8 +20,12 @@ private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray {
} }
private fun findPublicKey(wallets: List<CardDTO.Wallet>): ByteArray? { private fun findPublicKey(wallets: List<CardDTO.Wallet>): ByteArray? {
return wallets.firstOrNull() return wallets.firstOrNull()?.publicKey
?.publicKey }
@JvmName("findPublicKeyInMobileWallets")
private fun findPublicKey(wallets: List<MobileWallet>): ByteArray? {
return wallets.firstOrNull()?.publicKey
} }
private const val MESSAGE_FOR_ENCRYPTION_KEY = "UserWalletEncryptionKey" private const val MESSAGE_FOR_ENCRYPTION_KEY = "UserWalletEncryptionKey"

View file

@ -17,7 +17,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType
import com.tangem.data.walletconnect.network.ethereum.LegacySdkHelper 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.operations.sign.SignHashCommand
import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.safeUpdate

View file

@ -8,12 +8,12 @@ import com.reown.android.relay.ConnectionType
import com.reown.walletkit.client.Wallet import com.reown.walletkit.client.Wallet
import com.reown.walletkit.client.WalletKit import com.reown.walletkit.client.WalletKit
import com.tangem.core.analytics.api.AnalyticsEventHandler 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.WcPairService
import com.tangem.domain.walletconnect.model.WcPairRequest import com.tangem.domain.walletconnect.model.WcPairRequest
import com.tangem.domain.walletconnect.model.legacy.Account import com.tangem.domain.walletconnect.model.legacy.Account
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase 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.features.walletconnect.components.WalletConnectFeatureToggles
import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
@ -216,7 +216,7 @@ internal class DefaultLegacyWalletConnectRepository(
Timber.i("sessionProposal: $sessionProposal") Timber.i("sessionProposal: $sessionProposal")
this@DefaultLegacyWalletConnectRepository.sessionProposal = sessionProposal this@DefaultLegacyWalletConnectRepository.sessionProposal = sessionProposal
if (sessionProposal.name in unsupportedDApps) { if (sessionProposal.name in UnsupportedDApps.list) {
Timber.i("Unsupported DApp") Timber.i("Unsupported DApp")
scope.launch { scope.launch {
_events.emit( _events.emit(

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.walletconnect2.domain 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.walletconnect.model.legacy.Account
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.walletconnect2.domain.models.* import com.tangem.tap.domain.walletconnect2.domain.models.*
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow

View file

@ -5,16 +5,15 @@ import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network 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.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletconnect.model.legacy.Account import com.tangem.domain.walletconnect.model.legacy.Account
import com.tangem.domain.walletconnect.model.legacy.Session import com.tangem.domain.walletconnect.model.legacy.Session
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager 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.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOnMain
@ -422,8 +421,7 @@ class WalletConnectInteractor(
} }
private fun getCardId(userWallet: UserWallet): String? { private fun getCardId(userWallet: UserWallet): String? {
userWallet.requireColdWallet() // [REDACTED_TASK_KEY] return if (userWallet is UserWallet.Cold && userWallet.scanResponse.card.backupStatus?.isActive != true) {
return if (userWallet.scanResponse.card.backupStatus?.isActive != true) {
userWallet.cardId userWallet.cardId
} else { // if wallet has backup, any card from wallet can be used to sign } else { // if wallet has backup, any card from wallet can be used to sign
null null

View file

@ -43,9 +43,6 @@ object DemoHelper {
} }
private fun getScanResponse(appState: () -> AppState?): ScanResponse? { private fun getScanResponse(appState: () -> AppState?): ScanResponse? {
val state = appState() ?: return null return appState()?.globalState?.scanResponse
return state.globalState.onboardingState.onboardingManager?.scanResponse
?: state.globalState.scanResponse
} }
} }

View file

@ -9,7 +9,7 @@ import org.rekotlin.Action
sealed class DetailsAction : Action { sealed class DetailsAction : Action {
data class PrepareScreen( data class PrepareScreen(
val scanResponse: ScanResponse, val scanResponse: ScanResponse?,
val initializedAppSettingsState: AppSettingsState, val initializedAppSettingsState: AppSettingsState,
) : DetailsAction() ) : DetailsAction()

View file

@ -1,6 +1,6 @@
package com.tangem.tap.features.details.redux.walletconnect 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.WcPreparedRequest
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.cardsettings.api
import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
interface CardSettingsComponent : ComposableContentComponent { interface CardSettingsComponent : ComposableContentComponent {

View file

@ -17,9 +17,9 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.getBackupCardsCount import com.tangem.domain.common.util.getBackupCardsCount
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse 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.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.sdk.api.TangemSdkManager import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam 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.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchNavigationAction import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.AppDialog 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.CardInfo
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsScreenState import com.tangem.tap.features.details.ui.cardsettings.CardSettingsScreenState
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
@ -171,6 +170,8 @@ internal class CardSettingsModel @Inject constructor(
} }
} }
private fun CardDTO.signedHashesCount(): Int = wallets.sumOf { it.totalSignedHashes ?: 0 }
private fun handleClickingItem(item: CardInfo) { private fun handleClickingItem(item: CardInfo) {
when (item) { when (item) {
is CardInfo.ChangeAccessCode -> { is CardInfo.ChangeAccessCode -> {

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.resetcard.api
import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
interface ResetCardComponent : ComposableContentComponent { interface ResetCardComponent : ComposableContentComponent {

View file

@ -12,9 +12,9 @@ import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
import com.tangem.domain.card.ResetCardUseCase import com.tangem.domain.card.ResetCardUseCase
import com.tangem.domain.card.ResetCardUserCodeParams import com.tangem.domain.card.ResetCardUserCodeParams
import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable 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.DeleteWalletUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.securitymode.api
import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
interface SecurityModeComponent : ComposableContentComponent { interface SecurityModeComponent : ComposableContentComponent {

View file

@ -5,6 +5,7 @@ import arrow.core.getOrElse
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer 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.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
@ -31,12 +32,18 @@ internal class WalletConnectModel @Inject constructor(
init { init {
modelScope.launch { modelScope.launch {
listenToQrScanningUseCase(SourceType.WALLET_CONNECT) listenToQrScanningUseCase.listen(SourceType.WALLET_CONNECT)
.getOrElse { emptyFlow() } .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( WalletConnectAction.OpenSession(
wcUri = it, wcUri = result.qrCode,
source = WalletConnectAction.OpenSession.SourceType.QR, source = source,
userWalletId = params.userWalletId, userWalletId = params.userWalletId,
) )
} }

View file

@ -2,7 +2,7 @@ package com.tangem.tap.features.details.ui.walletconnect.api
import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
interface WalletConnectComponent : ComposableContentComponent { interface WalletConnectComponent : ComposableContentComponent {
data class Params(val userWalletId: UserWalletId) data class Params(val userWalletId: UserWalletId)

View file

@ -13,6 +13,7 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.SystemBarsIconsDisposable import com.tangem.core.ui.components.SystemBarsIconsDisposable
import com.tangem.core.ui.utils.ChangeRootBackgroundColorEffect import com.tangem.core.ui.utils.ChangeRootBackgroundColorEffect
import com.tangem.core.ui.utils.findActivity import com.tangem.core.ui.utils.findActivity
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.home.api.HomeComponent import com.tangem.tap.features.home.api.HomeComponent
import com.tangem.tap.features.home.compose.StoriesScreen import com.tangem.tap.features.home.compose.StoriesScreen
@ -29,6 +30,7 @@ import org.rekotlin.StoreSubscriber
internal class DefaultHomeComponent @AssistedInject constructor( internal class DefaultHomeComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext, @Assisted appComponentContext: AppComponentContext,
@Assisted params: Unit, @Assisted params: Unit,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) : HomeComponent, AppComponentContext by appComponentContext, StoreSubscriber<HomeState> { ) : HomeComponent, AppComponentContext by appComponentContext, StoreSubscriber<HomeState> {
private val model: HomeModel = getOrCreateModel() private val model: HomeModel = getOrCreateModel()
@ -58,7 +60,7 @@ internal class DefaultHomeComponent @AssistedInject constructor(
val activity = LocalContext.current.findActivity() val activity = LocalContext.current.findActivity()
BackHandler(onBack = activity::finish) BackHandler(onBack = activity::finish)
SystemBarsIconsDisposable(darkIcons = false) SystemBarsIconsDisposable(darkIcons = false)
if (homeState.value.isV2StoriesEnabled) { if (hotWalletFeatureToggles.isHotWalletEnabled) {
StoriesScreenV2( StoriesScreenV2(
homeState = homeState, homeState = homeState,
onCreateNewWalletButtonClick = model::onCreateNewWalletScreen, onCreateNewWalletButtonClick = model::onCreateNewWalletScreen,

View file

@ -22,7 +22,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags import com.tangem.core.ui.test.StoriesScreenTestTags
import com.tangem.tap.features.home.compose.content.* import com.tangem.tap.features.home.compose.content.*
import com.tangem.tap.features.home.compose.views.HomeButtons import com.tangem.tap.features.home.compose.views.HomeButtons
import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton
@ -61,7 +61,7 @@ internal fun StoriesScreen(
StoriesScreenContent( StoriesScreenContent(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.testTag(TestTags.STORIES_SCREEN), .testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
config = StoriesScreenContentConfig( config = StoriesScreenContentConfig(
storiesSize = state.stories.lastIndex, storiesSize = state.stories.lastIndex,
currentStoryIndex = currentStoryIndex, currentStoryIndex = currentStoryIndex,

View file

@ -19,7 +19,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags import com.tangem.core.ui.test.StoriesScreenTestTags
import com.tangem.tap.features.home.compose.content.* import com.tangem.tap.features.home.compose.content.*
import com.tangem.tap.features.home.compose.views.HomeButtonsV2 import com.tangem.tap.features.home.compose.views.HomeButtonsV2
import com.tangem.tap.features.home.compose.views.StoriesProgressBar import com.tangem.tap.features.home.compose.views.StoriesProgressBar
@ -57,7 +57,7 @@ internal fun StoriesScreenV2(
StoriesScreenContentV2( StoriesScreenContentV2(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.testTag(TestTags.STORIES_SCREEN), .testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
config = StoriesScreenContentV2Config( config = StoriesScreenContentV2Config(
storiesSize = state.stories.lastIndex, storiesSize = state.stories.lastIndex,
currentStoryIndex = currentStoryIndex, currentStoryIndex = currentStoryIndex,

Some files were not shown because too many files have changed in this diff Show more