Updated on 2026-08-14
This commit is contained in:
commit
dbe235560b
428 changed files with 10165 additions and 3907 deletions
|
|
@ -280,6 +280,8 @@ dependencies {
|
|||
implementation(tangemDeps.card.android) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
implementation(tangemDeps.hot.core)
|
||||
implementation(tangemDeps.hot.android)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
@ -299,6 +301,7 @@ dependencies {
|
|||
implementation(deps.zxing.qrCore)
|
||||
implementation(deps.coil)
|
||||
implementation(deps.coil.gif)
|
||||
implementation(deps.coil.svg)
|
||||
implementation(deps.amplitude)
|
||||
implementation(deps.kotsonGson)
|
||||
implementation(deps.spongecastle.core)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
package com.tangem.common
|
||||
|
||||
import android.Manifest
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.kaspersky.components.alluresupport.interceptors.step.ScreenshotStepInterceptor
|
||||
|
|
@ -10,8 +11,8 @@ import com.kaspersky.components.composesupport.config.addComposeSupport
|
|||
import com.kaspersky.kaspresso.kaspresso.Kaspresso
|
||||
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
|
||||
import com.tangem.common.allure.FailedStepScreenshotInterceptor
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.common.rules.ApiEnvironmentRule
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.tap.MainActivity
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import org.junit.Rule
|
||||
|
|
@ -35,17 +36,20 @@ abstract class BaseTestCase : TestCase(
|
|||
) {
|
||||
|
||||
@Inject
|
||||
lateinit var tangemSdkManager: TangemSdkManager
|
||||
|
||||
@Inject
|
||||
lateinit var appPreferencesStore: AppPreferencesStore
|
||||
lateinit var apiConfigsManager: ApiConfigsManager
|
||||
|
||||
private val hiltRule = HiltAndroidRule(this)
|
||||
private val permissionRule = GrantPermissionRule.grant(
|
||||
private val apiEnvironmentRule = ApiEnvironmentRule()
|
||||
private val permissionRule = GrantPermissionRule.grant(
|
||||
Manifest.permission.POST_NOTIFICATIONS,
|
||||
Manifest.permission.CAMERA,
|
||||
)
|
||||
val composeTestRule = createAndroidComposeRule<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
|
||||
@JvmField
|
||||
|
|
@ -53,18 +57,26 @@ abstract class BaseTestCase : TestCase(
|
|||
.outerRule(hiltRule)
|
||||
.around(ApplicationInjectionExecutionRule())
|
||||
.around(permissionRule)
|
||||
.around(apiEnvironmentRule)
|
||||
.around(composeTestRule)
|
||||
|
||||
/**
|
||||
* Initialization order is important:
|
||||
* – DI dependencies must be injected first,
|
||||
* – then the API environment should be set up,
|
||||
* – and only after that the activity should be launched.
|
||||
*/
|
||||
protected fun setupHooks(
|
||||
additionalBeforeSection: () -> Unit = {},
|
||||
additionalAfterSection: () -> Unit = {},
|
||||
) = before {
|
||||
hiltRule.inject()
|
||||
apiEnvironmentRule.setup(apiConfigsManager)
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
Intents.init()
|
||||
additionalBeforeSection()
|
||||
}.after {
|
||||
additionalAfterSection()
|
||||
Intents.release()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,7 @@ import com.kaspersky.kaspresso.testcases.api.scenario.Scenario
|
|||
import com.kaspersky.kaspresso.testcases.core.testcontext.TestContext
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.screens.DisclaimerTestScreen
|
||||
import com.tangem.screens.MainTestScreen
|
||||
import com.tangem.screens.StoriesTestScreen
|
||||
import com.tangem.screens.TestTopBar
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
|
||||
|
|
@ -20,22 +17,22 @@ class OpenMainScreenScenario(
|
|||
if (productType != null) {
|
||||
MockProvider.setMocks(productType)
|
||||
}
|
||||
ComposeScreen.onComposeScreen<DisclaimerTestScreen>(testRule) {
|
||||
ComposeScreen.onComposeScreen<DisclaimerPageObject>(testRule) {
|
||||
step("Click on \"Accept\" button") {
|
||||
acceptButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<StoriesTestScreen>(testRule) {
|
||||
ComposeScreen.onComposeScreen<StoriesPageObject>(testRule) {
|
||||
step("Click on \"Scan\" button") {
|
||||
scanButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<MainTestScreen>(testRule) {
|
||||
ComposeScreen.onComposeScreen<MainScreenPageObject>(testRule) {
|
||||
step("Make sure wallet screen is visible") {
|
||||
assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<TestTopBar>(testRule) {
|
||||
ComposeScreen.onComposeScreen<TopBarPageObject>(testRule) {
|
||||
step("Close Markets tooltip"){
|
||||
performClick()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,27 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.core.ui.test.TestTags
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.DetailsScreenTestTags
|
||||
import com.tangem.wallet.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class DetailsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<DetailsTestScreen>(
|
||||
class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<DetailsPageObject>(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(TestTags.DETAILS_SCREEN) }
|
||||
viewBuilderAction = { hasTestTag(DetailsScreenTestTags.SCREEN_CONTAINER) }
|
||||
) {
|
||||
|
||||
val walletConnectButton: KNode = child {
|
||||
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||
hasText(getResourceString(R.string.wallet_connect_title))
|
||||
}
|
||||
|
||||
private val walletBlock: KNode = child {
|
||||
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||
}
|
||||
|
||||
val walletNameButton: KNode = walletBlock.child {
|
||||
|
|
@ -32,20 +34,23 @@ class DetailsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
}
|
||||
|
||||
val buyTangemButton: KNode = child {
|
||||
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||
hasText(getResourceString(R.string.details_buy_wallet))
|
||||
}
|
||||
|
||||
val appSettingsButton: KNode = child {
|
||||
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||
hasText(getResourceString(R.string.app_settings_title))
|
||||
}
|
||||
val contactSupportButton: KNode = child {
|
||||
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||
hasText(getResourceString(R.string.details_row_title_contact_to_support))
|
||||
}
|
||||
val toSButton: KNode = child {
|
||||
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||
hasText(getResourceString(R.string.disclaimer_title))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onDetailsScreen(function: DetailsPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
|
||||
class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<MainScreenPageObject>(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(MainScreenTestTags.SCREEN_CONTAINER) }
|
||||
)
|
||||
|
||||
internal fun BaseTestCase.onMainScreen(function: MainScreenPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -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) }
|
||||
)
|
||||
|
|
@ -1,25 +1,27 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.core.ui.test.TestTags
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.wallet.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.views.KView
|
||||
import io.github.kakaocup.kakao.text.KButton
|
||||
|
||||
class StoriesTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<StoriesTestScreen>(
|
||||
class StoriesPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<StoriesPageObject>(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(TestTags.STORIES_SCREEN) }
|
||||
viewBuilderAction = { hasTestTag(StoriesScreenTestTags.SCREEN_CONTAINER) }
|
||||
) {
|
||||
|
||||
val scanButton: KNode = child {
|
||||
hasTestTag(TestTags.STORIES_SCREEN_SCAN_BUTTON)
|
||||
hasTestTag(StoriesScreenTestTags.SCAN_BUTTON)
|
||||
}
|
||||
|
||||
val orderButton: KNode = child {
|
||||
hasTestTag(TestTags.STORIES_SCREEN_ORDER_BUTTON)
|
||||
hasTestTag(StoriesScreenTestTags.ORDER_BUTTON)
|
||||
}
|
||||
|
||||
val enableNFCAlert: KView = KView {
|
||||
|
|
@ -29,4 +31,7 @@ class StoriesTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
val cancelButton: KButton = KButton {
|
||||
withId(android.R.id.button2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onStoriesScreen(function: StoriesPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -1,19 +1,21 @@
|
|||
package com.tangem.screens
|
||||
|
||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.core.ui.test.TestTags
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.WalletSettingsScreenTestTags
|
||||
import com.tangem.wallet.R
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class WalletSettingsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<WalletSettingsTestScreen>(
|
||||
class WalletSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<WalletSettingsPageObject>(
|
||||
semanticsProvider = semanticsProvider,
|
||||
viewBuilderAction = { hasTestTag(TestTags.WALLET_SETTINGS_SCREEN) }
|
||||
viewBuilderAction = { hasTestTag(WalletSettingsScreenTestTags.SCREEN_CONTAINER) }
|
||||
) {
|
||||
private val walletSettingsItem: KNode = child {
|
||||
hasTestTag(TestTags.WALLET_SETTINGS_SCREEN_ITEM)
|
||||
hasTestTag(WalletSettingsScreenTestTags.SCREEN_ITEM)
|
||||
}
|
||||
|
||||
val linkMoreCardsButton: KNode = walletSettingsItem.child {
|
||||
|
|
@ -28,4 +30,7 @@ class WalletSettingsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvi
|
|||
val forgetWalletButton: KNode = walletSettingsItem.child {
|
||||
hasText(getResourceString(R.string.settings_forget_wallet))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onWalletSettingsScreen(function: WalletSettingsPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -4,152 +4,151 @@ import com.tangem.common.BaseTestCase
|
|||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.screens.DetailsTestScreen
|
||||
import com.tangem.screens.TestTopBar
|
||||
import com.tangem.screens.WalletSettingsTestScreen
|
||||
import com.tangem.screens.onDetailsScreen
|
||||
import com.tangem.screens.onTopBar
|
||||
import com.tangem.screens.onWalletSettingsScreen
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class DetailsScreenTest : BaseTestCase() {
|
||||
class DetailsTest : BaseTestCase() {
|
||||
|
||||
@Test
|
||||
fun walletWithoutBackupDetails() =
|
||||
fun walletWithoutBackupDetailsTest() =
|
||||
setupHooks().run {
|
||||
scenario(OpenMainScreenScenario(composeTestRule))
|
||||
ComposeScreen.onComposeScreen<TestTopBar>(composeTestRule) {
|
||||
onTopBar {
|
||||
step("Open wallet details") {
|
||||
moreButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<DetailsTestScreen>(composeTestRule) {
|
||||
step("Assert wallet connect button is visible") {
|
||||
onDetailsScreen {
|
||||
step("Assert 'Wallet connect' button is visible") {
|
||||
walletConnectButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert scan card button is visible") {
|
||||
step("Assert 'Scan card' button is visible") {
|
||||
scanCardButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert buy Tangem card button is visible") {
|
||||
step("Assert 'Buy Tangem card' button is visible") {
|
||||
buyTangemButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert app settings button is visible") {
|
||||
step("Assert 'App settings' button is visible") {
|
||||
appSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert contact support button is visible") {
|
||||
step("Assert 'Contact support' button is visible") {
|
||||
contactSupportButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert terms or service button is visible") {
|
||||
step("Assert 'Terms of service' button is visible") {
|
||||
toSButton.assertIsDisplayed()
|
||||
}
|
||||
step("Open wallet settings screen") {
|
||||
step("Open 'Wallet settings' screen") {
|
||||
walletNameButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<WalletSettingsTestScreen>(composeTestRule) {
|
||||
step("Assert Link more cards button is visible") {
|
||||
onWalletSettingsScreen {
|
||||
step("Assert 'Link more cards' button is visible") {
|
||||
linkMoreCardsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert Card Settings button is visible") {
|
||||
step("Assert 'Card Settings' button is visible") {
|
||||
cardSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert Referral program button is visible") {
|
||||
step("Assert 'Referral program' button is visible") {
|
||||
referralProgramButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert Forget wallet button is visible") {
|
||||
step("Assert 'Forget wallet' button is visible") {
|
||||
forgetWalletButton.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wallet2Details() =
|
||||
fun wallet2DetailsTest() =
|
||||
setupHooks().run {
|
||||
scenario(OpenMainScreenScenario(composeTestRule, ProductType.Wallet2))
|
||||
ComposeScreen.onComposeScreen<TestTopBar>(composeTestRule) {
|
||||
onTopBar {
|
||||
step("Open wallet details") {
|
||||
moreButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<DetailsTestScreen>(composeTestRule) {
|
||||
step("Assert wallet connect button is visible") {
|
||||
onDetailsScreen {
|
||||
step("Assert 'Wallet connect' button is visible") {
|
||||
walletConnectButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert scan card button is visible") {
|
||||
step("Assert 'Scan card' button is visible") {
|
||||
scanCardButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert buy Tangem card button is visible") {
|
||||
step("Assert 'Buy Tangem card' button is visible") {
|
||||
buyTangemButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert app settings button is visible") {
|
||||
step("Assert 'App settings' button is visible") {
|
||||
appSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert contact support button is visible") {
|
||||
step("Assert 'Contact support' button is visible") {
|
||||
contactSupportButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert terms or service button is visible") {
|
||||
step("Assert 'Terms or service' button is visible") {
|
||||
toSButton.assertIsDisplayed()
|
||||
}
|
||||
step("Open wallet settings screen") {
|
||||
step("Open 'Wallet settings' screen") {
|
||||
walletNameButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<WalletSettingsTestScreen>(composeTestRule) {
|
||||
step("Assert Link more cards button does not exist") {
|
||||
onWalletSettingsScreen {
|
||||
step("Assert 'Link more cards' button does not exist") {
|
||||
linkMoreCardsButton.assertIsNotDisplayed()
|
||||
}
|
||||
step("Assert Card Settings button is visible") {
|
||||
step("Assert 'Card Settings' button is visible") {
|
||||
cardSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert Referral program button is visible") {
|
||||
step("Assert 'Referral program' button is visible") {
|
||||
referralProgramButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert Forget wallet button is visible") {
|
||||
step("Assert 'Forget wallet' button is visible") {
|
||||
forgetWalletButton.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noteDetails() =
|
||||
fun noteDetailsTest() =
|
||||
setupHooks().run {
|
||||
scenario(OpenMainScreenScenario(composeTestRule, ProductType.Note))
|
||||
ComposeScreen.onComposeScreen<TestTopBar>(composeTestRule) {
|
||||
onTopBar {
|
||||
step("Open wallet details") {
|
||||
moreButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<DetailsTestScreen>(composeTestRule) {
|
||||
step("Assert wallet connect button does not exist") {
|
||||
onDetailsScreen {
|
||||
step("Assert 'Wallet connect' button does not exist") {
|
||||
walletConnectButton.assertIsNotDisplayed()
|
||||
}
|
||||
step("Assert scan card button is visible") {
|
||||
step("Assert 'Scan card' button is visible") {
|
||||
scanCardButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert buy Tangem card button is visible") {
|
||||
step("Assert 'Buy Tangem card' button is visible") {
|
||||
buyTangemButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert app settings button is visible") {
|
||||
step("Assert 'App settings' button is visible") {
|
||||
appSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert contact support button is visible") {
|
||||
step("Assert 'Contact support' button is visible") {
|
||||
contactSupportButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert terms or service button is visible") {
|
||||
step("Assert 'Terms or service' button is visible") {
|
||||
toSButton.assertIsDisplayed()
|
||||
}
|
||||
step("Open wallet settings screen") {
|
||||
step("Open 'Wallet settings' screen") {
|
||||
walletNameButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<WalletSettingsTestScreen>(composeTestRule) {
|
||||
step("Assert Card Settings button is visible") {
|
||||
onWalletSettingsScreen {
|
||||
step("Assert 'Card Settings' button is visible") {
|
||||
cardSettingsButton.assertIsDisplayed()
|
||||
}
|
||||
step("Assert Referral program button does not exist") {
|
||||
step("Assert 'Referral program' button does not exist") {
|
||||
referralProgramButton.assertIsNotDisplayed()
|
||||
}
|
||||
step("Assert Forget wallet button is visible") {
|
||||
step("Assert 'Forget wallet' button is visible") {
|
||||
forgetWalletButton.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.scenarios.OpenMainScreenScenario
|
||||
import com.tangem.tap.MainActivity
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
|
|
|
|||
|
|
@ -2,36 +2,35 @@ package com.tangem.tests
|
|||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.screens.DisclaimerTestScreen
|
||||
import com.tangem.screens.MainTestScreen
|
||||
import com.tangem.screens.StoriesTestScreen
|
||||
import com.tangem.screens.onDisclaimerScreen
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onStoriesScreen
|
||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class ScanErrorTest : BaseTestCase() {
|
||||
|
||||
@Test
|
||||
fun goToMain() =
|
||||
fun goToMainTest() =
|
||||
setupHooks().run {
|
||||
ComposeScreen.onComposeScreen<DisclaimerTestScreen>(composeTestRule) {
|
||||
step("Click on \"Accept\" button") {
|
||||
onDisclaimerScreen {
|
||||
step("Click on 'Accept' button") {
|
||||
acceptButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<StoriesTestScreen>(composeTestRule) {
|
||||
step("Click on \"Scan\" button emulating scan error") {
|
||||
onStoriesScreen {
|
||||
step("Click on 'Scan' button emulating scan error") {
|
||||
MockProvider.setEmulateError()
|
||||
scanButton.clickWithAssertion()
|
||||
}
|
||||
step("Click on \"Scan\" button again without emulating error") {
|
||||
step("Click on 'Scan' button again without emulating error") {
|
||||
MockProvider.resetEmulateError()
|
||||
scanButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<MainTestScreen>(composeTestRule) {
|
||||
onMainScreen {
|
||||
step("Make sure wallet screen is visible") {
|
||||
assertIsDisplayed()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,28 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import android.content.Intent.ACTION_VIEW
|
||||
import androidx.test.espresso.intent.matcher.UriMatchers
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.screens.DisclaimerTestScreen
|
||||
import com.tangem.screens.StoriesTestScreen
|
||||
import com.tangem.screens.onDisclaimerScreen
|
||||
import com.tangem.screens.onStoriesScreen
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.kakao.intent.KIntent
|
||||
import org.hamcrest.Matchers
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class StoriesTest : BaseTestCase() {
|
||||
|
||||
@Test
|
||||
fun clickOnOrderButton() =
|
||||
fun clickOnOrderButtonTest() =
|
||||
setupHooks().run {
|
||||
ComposeScreen.onComposeScreen<DisclaimerTestScreen>(composeTestRule) {
|
||||
step("Click on \"Accept\" button") {
|
||||
onDisclaimerScreen {
|
||||
step("Click on 'Accept' button") {
|
||||
acceptButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
ComposeScreen.onComposeScreen<StoriesTestScreen>(composeTestRule) {
|
||||
step("Click on \"Order\" button") {
|
||||
onStoriesScreen {
|
||||
step("Click on 'Order' button") {
|
||||
orderButton.clickWithAssertion()
|
||||
}
|
||||
step("Assert: browser opened") {
|
||||
|
|
|
|||
|
|
@ -39,8 +39,6 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
|||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
|
||||
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
|
|
@ -118,8 +116,6 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getTransactionSignerFactory(): TransactionSignerFactory
|
||||
|
||||
fun getOnrampFeatureToggles(): OnrampFeatureToggles
|
||||
|
||||
fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles
|
||||
|
||||
fun getOnboardingRepository(): OnboardingRepository
|
||||
|
|
@ -141,8 +137,6 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getWorkerFactory(): HiltWorkerFactory
|
||||
|
||||
fun getOnlineCardVerifier(): OnlineCardVerifier
|
||||
|
||||
fun getColdUserWalletBuilderFactory(): ColdUserWalletBuilder.Factory
|
||||
|
||||
fun getApiConfigsManager(): ApiConfigsManager
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import androidx.lifecycle.flowWithLifecycle
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.RoutingFeatureToggle
|
||||
import com.tangem.common.routing.entity.SerializableIntent
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
|
|
@ -34,9 +33,7 @@ import com.tangem.core.decompose.di.RootAppComponentContext
|
|||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.core.deeplink.WEBLINK_KEY
|
||||
import com.tangem.core.deeplink.converter.PayloadToDeeplinkConverter
|
||||
import com.tangem.core.navigation.email.EmailSender
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.data.balancehiding.DefaultDeviceFlipDetector
|
||||
import com.tangem.data.card.sdk.CardSdkOwner
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
|
|
@ -53,6 +50,7 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
|||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.features.tester.api.TesterMenuLauncher
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.google.GoogleServicesHelper
|
||||
import com.tangem.operations.backup.BackupService
|
||||
|
|
@ -69,7 +67,6 @@ import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandle
|
|||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import com.tangem.tap.features.main.MainViewModel
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.tap.routing.component.RoutingComponent
|
||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||
|
|
@ -103,9 +100,6 @@ val mainScope = CoroutineScope(mainCoroutineContext)
|
|||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||
|
||||
@Inject
|
||||
lateinit var appStateHolder: AppStateHolder
|
||||
|
||||
/** Router for opening tester menu */
|
||||
@Inject
|
||||
lateinit var cardSdkOwner: CardSdkOwner
|
||||
|
|
@ -143,9 +137,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
lateinit var userWalletsListManager: UserWalletsListManager
|
||||
|
||||
@Inject
|
||||
lateinit var emailSender: EmailSender
|
||||
|
||||
@Inject
|
||||
@RootAppComponentContext
|
||||
internal lateinit var rootComponentContext: AppComponentContext
|
||||
|
|
@ -174,15 +165,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
lateinit var dispatchers: CoroutineDispatcherProvider
|
||||
|
||||
@Inject
|
||||
internal lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
internal lateinit var defaultDeviceFlipDetector: DefaultDeviceFlipDetector
|
||||
|
||||
@Inject
|
||||
internal lateinit var routingFeatureToggle: RoutingFeatureToggle
|
||||
|
||||
@Inject
|
||||
internal lateinit var deeplinkFactory: DeepLinkFactory
|
||||
|
||||
|
|
@ -192,6 +177,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
internal lateinit var urlOpener: UrlOpener
|
||||
|
||||
@Inject
|
||||
internal lateinit var testerMenuLauncher: TesterMenuLauncher
|
||||
|
||||
internal val viewModel: MainViewModel by viewModels()
|
||||
|
||||
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
||||
|
|
@ -244,13 +232,12 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
sendStakingUnsubmittedHashes()
|
||||
checkGoogleServicesAvailability()
|
||||
|
||||
if (routingFeatureToggle.isDeepLinkNavigationEnabled.not() && intent != null && savedInstanceState == null) {
|
||||
// handle intent only on start, not on recreate
|
||||
handleDeepLink(intent = intent, isFromOnNewIntent = false)
|
||||
}
|
||||
|
||||
lifecycle.addObserver(WindowObscurationObserver)
|
||||
lifecycle.addObserver(defaultDeviceFlipDetector)
|
||||
|
||||
if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
lifecycle.addObserver(testerMenuLauncher.launchOnShakeObserver)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setRootContent() {
|
||||
|
|
@ -481,7 +468,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
}
|
||||
}
|
||||
|
||||
if (routingFeatureToggle.isDeepLinkNavigationEnabled && intent != null) {
|
||||
if (intent != null) {
|
||||
handleDeepLink(intent = intent, isFromOnNewIntent = false)
|
||||
}
|
||||
|
||||
|
|
@ -489,26 +476,22 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
}
|
||||
|
||||
private fun handleDeepLink(intent: Intent, isFromOnNewIntent: Boolean) {
|
||||
if (routingFeatureToggle.isDeepLinkNavigationEnabled) {
|
||||
val deepLinkExtras = PayloadToDeeplinkConverter.convertBundle(intent.extras)?.toUri()
|
||||
val webLink = intent.getStringExtra(WEBLINK_KEY)
|
||||
val deepLinkExtras = PayloadToDeeplinkConverter.convertBundle(intent.extras)?.toUri()
|
||||
val webLink = intent.getStringExtra(WEBLINK_KEY)
|
||||
|
||||
val receivedDeepLink = intent.data ?: deepLinkExtras
|
||||
val receivedDeepLink = intent.data ?: deepLinkExtras
|
||||
|
||||
when {
|
||||
receivedDeepLink != null -> {
|
||||
deeplinkFactory.handleDeeplink(
|
||||
deeplinkUri = receivedDeepLink,
|
||||
coroutineScope = lifecycleScope,
|
||||
isFromOnNewIntent = isFromOnNewIntent,
|
||||
)
|
||||
}
|
||||
webLink?.uriValidate() == true -> {
|
||||
urlOpener.openUrl(webLink)
|
||||
}
|
||||
when {
|
||||
receivedDeepLink != null -> {
|
||||
deeplinkFactory.handleDeeplink(
|
||||
deeplinkUri = receivedDeepLink,
|
||||
coroutineScope = lifecycleScope,
|
||||
isFromOnNewIntent = isFromOnNewIntent,
|
||||
)
|
||||
}
|
||||
webLink?.uriValidate() == true -> {
|
||||
urlOpener.openUrl(webLink)
|
||||
}
|
||||
} else {
|
||||
deepLinksRegistry.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,8 +58,6 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
|||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.api.TangemApiServiceSettings
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
|
|
@ -79,7 +77,6 @@ import com.tangem.wallet.BuildConfig
|
|||
import dagger.hilt.EntryPoints
|
||||
import kotlinx.coroutines.*
|
||||
import org.rekotlin.Store
|
||||
import kotlin.collections.set
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository as WalletConnect2Repository
|
||||
|
||||
lateinit var store: Store<AppState>
|
||||
|
|
@ -186,9 +183,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
private val transactionSignerFactory: TransactionSignerFactory
|
||||
get() = entryPoint.getTransactionSignerFactory()
|
||||
|
||||
private val onrampFeatureToggles: OnrampFeatureToggles
|
||||
get() = entryPoint.getOnrampFeatureToggles()
|
||||
|
||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles
|
||||
get() = entryPoint.getOnboardingV2FeatureToggles()
|
||||
|
||||
|
|
@ -224,9 +218,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
.setWorkerFactory(workerFactory)
|
||||
.build()
|
||||
|
||||
private val onlineCardVerifier: OnlineCardVerifier
|
||||
get() = entryPoint.getOnlineCardVerifier()
|
||||
|
||||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory
|
||||
get() = entryPoint.getColdUserWalletBuilderFactory()
|
||||
|
||||
|
|
@ -363,7 +354,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
shareManager = shareManager,
|
||||
appRouter = appRouter,
|
||||
transactionSignerFactory = transactionSignerFactory,
|
||||
onrampFeatureToggles = onrampFeatureToggles,
|
||||
environmentConfigStorage = environmentConfigStorage,
|
||||
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
||||
onboardingRepository = onboardingRepository,
|
||||
|
|
@ -372,7 +362,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
clipboardManager = clipboardManager,
|
||||
settingsManager = settingsManager,
|
||||
uiMessageSender = uiMessageSender,
|
||||
onlineCardVerifier = onlineCardVerifier,
|
||||
coldUserWalletBuilderFactory = coldUserWalletBuilderFactory,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,16 +3,11 @@ package com.tangem.tap.common.extensions
|
|||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.blockchainsdk.utils.amountToCreateAccount
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.model.WalletAddressData
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -57,43 +52,4 @@ suspend fun WalletManager.safeUpdate(isDemoCard: Boolean): Result<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
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import android.util.Log
|
|||
import coil.ImageLoader
|
||||
import coil.decode.GifDecoder
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.decode.SvgDecoder
|
||||
import coil.memory.MemoryCache
|
||||
import coil.request.CachePolicy
|
||||
import coil.util.Logger
|
||||
|
|
@ -36,6 +37,7 @@ fun createCoilImageLoader(context: Context, logEnabled: Boolean = false): ImageL
|
|||
} else {
|
||||
add(GifDecoder.Factory())
|
||||
}
|
||||
add(SvgDecoder.Factory())
|
||||
}
|
||||
.memoryCachePolicy(CachePolicy.ENABLED)
|
||||
.memoryCache {
|
||||
|
|
|
|||
|
|
@ -30,21 +30,5 @@ sealed class GlobalAction : Action {
|
|||
data class Success(val appCurrency: AppCurrency) : GlobalAction()
|
||||
}
|
||||
|
||||
data class UpdateWalletSignedHashes(
|
||||
val walletSignedHashes: Int?,
|
||||
val remainingSignatures: Int?,
|
||||
val walletPublicKey: ByteArray,
|
||||
) : GlobalAction()
|
||||
|
||||
data class IsSignWithRing(val isSignWithRing: Boolean) : GlobalAction()
|
||||
|
||||
object ExchangeManager : GlobalAction() {
|
||||
object Init : GlobalAction() {
|
||||
data class Success(
|
||||
val exchangeManager: com.tangem.tap.network.exchangeServices.CurrencyExchangeManager,
|
||||
) : GlobalAction()
|
||||
}
|
||||
|
||||
object Update : GlobalAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,23 +2,14 @@ package com.tangem.tap.common.redux.global
|
|||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.network.exchangeServices.CardExchangeRules
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoEnvironment
|
||||
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
|
||||
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -31,17 +22,17 @@ object GlobalMiddleware {
|
|||
val handler = globalMiddlewareHandler
|
||||
}
|
||||
|
||||
private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState ->
|
||||
private val globalMiddlewareHandler: Middleware<AppState> = { _, _ ->
|
||||
{ nextDispatch ->
|
||||
{ action ->
|
||||
handleAction(action, appState)
|
||||
handleAction(action)
|
||||
nextDispatch(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleAction(action: Action, appState: () -> AppState?) {
|
||||
private fun handleAction(action: Action) {
|
||||
when (action) {
|
||||
is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
|
||||
when (action.result) {
|
||||
|
|
@ -51,42 +42,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
}
|
||||
}
|
||||
}
|
||||
is GlobalAction.RestoreAppCurrency -> {
|
||||
restoreAppCurrency()
|
||||
}
|
||||
is GlobalAction.ExchangeManager.Init -> {
|
||||
val config = store.inject(DaggerGraphState::environmentConfigStorage).getConfigSync()
|
||||
|
||||
scope.launch {
|
||||
val scanResponseProvider: () -> ScanResponse? = {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.selectedUserWalletSync?.requireColdWallet()?.scanResponse // TODO [REDACTED_TASK_KEY]
|
||||
}
|
||||
val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card }
|
||||
|
||||
val buyService = makeBuyExchangeService(config)
|
||||
val sellService = makeSellExchangeService(config)
|
||||
val exchangeManager = CurrencyExchangeManager(
|
||||
buyService = buyService,
|
||||
sellService = sellService,
|
||||
primaryRules = CardExchangeRules(cardProvider),
|
||||
)
|
||||
// TODO: for refactoring (after remove old design refactor CurrencyExchangeManager and use 1 instance)
|
||||
store.inject(DaggerGraphState::appStateHolder).buyService = buyService
|
||||
store.inject(DaggerGraphState::appStateHolder).sellService = sellService
|
||||
store.inject(DaggerGraphState::appStateHolder).exchangeService = exchangeManager
|
||||
store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
|
||||
store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
|
||||
}
|
||||
}
|
||||
is GlobalAction.ExchangeManager.Init.Success -> {}
|
||||
is GlobalAction.ExchangeManager.Update -> {
|
||||
val exchangeManager = appState()?.globalState?.exchangeManager.guard {
|
||||
store.dispatchDebugErrorNotification("exchangeManager is not initialized")
|
||||
return
|
||||
}
|
||||
scope.launch { exchangeManager.update() }
|
||||
}
|
||||
is GlobalAction.RestoreAppCurrency -> restoreAppCurrency()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,21 +75,4 @@ private fun restoreAppCurrency() {
|
|||
|
||||
store.dispatchWithMain(GlobalAction.RestoreAppCurrency.Success(currency))
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeSellExchangeService(environmentConfig: EnvironmentConfig): ExchangeService {
|
||||
return MoonPayService(
|
||||
apiKey = environmentConfig.moonPayApiKey,
|
||||
secretKey = environmentConfig.moonPayApiSecretKey,
|
||||
logEnabled = LogConfig.network.moonPayService,
|
||||
)
|
||||
}
|
||||
|
||||
private fun makeBuyExchangeService(environmentConfig: EnvironmentConfig): ExchangeService {
|
||||
return MercuryoService(
|
||||
environment = MercuryoEnvironment.prod(
|
||||
widgetId = environmentConfig.mercuryoWidgetId,
|
||||
secret = environmentConfig.mercuryoSecret,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.utils.extensions.replaceBy
|
||||
import org.rekotlin.Action
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
|
|
@ -26,24 +25,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
is GlobalAction.RestoreAppCurrency.Success -> {
|
||||
globalState.copy(appCurrency = action.appCurrency)
|
||||
}
|
||||
is GlobalAction.UpdateWalletSignedHashes -> {
|
||||
val card = globalState.scanResponse?.card ?: return globalState
|
||||
val wallet = card.wallets
|
||||
.firstOrNull { it.publicKey.contentEquals(action.walletPublicKey) }
|
||||
?: return globalState
|
||||
|
||||
val newCardInstance = card.copy(
|
||||
wallets = card.wallets.toMutableList().also { walletsMutable ->
|
||||
walletsMutable.replaceBy(
|
||||
item = wallet.copy(
|
||||
totalSignedHashes = action.walletSignedHashes,
|
||||
remainingSignatures = action.remainingSignatures,
|
||||
),
|
||||
) { it.index == wallet.index }
|
||||
},
|
||||
)
|
||||
globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance))
|
||||
}
|
||||
is GlobalAction.IsSignWithRing -> globalState.copy(isLastSignWithRing = action.isSignWithRing)
|
||||
is GlobalAction.ShowDialog -> {
|
||||
globalState.copy(dialog = action.stateDialog)
|
||||
|
|
@ -51,9 +32,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
is GlobalAction.HideDialog -> {
|
||||
globalState.copy(dialog = null)
|
||||
}
|
||||
is GlobalAction.ExchangeManager.Init.Success -> {
|
||||
globalState.copy(exchangeManager = action.exchangeManager)
|
||||
}
|
||||
else -> globalState
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.domain.TapWalletManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingManager
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class GlobalState(
|
||||
|
|
@ -16,7 +14,6 @@ data class GlobalState(
|
|||
val appCurrency: AppCurrency = AppCurrency.Default,
|
||||
val scanCardFailsCounter: Int = 0,
|
||||
val dialog: StateDialog? = null,
|
||||
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
|
||||
val isLastSignWithRing: Boolean = false,
|
||||
) : StateType
|
||||
|
||||
|
|
@ -24,6 +21,5 @@ typealias CryptoCurrencyName = String
|
|||
|
||||
data class OnboardingState(
|
||||
val onboardingStarted: Boolean = false,
|
||||
val onboardingManager: OnboardingManager? = null,
|
||||
val shouldResetOnCreate: Boolean = false,
|
||||
)
|
||||
|
|
@ -16,16 +16,13 @@ import com.tangem.crypto.bip39.Wordlist
|
|||
import com.tangem.data.card.sdk.CardSdkOwner
|
||||
import com.tangem.data.card.sdk.CardSdkProvider
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironment
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectMap
|
||||
import com.tangem.datasource.api.common.config.managers.MutableApiConfigsManager
|
||||
import com.tangem.datasource.utils.AddHeadersInterceptor
|
||||
import com.tangem.datasource.utils.RequestHeader
|
||||
import com.tangem.operations.attestation.api.TangemApiServiceSettings
|
||||
import com.tangem.sdk.DefaultSessionViewDelegate
|
||||
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
|
||||
import com.tangem.sdk.extensions.*
|
||||
import com.tangem.sdk.nfc.AndroidNfcAvailabilityProvider
|
||||
import com.tangem.sdk.nfc.NfcManager
|
||||
|
|
@ -34,13 +31,6 @@ import com.tangem.tap.foregroundActivityObserver
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -55,11 +45,9 @@ import javax.inject.Singleton
|
|||
internal class DefaultCardSdkProvider @Inject constructor(
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val cardSdkFeatureToggles: CardSdkFeatureToggles,
|
||||
private val apiConfigsManager: ApiConfigsManager,
|
||||
appVersionProvider: AppVersionProvider,
|
||||
appInfoProvider: AppInfoProvider,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
) : CardSdkProvider, CardSdkOwner {
|
||||
|
||||
private val observer = Observer()
|
||||
|
|
@ -70,26 +58,15 @@ internal class DefaultCardSdkProvider @Inject constructor(
|
|||
get() = holder?.sdk ?: tryToRegisterWithForegroundActivity()
|
||||
|
||||
init {
|
||||
if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
appPreferencesStore.getObjectMap<ApiEnvironment>(PreferencesKeys.apiConfigsEnvironmentKey)
|
||||
.map {
|
||||
when (it[ApiConfig.ID.Attestation.name]) {
|
||||
ApiEnvironment.DEV,
|
||||
ApiEnvironment.STAGE,
|
||||
-> false
|
||||
ApiEnvironment.PROD,
|
||||
null,
|
||||
-> true
|
||||
}
|
||||
val mutableManager = apiConfigsManager as? MutableApiConfigsManager
|
||||
|
||||
mutableManager?.addListener(
|
||||
object : MutableApiConfigsManager.ApiConfigEnvChangeListener(id = ApiConfig.ID.TangemTech) {
|
||||
override fun onChange(environmentConfig: ApiEnvironmentConfig) {
|
||||
holder?.sdk?.config?.tangemApiBaseUrl = environmentConfig.baseUrl
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.onEach { isProd ->
|
||||
holder?.let {
|
||||
it.sdk.config.isTangemAttestationProdEnv = isProd
|
||||
}
|
||||
}
|
||||
.launchIn(CoroutineScope(SupervisorJob() + dispatchers.main))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
TangemApiServiceSettings.addInterceptors(
|
||||
AddHeadersInterceptor(
|
||||
|
|
@ -187,10 +164,8 @@ internal class DefaultCardSdkProvider @Inject constructor(
|
|||
keystoreManager = keystoreManager,
|
||||
wordlist = Wordlist.getWordlist(activity),
|
||||
config = config.apply {
|
||||
isNewOnlineAttestationEnabled = cardSdkFeatureToggles.isNewAttestationEnabled
|
||||
|
||||
val apiConfig = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.Attestation)
|
||||
isTangemAttestationProdEnv = apiConfig.environment == ApiEnvironment.PROD
|
||||
val apiConfig = apiConfigsManager.getEnvironmentConfig(id = ApiConfig.ID.TangemTech)
|
||||
tangemApiBaseUrl = apiConfig.baseUrl
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
|
|||
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
|
||||
import com.tangem.tap.network.exchangeServices.DefaultRampManager
|
||||
|
|
@ -51,17 +50,13 @@ internal object ActivityModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
onrampFeatureToggles: OnrampFeatureToggles,
|
||||
): RampStateManager {
|
||||
return DefaultRampManager(
|
||||
exchangeService = appStateHolder.exchangeService,
|
||||
buyService = Provider { requireNotNull(appStateHolder.buyService) },
|
||||
sellService = Provider { requireNotNull(appStateHolder.sellService) },
|
||||
expressServiceLoader = expressServiceLoader,
|
||||
currenciesRepository = currenciesRepository,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
dispatchers = dispatchers,
|
||||
onrampFeatureToggles = onrampFeatureToggles,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
|||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -75,6 +76,7 @@ internal object ManageTokensDomainModule {
|
|||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): SaveManagedTokensUseCase {
|
||||
return SaveManagedTokensUseCase(
|
||||
|
|
@ -86,6 +88,7 @@ internal object ManageTokensDomainModule {
|
|||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import com.tangem.domain.nft.*
|
|||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -33,9 +35,13 @@ internal object NFTDomainModule {
|
|||
fun providesFetchNFTCollectionsUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): FetchNFTCollectionsUseCase = FetchNFTCollectionsUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -43,9 +49,13 @@ internal object NFTDomainModule {
|
|||
fun providesRefreshAllNFTUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): RefreshAllNFTUseCase = RefreshAllNFTUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -118,8 +128,16 @@ internal object NFTDomainModule {
|
|||
walletsRepository: WalletsRepository,
|
||||
nftRepository: NFTRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): DisableWalletNFTUseCase {
|
||||
return DisableWalletNFTUseCase(walletsRepository, nftRepository, currenciesRepository)
|
||||
return DisableWalletNFTUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
nftRepository = nftRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
|||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
|
||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -44,6 +45,7 @@ internal object TokensDomainModule {
|
|||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): AddCryptoCurrenciesUseCase {
|
||||
return AddCryptoCurrenciesUseCase(
|
||||
|
|
@ -52,6 +54,7 @@ internal object TokensDomainModule {
|
|||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -107,8 +110,15 @@ internal object TokensDomainModule {
|
|||
fun provideRemoveCurrencyUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): RemoveCurrencyUseCase {
|
||||
return RemoveCurrencyUseCase(currenciesRepository, walletManagersFacade)
|
||||
return RemoveCurrencyUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -145,6 +155,8 @@ internal object TokensDomainModule {
|
|||
currencyChecksRepository: CurrencyChecksRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): GetCurrencyWarningsUseCase {
|
||||
return GetCurrencyWarningsUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
|
|
@ -152,6 +164,8 @@ internal object TokensDomainModule {
|
|||
dispatchers = dispatchers,
|
||||
currencyChecksRepository = currencyChecksRepository,
|
||||
currencyStatusOperations = baseCurrencyStatusOperations,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -163,6 +177,7 @@ internal object TokensDomainModule {
|
|||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): FetchCurrencyStatusUseCase {
|
||||
return FetchCurrencyStatusUseCase(
|
||||
|
|
@ -171,6 +186,7 @@ internal object TokensDomainModule {
|
|||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -197,8 +213,12 @@ internal object TokensDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCryptoCurrencyUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrencyUseCase {
|
||||
return GetCryptoCurrencyUseCase(currenciesRepository)
|
||||
fun provideGetCryptoCurrencyUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): GetCryptoCurrencyUseCase {
|
||||
return GetCryptoCurrencyUseCase(currenciesRepository, multiWalletCryptoCurrenciesSupplier, tokensFeatureToggles)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -219,9 +239,16 @@ internal object TokensDomainModule {
|
|||
@Singleton
|
||||
fun provideApplyTokenListSortingUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ApplyTokenListSortingUseCase {
|
||||
return ApplyTokenListSortingUseCase(currenciesRepository, dispatchers)
|
||||
return ApplyTokenListSortingUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -280,9 +307,13 @@ internal object TokensDomainModule {
|
|||
@Singleton
|
||||
fun provideIsCryptoCurrencyCoinCouldHideUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): IsCryptoCurrencyCoinCouldHideUseCase {
|
||||
return IsCryptoCurrencyCoinCouldHideUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -300,9 +331,16 @@ internal object TokensDomainModule {
|
|||
@Singleton
|
||||
fun provideGetBalanceNotEnoughForFeeWarningUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetBalanceNotEnoughForFeeWarningUseCase {
|
||||
return GetBalanceNotEnoughForFeeWarningUseCase(currenciesRepository, dispatchers)
|
||||
return GetBalanceNotEnoughForFeeWarningUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -343,10 +381,14 @@ internal object TokensDomainModule {
|
|||
fun provideRefreshMultiCurrencyWalletQuotesUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): RefreshMultiCurrencyWalletQuotesUseCase {
|
||||
return RefreshMultiCurrencyWalletQuotesUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -374,6 +416,7 @@ internal object TokensDomainModule {
|
|||
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
): BaseCurrenciesStatusesOperations {
|
||||
return CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
|
|
@ -388,6 +431,7 @@ internal object TokensDomainModule {
|
|||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -406,6 +450,7 @@ internal object TokensDomainModule {
|
|||
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
): BaseCurrencyStatusOperations {
|
||||
return CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
|
|
@ -419,6 +464,7 @@ internal object TokensDomainModule {
|
|||
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
|
||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -428,4 +474,26 @@ internal object TokensDomainModule {
|
|||
fun provideGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
|
||||
return GetCryptoCurrenciesUseCase(currenciesRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletBalanceFetcher(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesFetcher: MultiWalletCryptoCurrenciesFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): WalletBalanceFetcher {
|
||||
return WalletBalanceFetcher(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiWalletCryptoCurrenciesFetcher = multiWalletCryptoCurrenciesFetcher,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
|
|||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
|
|
@ -58,12 +60,16 @@ internal object TransactionDomainModule {
|
|||
walletManagersFacade: WalletManagersFacade,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): AssociateAssetUseCase {
|
||||
return AssociateAssetUseCase(
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
currenciesRepository = currenciesRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,7 @@ import com.tangem.domain.wallets.repository.WalletsRepository
|
|||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -191,28 +188,14 @@ internal object WalletsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCardImageUseCase(
|
||||
onlineCardVerifier: OnlineCardVerifier,
|
||||
cardArtworksProvider: CardArtworksProvider,
|
||||
cardSdkFeatureToggles: CardSdkFeatureToggles,
|
||||
): GetCardImageUseCase {
|
||||
return GetCardImageUseCase(
|
||||
verifier = onlineCardVerifier,
|
||||
cardArtworksProvider = cardArtworksProvider,
|
||||
cardSdkFeatureToggles = cardSdkFeatureToggles,
|
||||
)
|
||||
fun provideGetCardImageUseCase(cardArtworksProvider: CardArtworksProvider): GetCardImageUseCase {
|
||||
return GetCardImageUseCase(cardArtworksProvider = cardArtworksProvider)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesIsWalletNFTEnabledSyncUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
nftFeatureToggles: NFTFeatureToggles,
|
||||
): IsWalletNFTEnabledSyncUseCase {
|
||||
return IsWalletNFTEnabledSyncUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
nftFeatureToggles = nftFeatureToggles,
|
||||
)
|
||||
fun providesIsWalletNFTEnabledSyncUseCase(walletsRepository: WalletsRepository): IsWalletNFTEnabledSyncUseCase {
|
||||
return IsWalletNFTEnabledSyncUseCase(walletsRepository = walletsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.di.hot
|
||||
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.tap.features.hot.TangemHotSDKProxy
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface TangemHotSdkModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindTangemHotSdk(proxy: TangemHotSDKProxy): TangemHotSdk
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.tap.di.routing
|
||||
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.RoutingFeatureToggle
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.tap.routing.ProxyAppRouter
|
||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||
import com.tangem.tap.routing.configurator.MutableAppRouterConfig
|
||||
|
|
@ -33,10 +31,4 @@ internal object AppRouterModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideAppRouterConfigurator(): AppRouterConfig = MutableAppRouterConfig()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRoutingFeatureToggle(featureTogglesManager: FeatureTogglesManager): RoutingFeatureToggle {
|
||||
return RoutingFeatureToggle(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import com.reown.android.relay.ConnectionType
|
|||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.pair.unsupportedDApps
|
||||
import com.tangem.data.walletconnect.pair.UnsupportedDApps
|
||||
import com.tangem.domain.walletconnect.WcPairService
|
||||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
|
|
@ -216,7 +216,7 @@ internal class DefaultLegacyWalletConnectRepository(
|
|||
Timber.i("sessionProposal: $sessionProposal")
|
||||
this@DefaultLegacyWalletConnectRepository.sessionProposal = sessionProposal
|
||||
|
||||
if (sessionProposal.name in unsupportedDApps) {
|
||||
if (sessionProposal.name in UnsupportedDApps.list) {
|
||||
Timber.i("Unsupported DApp")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
|
|
|
|||
|
|
@ -43,9 +43,6 @@ object DemoHelper {
|
|||
}
|
||||
|
||||
private fun getScanResponse(appState: () -> AppState?): ScanResponse? {
|
||||
val state = appState() ?: return null
|
||||
|
||||
return state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: state.globalState.scanResponse
|
||||
return appState()?.globalState?.scanResponse
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,6 @@ import com.tangem.tap.common.analytics.events.Settings
|
|||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.domain.extensions.signedHashesCount
|
||||
import com.tangem.tap.features.details.ui.cardsettings.CardInfo
|
||||
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsScreenState
|
||||
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
|
||||
|
|
@ -171,6 +170,8 @@ internal class CardSettingsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun CardDTO.signedHashesCount(): Int = wallets.sumOf { it.totalSignedHashes ?: 0 }
|
||||
|
||||
private fun handleClickingItem(item: CardInfo) {
|
||||
when (item) {
|
||||
is CardInfo.ChangeAccessCode -> {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import arrow.core.getOrElse
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.qrscanning.models.QrResultSource
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
|
|
@ -31,12 +32,18 @@ internal class WalletConnectModel @Inject constructor(
|
|||
|
||||
init {
|
||||
modelScope.launch {
|
||||
listenToQrScanningUseCase(SourceType.WALLET_CONNECT)
|
||||
listenToQrScanningUseCase.listen(SourceType.WALLET_CONNECT)
|
||||
.getOrElse { emptyFlow() }
|
||||
.map {
|
||||
.map { result ->
|
||||
val source = when (result.resultSource) {
|
||||
QrResultSource.CLIPBOARD -> WalletConnectAction.OpenSession.SourceType.CLIPBOARD
|
||||
QrResultSource.CAMERA,
|
||||
QrResultSource.GALLERY,
|
||||
-> WalletConnectAction.OpenSession.SourceType.QR
|
||||
}
|
||||
WalletConnectAction.OpenSession(
|
||||
wcUri = it,
|
||||
source = WalletConnectAction.OpenSession.SourceType.QR,
|
||||
wcUri = result.qrCode,
|
||||
source = source,
|
||||
userWalletId = params.userWalletId,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TestTags
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.tap.features.home.compose.content.*
|
||||
import com.tangem.tap.features.home.compose.views.HomeButtons
|
||||
import com.tangem.tap.features.home.compose.views.SearchCurrenciesButton
|
||||
|
|
@ -61,7 +61,7 @@ internal fun StoriesScreen(
|
|||
StoriesScreenContent(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.testTag(TestTags.STORIES_SCREEN),
|
||||
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
|
||||
config = StoriesScreenContentConfig(
|
||||
storiesSize = state.stories.lastIndex,
|
||||
currentStoryIndex = currentStoryIndex,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TestTags
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.tap.features.home.compose.content.*
|
||||
import com.tangem.tap.features.home.compose.views.HomeButtonsV2
|
||||
import com.tangem.tap.features.home.compose.views.StoriesProgressBar
|
||||
|
|
@ -57,7 +57,7 @@ internal fun StoriesScreenV2(
|
|||
StoriesScreenContentV2(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.testTag(TestTags.STORIES_SCREEN),
|
||||
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
|
||||
config = StoriesScreenContentV2Config(
|
||||
storiesSize = state.stories.lastIndex,
|
||||
currentStoryIndex = currentStoryIndex,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TestTags
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -34,7 +34,7 @@ internal fun HomeButtons(
|
|||
ScanCardButton(
|
||||
modifier = Modifier
|
||||
.weight(weight = 1f)
|
||||
.testTag(TestTags.STORIES_SCREEN_SCAN_BUTTON),
|
||||
.testTag(StoriesScreenTestTags.SCAN_BUTTON),
|
||||
showProgress = btnScanStateInProgress,
|
||||
onClick = onScanButtonClick,
|
||||
)
|
||||
|
|
@ -42,7 +42,7 @@ internal fun HomeButtons(
|
|||
OrderCardButton(
|
||||
modifier = Modifier
|
||||
.weight(weight = 1f)
|
||||
.testTag(TestTags.STORIES_SCREEN_ORDER_BUTTON),
|
||||
.testTag(StoriesScreenTestTags.ORDER_BUTTON),
|
||||
onClick = onShopButtonClick,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TestTags
|
||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -37,19 +37,19 @@ internal fun HomeButtonsV2(
|
|||
CreateNewWalletButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.STORIES_SCREEN_CREATE_NEW_WALLET_BUTTON),
|
||||
.testTag(StoriesScreenTestTags.CREATE_NEW_WALLET_BUTTON),
|
||||
onClick = onCreateNewWalletButtonClick,
|
||||
)
|
||||
AddExistingWalletButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.STORIES_SCREEN_ADD_EXISTING_WALLET_BUTTON),
|
||||
.testTag(StoriesScreenTestTags.ADD_EXISTING_WALLET_BUTTON),
|
||||
onClick = onAddExistingWalletButtonClick,
|
||||
)
|
||||
ScanCardButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.STORIES_SCREEN_SCAN_BUTTON),
|
||||
.testTag(StoriesScreenTestTags.SCAN_BUTTON),
|
||||
showProgress = btnScanStateInProgress,
|
||||
onClick = onScanButtonClick,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ private fun handleHomeAction(action: Action) {
|
|||
Analytics.send(IntroductionProcess.ScreenOpened())
|
||||
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
store.dispatch(GlobalAction.ExchangeManager.Init)
|
||||
}
|
||||
is HomeAction.ReadCard -> {
|
||||
action.scope.launch {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.tap.features.hot
|
||||
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.hot.sdk.model.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Proxy for [TangemHotSdk] to allow lazy initialization and provide a way to access the SDK state from domain layer.
|
||||
* SDK is initialized in the [com.tangem.tap.routing.component.RoutingComponent] and can be accessed through this proxy.
|
||||
* Be aware that the SDK is initialized on activity creation, so it may not be available immediately.
|
||||
*/
|
||||
@Singleton
|
||||
class TangemHotSDKProxy @Inject constructor() : TangemHotSdk {
|
||||
|
||||
val sdkState = MutableStateFlow<TangemHotSdk?>(null)
|
||||
|
||||
override suspend fun importWallet(mnemonic: Mnemonic, passphrase: CharArray?, auth: HotAuth): HotWalletId =
|
||||
callSdk { importWallet(mnemonic, passphrase, auth) }
|
||||
|
||||
override suspend fun generateWallet(auth: HotAuth, mnemonicType: MnemonicType): HotWalletId =
|
||||
callSdk { generateWallet(auth, mnemonicType) }
|
||||
|
||||
override suspend fun exportMnemonic(unlockHotWallet: UnlockHotWallet): SeedPhrasePrivateInfo =
|
||||
callSdk { exportMnemonic(unlockHotWallet) }
|
||||
|
||||
override suspend fun exportBackup(unlockHotWallet: UnlockHotWallet): ByteArray =
|
||||
callSdk { exportBackup(unlockHotWallet) }
|
||||
|
||||
override suspend fun delete(id: HotWalletId) = callSdk { delete(id) }
|
||||
|
||||
override suspend fun changeAuth(unlockHotWallet: UnlockHotWallet, auth: HotAuth): HotWalletId =
|
||||
callSdk { changeAuth(unlockHotWallet, auth) }
|
||||
|
||||
override suspend fun derivePublicKey(
|
||||
unlockHotWallet: UnlockHotWallet,
|
||||
request: DeriveWalletRequest,
|
||||
): DerivedPublicKeyResponse = callSdk { derivePublicKey(unlockHotWallet, request) }
|
||||
|
||||
override suspend fun signHashes(unlockHotWallet: UnlockHotWallet, dataToSign: List<DataToSign>): List<SignedData> =
|
||||
callSdk { signHashes(unlockHotWallet, dataToSign) }
|
||||
|
||||
private suspend fun <T> callSdk(block: suspend TangemHotSdk.() -> T): T {
|
||||
return withTimeout(timeMillis = 1000) {
|
||||
sdkState.filterNotNull().first()
|
||||
}.block()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,13 +4,11 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.common.keyboard.KeyboardValidator
|
||||
import com.tangem.common.routing.RoutingFeatureToggle
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.event.TechAnalyticsEvent
|
||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.coil.ImagePreloader
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
|
|
@ -18,11 +16,15 @@ import com.tangem.core.ui.message.BottomSheetMessage
|
|||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase
|
||||
import com.tangem.domain.balancehiding.BalanceHidingSettings
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.notifications.GetApplicationIdUseCase
|
||||
import com.tangem.domain.notifications.SendPushTokenUseCase
|
||||
import com.tangem.domain.notifications.models.ApplicationId
|
||||
|
|
@ -37,15 +39,18 @@ import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
|
|||
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
||||
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.domain.wallets.usecase.AssociateWalletsWithApplicationIdUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSavedWalletChangesUseCase
|
||||
import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase
|
||||
import com.tangem.feature.swap.analytics.StoriesEvents
|
||||
import com.tangem.features.onramp.deeplink.OnrampDeepLink
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.wallet.BuildConfig
|
||||
|
|
@ -58,7 +63,7 @@ import timber.log.Timber
|
|||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
@HiltViewModel
|
||||
internal class MainViewModel @Inject constructor(
|
||||
private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase,
|
||||
|
|
@ -78,8 +83,6 @@ internal class MainViewModel @Inject constructor(
|
|||
private val imagePreloader: ImagePreloader,
|
||||
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val onrampDeepLinkFactory: OnrampDeepLink.Factory,
|
||||
private val notificationsToggles: NotificationsFeatureToggles,
|
||||
private val getApplicationIdUseCase: GetApplicationIdUseCase,
|
||||
private val subscribeOnWalletsUseCase: GetSavedWalletChangesUseCase,
|
||||
|
|
@ -88,7 +91,8 @@ internal class MainViewModel @Inject constructor(
|
|||
private val sendPushTokenUseCase: SendPushTokenUseCase,
|
||||
private val apiConfigsManager: ApiConfigsManager,
|
||||
private val multiQuoteUpdater: MultiQuoteUpdater,
|
||||
routingFeatureToggle: RoutingFeatureToggle,
|
||||
private val appStateHolder: AppStateHolder,
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -120,6 +124,8 @@ internal class MainViewModel @Inject constructor(
|
|||
|
||||
multiQuoteUpdater.subscribe()
|
||||
|
||||
initializeOffRamp()
|
||||
|
||||
observeFlips()
|
||||
displayBalancesHidingStatusToast()
|
||||
displayHiddenBalancesModalNotification()
|
||||
|
|
@ -129,10 +135,6 @@ internal class MainViewModel @Inject constructor(
|
|||
sendKeyboardIdentifierEvent()
|
||||
|
||||
preloadImages()
|
||||
|
||||
if (!routingFeatureToggle.isDeepLinkNavigationEnabled) {
|
||||
initializeDeepLinks()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
|
@ -189,7 +191,12 @@ internal class MainViewModel @Inject constructor(
|
|||
userWalletsListManager.selectedUserWallet
|
||||
.distinctUntilChanged()
|
||||
.onEach { userWallet ->
|
||||
Analytics.setContext(userWallet.requireColdWallet().scanResponse) // TODO [REDACTED_TASK_KEY]
|
||||
when (userWallet) {
|
||||
is UserWallet.Cold -> Analytics.setContext(userWallet.scanResponse)
|
||||
is UserWallet.Hot -> {
|
||||
// TODO [REDACTED_TASK_KEY]
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(viewModelScope)
|
||||
|
|
@ -201,6 +208,28 @@ internal class MainViewModel @Inject constructor(
|
|||
.onRight { Timber.d("Staking token list was fetched successfully") }
|
||||
}
|
||||
|
||||
private fun initializeOffRamp() {
|
||||
viewModelScope.launch {
|
||||
val sellService = makeSellExchangeService(environmentConfig = environmentConfigStorage.getConfigSync())
|
||||
appStateHolder.sellService = sellService
|
||||
|
||||
sellService.update()
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeSellExchangeService(environmentConfig: EnvironmentConfig): ExchangeService {
|
||||
val cardProvider: () -> ScanResponse? = {
|
||||
userWalletsListManager.selectedUserWalletSync?.requireColdWallet()?.scanResponse // TODO [REDACTED_TASK_KEY]
|
||||
}
|
||||
|
||||
return MoonPayService(
|
||||
apiKey = environmentConfig.moonPayApiKey,
|
||||
secretKey = environmentConfig.moonPayApiSecretKey,
|
||||
logEnabled = LogConfig.network.moonPayService,
|
||||
cardProvider = { cardProvider.invoke()?.card },
|
||||
)
|
||||
}
|
||||
|
||||
private fun observeFlips() {
|
||||
listenToFlipsUseCase().launchIn(viewModelScope)
|
||||
}
|
||||
|
|
@ -384,7 +413,7 @@ internal class MainViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex.message)
|
||||
Timber.e(ex)
|
||||
analyticsEventHandler.send(
|
||||
StoriesEvents.Error(
|
||||
type = StoryContentIds.STORY_FIRST_TIME_SWAP.analyticType,
|
||||
|
|
@ -393,10 +422,6 @@ internal class MainViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun initializeDeepLinks() {
|
||||
deepLinksRegistry.register(onrampDeepLinkFactory.create(viewModelScope))
|
||||
}
|
||||
|
||||
private suspend fun initPushNotifications() {
|
||||
if (notificationsToggles.isNotificationsEnabled) {
|
||||
getApplicationIdUseCase().onRight { applicationId ->
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
||||
@Deprecated("Remove when navigation refactoring will be implemented")
|
||||
class OnboardingManager(var scanResponse: ScanResponse)
|
||||
|
|
@ -27,7 +27,7 @@ object WalletActivationErrorDialog {
|
|||
// changed on email support [REDACTED_TASK_KEY]
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
|
||||
|
||||
val scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
|
||||
val cardInfo = store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull()
|
||||
|
|
|
|||
|
|
@ -1,30 +1,18 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
@Deprecated("Will be removed soon")
|
||||
|
|
@ -46,79 +34,10 @@ object TradeCryptoMiddleware {
|
|||
|
||||
when (action) {
|
||||
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
|
||||
is TradeCryptoAction.Buy -> proceedBuyAction(action)
|
||||
is TradeCryptoAction.Sell -> proceedSellAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedBuyAction(action: TradeCryptoAction.Buy) {
|
||||
val isOnrampEnabled = store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled
|
||||
if (isOnrampEnabled) {
|
||||
proceedWithOnramp(action.userWallet.walletId, action.cryptoCurrencyStatus.currency, action.source)
|
||||
} else {
|
||||
proceedWithLegacyBuyAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithOnramp(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, source: OnrampSource) {
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Onramp(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
source = source,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithLegacyBuyAction(action: TradeCryptoAction.Buy) {
|
||||
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
|
||||
?.defaultAddress
|
||||
?.let(NetworkAddress.Address::value)
|
||||
?: return
|
||||
|
||||
val status = action.cryptoCurrencyStatus
|
||||
val currency = status.currency
|
||||
val blockchain = currency.network.toBlockchain()
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
val topUrl = exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Buy,
|
||||
cryptoCurrency = currency,
|
||||
fiatCurrencyName = action.appCurrencyCode,
|
||||
walletAddress = networkAddress,
|
||||
isDarkTheme = MutableAppThemeModeHolder.isDarkThemeActive,
|
||||
)
|
||||
|
||||
scope.launch {
|
||||
if (currency is CryptoCurrency.Token && currency.network.isTestnet) {
|
||||
val walletManager = store.inject(DaggerGraphState::walletManagersFacade)
|
||||
.getOrCreateWalletManager(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = currency.network.derivationPath.value,
|
||||
)
|
||||
|
||||
if (walletManager !is EthereumWalletManager) {
|
||||
store.dispatchDebugErrorNotification("Testnet tokens available only for the Ethereum")
|
||||
return@launch
|
||||
}
|
||||
|
||||
buyErc20TestnetTokens(
|
||||
card = action.userWallet.requireColdWallet().scanResponse.card, // TODO [REDACTED_TASK_KEY]
|
||||
walletManager = walletManager,
|
||||
destinationAddress = currency.contractAddress,
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
topUrl?.let {
|
||||
store.dispatchOpenUrl(it)
|
||||
Analytics.send(Token.Topup.ScreenOpened())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedSellAction(action: TradeCryptoAction.Sell) {
|
||||
val networkAddress = action.cryptoCurrencyStatus.value.networkAddress
|
||||
?.defaultAddress
|
||||
|
|
@ -126,8 +45,7 @@ object TradeCryptoMiddleware {
|
|||
?: return
|
||||
val currency = action.cryptoCurrencyStatus.currency
|
||||
|
||||
store.state.globalState.exchangeManager.getUrl(
|
||||
action = CurrencyExchangeManager.Action.Sell,
|
||||
store.inject(DaggerGraphState::appStateHolder).sellService?.getUrl(
|
||||
cryptoCurrency = currency,
|
||||
fiatCurrencyName = action.appCurrencyCode,
|
||||
walletAddress = networkAddress,
|
||||
|
|
@ -140,9 +58,9 @@ object TradeCryptoMiddleware {
|
|||
|
||||
private fun openReceiptUrl(transactionId: String) {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
store.state.globalState.exchangeManager.getSellCryptoReceiptUrl(
|
||||
action = CurrencyExchangeManager.Action.Sell,
|
||||
transactionId = transactionId,
|
||||
)?.let { store.dispatchOpenUrl(it) }
|
||||
|
||||
val sellService = store.inject(DaggerGraphState::appStateHolder).sellService
|
||||
sellService?.getSellCryptoReceiptUrl(transactionId = transactionId)
|
||||
?.let(store::dispatchOpenUrl)
|
||||
}
|
||||
}
|
||||
|
|
@ -131,6 +131,5 @@ internal class WelcomeModel @Inject constructor(
|
|||
|
||||
private fun initGlobalState() {
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
store.dispatch(GlobalAction.ExchangeManager.Init)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,13 +9,23 @@ import com.tangem.domain.wallets.models.requireColdWallet
|
|||
internal class DefaultAuthProvider(private val userWalletsListManager: UserWalletsListManager) : AuthProvider {
|
||||
|
||||
override fun getCardPublicKey(): String {
|
||||
return userWalletsListManager.selectedUserWalletSync
|
||||
?.requireColdWallet()?.scanResponse?.card?.cardPublicKey?.toHexString() ?: ""
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync
|
||||
|
||||
return when (userWallet) {
|
||||
is UserWallet.Cold -> userWallet.scanResponse.card.cardPublicKey.toHexString()
|
||||
is UserWallet.Hot -> userWallet.wallets?.firstOrNull()?.publicKey?.toHexString() ?: ""
|
||||
null -> ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCardId(): String {
|
||||
return userWalletsListManager.selectedUserWalletSync
|
||||
?.requireColdWallet()?.scanResponse?.card?.cardId ?: ""
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync
|
||||
|
||||
if (userWallet !is UserWallet.Cold) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return userWallet.requireColdWallet().scanResponse.card.cardId
|
||||
}
|
||||
|
||||
override fun getCardsPublicKeys(): Map<String, String> {
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.network.exchangeServices
|
||||
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CardExchangeRules(
|
||||
val cardProvider: () -> CardDTO?,
|
||||
) : ExchangeRules {
|
||||
|
||||
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean {
|
||||
val card = scanResponse.card
|
||||
|
||||
return when {
|
||||
card.isDemoCard() -> true
|
||||
card.isStart2Coin -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
override fun availableForSell(currency: Currency): Boolean {
|
||||
val card = cardProvider() ?: return false
|
||||
|
||||
return !card.isStart2Coin
|
||||
}
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
package com.tangem.tap.network.exchangeServices
|
||||
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TangemSigner
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CurrencyExchangeManager(
|
||||
private val buyService: ExchangeService,
|
||||
private val sellService: ExchangeService,
|
||||
private val primaryRules: ExchangeRules,
|
||||
) : ExchangeService {
|
||||
|
||||
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||
get() = _initializationStatus
|
||||
|
||||
private val _initializationStatus: MutableStateFlow<ExchangeServiceInitializationStatus> =
|
||||
MutableStateFlow(value = lceLoading())
|
||||
|
||||
override suspend fun update() {
|
||||
_initializationStatus.value = lceLoading()
|
||||
|
||||
if (!store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled) {
|
||||
buyService.update()
|
||||
}
|
||||
|
||||
sellService.update()
|
||||
|
||||
_initializationStatus.value = lceContent()
|
||||
}
|
||||
|
||||
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean {
|
||||
return primaryRules.availableForBuy(scanResponse, currency) &&
|
||||
buyService.availableForBuy(scanResponse, currency)
|
||||
}
|
||||
|
||||
override fun availableForSell(currency: Currency): Boolean {
|
||||
return primaryRules.availableForSell(currency) && sellService.availableForSell(currency)
|
||||
}
|
||||
|
||||
override fun getUrl(
|
||||
action: Action,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
fiatCurrencyName: String,
|
||||
walletAddress: String,
|
||||
isDarkTheme: Boolean,
|
||||
): String? {
|
||||
val blockchain = cryptoCurrency.network.toBlockchain()
|
||||
if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl()
|
||||
|
||||
val urlBuilder = getExchangeUrlBuilder(action)
|
||||
return urlBuilder.getUrl(
|
||||
action,
|
||||
cryptoCurrency,
|
||||
fiatCurrencyName,
|
||||
walletAddress,
|
||||
isDarkTheme,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getSellCryptoReceiptUrl(action: Action, transactionId: String): String? {
|
||||
val urlBuilder = getExchangeUrlBuilder(action)
|
||||
return urlBuilder.getSellCryptoReceiptUrl(action, transactionId)
|
||||
}
|
||||
|
||||
private fun getExchangeUrlBuilder(action: Action): ExchangeUrlBuilder {
|
||||
return when (action) {
|
||||
Action.Buy -> buyService
|
||||
Action.Sell -> sellService
|
||||
}
|
||||
}
|
||||
|
||||
enum class Action { Buy, Sell }
|
||||
|
||||
companion object {
|
||||
fun dummy(): CurrencyExchangeManager = CurrencyExchangeManager(
|
||||
buyService = ExchangeService.dummy(),
|
||||
sellService = ExchangeService.dummy(),
|
||||
primaryRules = ExchangeRules.dummy(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Useless")
|
||||
suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletManager, destinationAddress: String) {
|
||||
walletManager.safeUpdate(card.isDemoCard())
|
||||
|
||||
val amountToSend = Amount(walletManager.wallet.blockchain)
|
||||
|
||||
val feeResult = walletManager.getFee(amountToSend, destinationAddress) as? Result.Success ?: return
|
||||
val fee = when (val feeForTx = feeResult.data) {
|
||||
is TransactionFee.Choosable -> feeForTx.minimum
|
||||
is TransactionFee.Single -> feeForTx.normal
|
||||
}
|
||||
|
||||
val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO
|
||||
if (coinValue < fee.amount.value) return
|
||||
|
||||
val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins
|
||||
val signer = TangemSigner(
|
||||
cardId = card.cardId.takeIf { isCardNotBackedUp },
|
||||
tangemSdk = store.inject(DaggerGraphState::cardSdkConfigRepository).sdk,
|
||||
initialMessage = Message(),
|
||||
twinKey = null,
|
||||
) { signResponse ->
|
||||
store.dispatch(
|
||||
GlobalAction.UpdateWalletSignedHashes(
|
||||
walletSignedHashes = signResponse.totalSignedHashes,
|
||||
walletPublicKey = walletManager.wallet.publicKey.seedKey,
|
||||
remainingSignatures = signResponse.remainingSignatures,
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatch(action = GlobalAction.IsSignWithRing(signResponse.isRing))
|
||||
}
|
||||
|
||||
walletManager.send(
|
||||
transactionData = walletManager.createTransaction(
|
||||
amount = amountToSend,
|
||||
fee = fee,
|
||||
destination = destinationAddress,
|
||||
),
|
||||
signer = signer,
|
||||
)
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
|
|
@ -28,13 +27,10 @@ import kotlinx.coroutines.flow.firstOrNull
|
|||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultRampManager(
|
||||
private val exchangeService: ExchangeService?,
|
||||
private val buyService: Provider<ExchangeService>,
|
||||
private val sellService: Provider<ExchangeService>,
|
||||
private val expressServiceLoader: ExpressServiceLoader,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val onrampFeatureToggles: OnrampFeatureToggles,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : RampStateManager {
|
||||
|
||||
|
|
@ -45,9 +41,9 @@ internal class DefaultRampManager(
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): ScenarioUnavailabilityReason {
|
||||
val availabilityState = runCatching {
|
||||
getOnrampAvailableState(scanResponse, userWalletId, cryptoCurrency)
|
||||
}.getOrNull() ?: ExpressAvailabilityState.Error
|
||||
val availabilityState = runCatching { getOnrampAvailableState(userWalletId, cryptoCurrency) }
|
||||
.getOrNull()
|
||||
?: ExpressAvailabilityState.Error
|
||||
|
||||
return availabilityState.toReason(cryptoCurrency.name)
|
||||
}
|
||||
|
|
@ -62,7 +58,7 @@ internal class DefaultRampManager(
|
|||
block = {
|
||||
val serviceCurrency = cryptoCurrencyConverter.convertBack(status.currency)
|
||||
|
||||
exchangeService?.availableForSell(currency = serviceCurrency) == true
|
||||
sellService().availableForSell(currency = serviceCurrency)
|
||||
},
|
||||
catch = { raise(ScenarioUnavailabilityReason.NotSupportedBySellService(status.currency.name)) },
|
||||
)
|
||||
|
|
@ -100,16 +96,6 @@ internal class DefaultRampManager(
|
|||
return availabilityState.toReason(cryptoCurrency.name)
|
||||
}
|
||||
|
||||
override fun getBuyInitializationStatus(): Flow<ExchangeServiceInitializationStatus> {
|
||||
return buyService.invoke().initializationStatus
|
||||
}
|
||||
|
||||
override suspend fun fetchBuyServiceData() {
|
||||
runCatching(dispatchers.io) {
|
||||
buyService.invoke().update()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSellInitializationStatus(): Flow<ExchangeServiceInitializationStatus> {
|
||||
return sellService.invoke().initializationStatus
|
||||
}
|
||||
|
|
@ -163,31 +149,20 @@ internal class DefaultRampManager(
|
|||
}
|
||||
|
||||
private suspend fun getOnrampAvailableState(
|
||||
scanResponse: ScanResponse,
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): ExpressAvailabilityState {
|
||||
return when {
|
||||
onrampFeatureToggles.isFeatureEnabled -> {
|
||||
val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull()
|
||||
?: return ExpressAvailabilityState.Loading
|
||||
return when (asset) {
|
||||
is Lce.Error -> ExpressAvailabilityState.Error
|
||||
is Lce.Loading -> ExpressAvailabilityState.Loading
|
||||
is Lce.Content -> {
|
||||
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) }
|
||||
foundAsset?.onrampAvailable?.toOnrampAvailabilityState()
|
||||
?: ExpressAvailabilityState.AssetNotFound
|
||||
}
|
||||
}
|
||||
val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull()
|
||||
?: return ExpressAvailabilityState.Loading
|
||||
|
||||
return when (asset) {
|
||||
is Lce.Error -> ExpressAvailabilityState.Error
|
||||
is Lce.Loading -> ExpressAvailabilityState.Loading
|
||||
is Lce.Content -> {
|
||||
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) }
|
||||
foundAsset?.onrampAvailable?.toOnrampAvailabilityState()
|
||||
?: ExpressAvailabilityState.AssetNotFound
|
||||
}
|
||||
exchangeService != null -> {
|
||||
exchangeService.availableForBuy(
|
||||
scanResponse = scanResponse,
|
||||
currency = cryptoCurrencyConverter.convertBack(cryptoCurrency),
|
||||
).toOnrampAvailabilityState()
|
||||
}
|
||||
else -> ExpressAvailabilityState.Error
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,75 +1,26 @@
|
|||
package com.tangem.tap.network.exchangeServices
|
||||
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
typealias ExchangeServiceInitializationStatus = Lce<Throwable, Any>
|
||||
|
||||
interface Exchanger {
|
||||
fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean
|
||||
fun availableForSell(currency: Currency): Boolean
|
||||
}
|
||||
|
||||
interface ExchangeService : Exchanger, ExchangeUrlBuilder {
|
||||
interface ExchangeService {
|
||||
|
||||
val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||
|
||||
suspend fun update()
|
||||
|
||||
companion object {
|
||||
fun dummy(): ExchangeService = object : ExchangeService {
|
||||
fun availableForSell(currency: Currency): Boolean
|
||||
|
||||
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus> =
|
||||
MutableStateFlow(value = lceLoading())
|
||||
|
||||
override suspend fun update() {}
|
||||
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false
|
||||
override fun availableForSell(currency: Currency): Boolean = false
|
||||
override fun getUrl(
|
||||
action: CurrencyExchangeManager.Action,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
fiatCurrencyName: String,
|
||||
walletAddress: String,
|
||||
isDarkTheme: Boolean,
|
||||
): String? = null
|
||||
|
||||
override fun getSellCryptoReceiptUrl(
|
||||
action: CurrencyExchangeManager.Action,
|
||||
transactionId: String,
|
||||
): String? = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ExchangeRules : Exchanger {
|
||||
|
||||
companion object {
|
||||
fun dummy(): ExchangeRules = object : ExchangeRules {
|
||||
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false
|
||||
override fun availableForSell(currency: Currency): Boolean = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ExchangeUrlBuilder {
|
||||
@Suppress("LongParameterList")
|
||||
fun getUrl(
|
||||
action: CurrencyExchangeManager.Action,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
fiatCurrencyName: String,
|
||||
walletAddress: String,
|
||||
isDarkTheme: Boolean,
|
||||
): String?
|
||||
|
||||
fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String?
|
||||
|
||||
companion object {
|
||||
const val SCHEME = "https"
|
||||
const val SUCCESS_URL = "https://tangem.com/success"
|
||||
}
|
||||
fun getSellCryptoReceiptUrl(transactionId: String): String?
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package com.tangem.tap.network.exchangeServices.mercuryo
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
|
||||
interface MercuryoApi {
|
||||
|
||||
@GET("{apiVersion}/lib/currencies")
|
||||
suspend fun currencies(@Path("apiVersion") apiVersion: String): MercuryoCurrenciesResponse
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MercuryoCurrenciesResponse(
|
||||
@Json(name = "status") val status: Int,
|
||||
@Json(name = "data") val data: Data,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
@Json(name = "fiat") val fiat: List<String>,
|
||||
@Json(name = "crypto") val crypto: List<String>,
|
||||
@Json(name = "config") val config: Config,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Config(
|
||||
@Json(name = "crypto_currencies")
|
||||
val cryptoCurrencies: List<MercuryoCryptoCurrency>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MercuryoCryptoCurrency(
|
||||
@Json(name = "currency")
|
||||
val currencySymbol: String,
|
||||
@Json(name = "network")
|
||||
val network: String,
|
||||
@Json(name = "contract")
|
||||
val contractAddress: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package com.tangem.tap.network.exchangeServices.mercuryo
|
||||
|
||||
import com.tangem.datasource.api.common.createRetrofitInstance
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class MercuryoEnvironment(
|
||||
val baseUrl: String,
|
||||
val apiVersion: String,
|
||||
val widgetId: String,
|
||||
val secret: String,
|
||||
val mercuryoApi: MercuryoApi,
|
||||
) {
|
||||
companion object {
|
||||
private const val BASE_URL = "https://api.mercuryo.io/"
|
||||
private const val API_VERSION = "v1.6"
|
||||
|
||||
fun prod(widgetId: String, secret: String, apiVersion: String = API_VERSION): MercuryoEnvironment {
|
||||
return MercuryoEnvironment(
|
||||
baseUrl = BASE_URL,
|
||||
apiVersion = apiVersion,
|
||||
widgetId = widgetId,
|
||||
secret = secret,
|
||||
mercuryoApi = createRetrofitInstance(
|
||||
baseUrl = BASE_URL,
|
||||
logEnabled = false,
|
||||
).create(MercuryoApi::class.java),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
package com.tangem.tap.network.exchangeServices.mercuryo
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.extensions.calculateSha512
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.common.services.performRequest
|
||||
import com.tangem.data.onramp.legacy.mercuryoNetwork
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class MercuryoService(private val environment: MercuryoEnvironment) : ExchangeService {
|
||||
|
||||
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||
get() = _initializationStatus
|
||||
|
||||
private val _initializationStatus: MutableStateFlow<ExchangeServiceInitializationStatus> =
|
||||
MutableStateFlow(value = lceLoading())
|
||||
|
||||
private val api: MercuryoApi = environment.mercuryoApi
|
||||
|
||||
private val availableMercuryoCurrencies = CopyOnWriteArrayList<MercuryoCurrenciesResponse.MercuryoCryptoCurrency>()
|
||||
|
||||
override suspend fun update() {
|
||||
Timber.i("Start updating")
|
||||
_initializationStatus.value = lceLoading()
|
||||
|
||||
val result = performRequest { api.currencies(environment.apiVersion) }
|
||||
when {
|
||||
result is Result.Success && result.data.status == RESPONSE_SUCCESS_STATUS_CODE -> {
|
||||
handleSuccessfullyUpdatedData(data = result.data.data)
|
||||
}
|
||||
result is Result.Failure -> {
|
||||
availableMercuryoCurrencies.clear()
|
||||
|
||||
Timber.e("Failed to load currencies", result.error)
|
||||
_initializationStatus.value = result.error.lceError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean {
|
||||
val mercuryoNetwork = currency.blockchain.mercuryoNetwork
|
||||
val contractAddress = (currency as? Currency.Token)?.token?.contractAddress ?: ""
|
||||
val availableCurrency = availableMercuryoCurrencies.firstOrNull {
|
||||
it.currencySymbol == currency.currencySymbol &&
|
||||
it.network == mercuryoNetwork &&
|
||||
it.contractAddress.equals(contractAddress, ignoreCase = true)
|
||||
}
|
||||
return availableCurrency != null
|
||||
}
|
||||
|
||||
override fun availableForSell(currency: Currency): Boolean = false
|
||||
|
||||
override fun getUrl(
|
||||
action: CurrencyExchangeManager.Action,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
fiatCurrencyName: String,
|
||||
walletAddress: String,
|
||||
isDarkTheme: Boolean,
|
||||
): String {
|
||||
if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException()
|
||||
|
||||
val blockchain = cryptoCurrency.network.toBlockchain()
|
||||
|
||||
val builder = Uri.Builder()
|
||||
.scheme(ExchangeUrlBuilder.SCHEME)
|
||||
.authority("exchange.mercuryo.io")
|
||||
.appendQueryParameter("widget_id", environment.widgetId)
|
||||
.appendQueryParameter("type", action.name.lowercase())
|
||||
.appendQueryParameter("currency", cryptoCurrency.symbol)
|
||||
.appendQueryParameter("address", walletAddress)
|
||||
.appendQueryParameter("signature", signature(walletAddress))
|
||||
.appendQueryParameter("fix_currency", "true")
|
||||
.appendQueryParameter("redirect_url", ExchangeUrlBuilder.SUCCESS_URL)
|
||||
if (isDarkTheme) builder.appendQueryParameter("theme", "1inch")
|
||||
|
||||
blockchain.mercuryoNetwork?.let {
|
||||
builder.appendQueryParameter("network", it)
|
||||
}
|
||||
|
||||
return builder.build().toString()
|
||||
}
|
||||
|
||||
override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null
|
||||
|
||||
private fun handleSuccessfullyUpdatedData(data: MercuryoCurrenciesResponse.Data) {
|
||||
availableMercuryoCurrencies.clear()
|
||||
availableMercuryoCurrencies.addAll(data.config.cryptoCurrencies)
|
||||
|
||||
Timber.i("Successfully updated")
|
||||
_initializationStatus.value = lceContent()
|
||||
}
|
||||
|
||||
private fun signature(address: String) = (address + environment.secret).calculateSha512().toHexString().lowercase()
|
||||
|
||||
private companion object {
|
||||
const val RESPONSE_SUCCESS_STATUS_CODE = 200
|
||||
}
|
||||
}
|
||||
|
|
@ -6,17 +6,16 @@ import com.tangem.blockchainsdk.utils.toBlockchain
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.common.services.performRequest
|
||||
import com.tangem.datasource.api.common.createRetrofitInstance
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.common.extensions.withIOContext
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME
|
||||
import com.tangem.tap.network.exchangeServices.moonpay.models.MoonPayAvailableCurrency
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -28,6 +27,7 @@ class MoonPayService(
|
|||
private val apiKey: String,
|
||||
private val secretKey: String,
|
||||
private val logEnabled: Boolean,
|
||||
private val cardProvider: () -> CardDTO?,
|
||||
) : ExchangeService {
|
||||
|
||||
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||
|
|
@ -102,9 +102,12 @@ class MoonPayService(
|
|||
}
|
||||
}
|
||||
|
||||
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false
|
||||
|
||||
override fun availableForSell(currency: Currency): Boolean {
|
||||
val card = cardProvider() ?: return false
|
||||
val checkCardExchange = !card.isStart2Coin
|
||||
|
||||
if (!checkCardExchange) return false
|
||||
|
||||
if (!isSellAllowed()) return false
|
||||
|
||||
val availableForSell = status?.availableForSell ?: return false
|
||||
|
|
@ -125,15 +128,14 @@ class MoonPayService(
|
|||
}
|
||||
|
||||
override fun getUrl(
|
||||
action: CurrencyExchangeManager.Action,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
fiatCurrencyName: String,
|
||||
walletAddress: String,
|
||||
isDarkTheme: Boolean,
|
||||
): String? {
|
||||
if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException()
|
||||
|
||||
val blockchain = cryptoCurrency.network.toBlockchain()
|
||||
if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl()
|
||||
|
||||
val supportedCurrency = blockchain.moonPaySupportedCurrency ?: return null
|
||||
val moonpayCurrency = status?.availableForSell?.firstOrNull {
|
||||
when (cryptoCurrency) {
|
||||
|
|
@ -165,7 +167,7 @@ class MoonPayService(
|
|||
return uri.build().toString()
|
||||
}
|
||||
|
||||
override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String {
|
||||
override fun getSellCryptoReceiptUrl(transactionId: String): String {
|
||||
return Uri.Builder()
|
||||
.scheme(SCHEME)
|
||||
.authority(URL_SELL)
|
||||
|
|
@ -185,8 +187,9 @@ class MoonPayService(
|
|||
return status?.responseUserStatus?.isSellAllowed ?: false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private companion object {
|
||||
const val URL_SELL = "sell.moonpay.com"
|
||||
const val SCHEME = "https"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import javax.inject.Inject
|
|||
class AppStateHolder @Inject constructor() : ReduxStateHolder {
|
||||
|
||||
var mainStore: Store<AppState>? = null
|
||||
var exchangeService: ExchangeService? = null
|
||||
var buyService: ExchangeService? = null
|
||||
var sellService: ExchangeService? = null
|
||||
|
||||
override fun dispatch(action: Action) {
|
||||
|
|
|
|||
|
|
@ -32,9 +32,7 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
|||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
|
|
@ -68,7 +66,6 @@ data class DaggerGraphState(
|
|||
val shareManager: ShareManager? = null,
|
||||
val appRouter: AppRouter? = null,
|
||||
val transactionSignerFactory: TransactionSignerFactory? = null,
|
||||
val onrampFeatureToggles: OnrampFeatureToggles? = null,
|
||||
val environmentConfigStorage: EnvironmentConfigStorage? = null,
|
||||
val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null,
|
||||
val onboardingRepository: OnboardingRepository? = null,
|
||||
|
|
@ -77,7 +74,6 @@ data class DaggerGraphState(
|
|||
val clipboardManager: ClipboardManager? = null,
|
||||
val settingsManager: SettingsManager? = null,
|
||||
val uiMessageSender: UiMessageSender? = null,
|
||||
val onlineCardVerifier: OnlineCardVerifier? = null,
|
||||
val cardArworksProvider: CardArtworksProvider? = null,
|
||||
val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory? = null,
|
||||
val userTokensResponseStore: UserTokensResponseStore? = null,
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ internal class ProxyAppRouter(
|
|||
try {
|
||||
block()
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e)
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.arkivanov.decompose.router.stack.ChildStack
|
|||
import com.arkivanov.decompose.router.stack.childStack
|
||||
import com.arkivanov.decompose.value.Value
|
||||
import com.arkivanov.decompose.value.subscribe
|
||||
import com.arkivanov.essenty.lifecycle.subscribe
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
|
|
@ -16,7 +17,10 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.features.walletconnect.components.WcRoutingComponent
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.hot.sdk.android.create
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.features.hot.TangemHotSDKProxy
|
||||
import com.tangem.tap.routing.RootContent
|
||||
import com.tangem.tap.routing.component.RoutingComponent
|
||||
import com.tangem.tap.routing.component.RoutingComponent.Child
|
||||
|
|
@ -36,6 +40,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
private val uiDependencies: UiDependencies,
|
||||
private val wcRoutingComponentFactory: WcRoutingComponent.Factory,
|
||||
private val deeplinkFactory: DeepLinkFactory,
|
||||
private val tangemHotSDKProxy: TangemHotSDKProxy,
|
||||
) : RoutingComponent,
|
||||
AppComponentContext by context,
|
||||
SnackbarHandler {
|
||||
|
|
@ -70,6 +75,8 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
appRouterConfig.stack = stackItems
|
||||
}
|
||||
}
|
||||
|
||||
configureHotSdk()
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -120,6 +127,17 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
initialStack
|
||||
}
|
||||
|
||||
private fun configureHotSdk() {
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
tangemHotSDKProxy.sdkState.value = TangemHotSdk.create(activity)
|
||||
},
|
||||
onDestroy = {
|
||||
tangemHotSDKProxy.sdkState.value = null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : RoutingComponent.Factory {
|
||||
override fun create(context: AppComponentContext, initialStack: List<AppRoute>?): DefaultRoutingComponent
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import com.tangem.features.send.v2.api.NFTSendComponent
|
|||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.staking.api.StakingComponent
|
||||
import com.tangem.features.swap.SwapComponent
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import com.tangem.features.tokendetails.TokenDetailsComponent
|
||||
import com.tangem.features.wallet.WalletEntryComponent
|
||||
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
|
||||
|
|
@ -86,7 +85,6 @@ internal class ChildFactory @Inject constructor(
|
|||
private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory,
|
||||
private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory,
|
||||
private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory,
|
||||
private val testerRouter: TesterRouter,
|
||||
private val walletConnectFeatureToggles: WalletConnectFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -132,9 +130,6 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = welcomeComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.TesterMenu -> {
|
||||
Child.LegacyIntent(testerRouter.getEntryIntent())
|
||||
}
|
||||
is AppRoute.WalletSettings -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import com.tangem.data.card.sdk.CardSdkProvider
|
|||
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
|
||||
import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
|
||||
import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.*
|
||||
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.SellDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.SwapDeepLinkHandler
|
||||
import com.tangem.features.send.v2.api.deeplink.SellRedirectDeepLinkHandler
|
||||
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
|
||||
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
||||
|
|
@ -33,7 +36,6 @@ internal class DeepLinkFactory @Inject constructor(
|
|||
private val cardSdkProvider: CardSdkProvider,
|
||||
private val onrampDeepLink: OnrampDeepLinkHandler.Factory,
|
||||
private val sellRedirectDeepLink: SellRedirectDeepLinkHandler.Factory,
|
||||
private val buyRedirectDeepLink: BuyRedirectDeepLinkHandler.Factory,
|
||||
private val referralDeepLink: ReferralDeepLinkHandler.Factory,
|
||||
private val walletConnectDeepLink: WalletConnectDeepLinkHandler.Factory,
|
||||
private val walletDeepLink: WalletDeepLinkHandler.Factory,
|
||||
|
|
@ -114,7 +116,7 @@ internal class DeepLinkFactory @Inject constructor(
|
|||
when (deeplinkUri.host) {
|
||||
DeepLinkRoute.Onramp.host -> onrampDeepLink.create(coroutineScope, queryParams)
|
||||
DeepLinkRoute.SellRedirect.host -> sellRedirectDeepLink.create(coroutineScope, queryParams)
|
||||
DeepLinkRoute.BuyRedirect.host -> buyRedirectDeepLink.create(coroutineScope)
|
||||
DeepLinkRoute.BuyRedirect.host -> Unit
|
||||
DeepLinkRoute.Referral.host -> referralDeepLink.create()
|
||||
DeepLinkRoute.Wallet.host -> walletDeepLink.create()
|
||||
DeepLinkRoute.TokenDetails.host -> tokenDetailsDeepLink.create(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ import com.tangem.data.card.sdk.CardSdkProvider
|
|||
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
|
||||
import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
|
||||
import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.*
|
||||
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.SellDeepLinkHandler
|
||||
import com.tangem.features.onramp.deeplink.SwapDeepLinkHandler
|
||||
import com.tangem.features.send.v2.api.deeplink.SellRedirectDeepLinkHandler
|
||||
import com.tangem.features.staking.api.deeplink.StakingDeepLinkHandler
|
||||
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
||||
|
|
@ -34,9 +37,6 @@ class DeepLinkFactoryTest {
|
|||
private val sellRedirectDeepLinkFactory = mockk<SellRedirectDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any(), any()) } returns mockk()
|
||||
}
|
||||
private val buyRedirectDeepLinkFactory = mockk<BuyRedirectDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create(any()) } returns mockk()
|
||||
}
|
||||
private val referralDeepLinkFactory = mockk<ReferralDeepLinkHandler.Factory>(relaxed = true) {
|
||||
every { create() } returns mockk()
|
||||
}
|
||||
|
|
@ -81,7 +81,6 @@ class DeepLinkFactoryTest {
|
|||
cardSdkProvider = cardSdkProvider,
|
||||
onrampDeepLink = onrampDeepLinkFactory,
|
||||
sellRedirectDeepLink = sellRedirectDeepLinkFactory,
|
||||
buyRedirectDeepLink = buyRedirectDeepLinkFactory,
|
||||
referralDeepLink = referralDeepLinkFactory,
|
||||
walletConnectDeepLink = walletConnectDeepLinkFactory,
|
||||
walletDeepLink = walletDeepLinkFactory,
|
||||
|
|
@ -220,7 +219,6 @@ class DeepLinkFactoryTest {
|
|||
verify(inverse = true) {
|
||||
onrampDeepLinkFactory.create(any(), any())
|
||||
sellRedirectDeepLinkFactory.create(any(), any())
|
||||
buyRedirectDeepLinkFactory.create(any())
|
||||
referralDeepLinkFactory.create()
|
||||
walletConnectDeepLinkFactory.create(any())
|
||||
walletDeepLinkFactory.create()
|
||||
|
|
@ -277,12 +275,6 @@ class DeepLinkFactoryTest {
|
|||
every { mockedUri.queryParameterNames } returns emptySet()
|
||||
every { mockedUri.getQueryParameter(any()) } returns ""
|
||||
|
||||
// Test Buy Redirect
|
||||
every { mockedUri.host } returns "redirect"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
advanceUntilIdle()
|
||||
verify { buyRedirectDeepLinkFactory.create(eq(testScope)) }
|
||||
|
||||
// Test Referral
|
||||
every { mockedUri.host } returns "referral"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
|
|
@ -333,7 +325,6 @@ class DeepLinkFactoryTest {
|
|||
verify(inverse = true) {
|
||||
onrampDeepLinkFactory.create(any(), any())
|
||||
sellRedirectDeepLinkFactory.create(any(), any())
|
||||
buyRedirectDeepLinkFactory.create(any())
|
||||
referralDeepLinkFactory.create()
|
||||
walletConnectDeepLinkFactory.create(any())
|
||||
walletDeepLinkFactory.create()
|
||||
|
|
@ -348,11 +339,10 @@ class DeepLinkFactoryTest {
|
|||
fun `getParams filters malicious parameters`() = runTest {
|
||||
every { mockedUri.scheme } returns "tangem"
|
||||
every { mockedUri.host } returns "onramp"
|
||||
every { mockedUri.query } returns "safe=ok&malicious=%3Cscript%3E"e=O%27Brien"
|
||||
every { mockedUri.queryParameterNames } returns setOf("safe", "malicious", "quote")
|
||||
every { mockedUri.query } returns "safe=ok&malicious=%3Cscript%3E"
|
||||
every { mockedUri.queryParameterNames } returns setOf("safe", "malicious")
|
||||
every { mockedUri.getQueryParameter("safe") } returns "ok"
|
||||
every { mockedUri.getQueryParameter("malicious") } returns "<script>"
|
||||
every { mockedUri.getQueryParameter("quote") } returns "O'Brien"
|
||||
|
||||
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
|
|
@ -389,13 +379,6 @@ class DeepLinkFactoryTest {
|
|||
advanceUntilIdle()
|
||||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||
|
||||
every { mockedUri.query } returns "unsafe=O'Brien"
|
||||
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||
every { mockedUri.getQueryParameter("unsafe") } returns "O'Brien"
|
||||
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
|
||||
advanceUntilIdle()
|
||||
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||
|
||||
every { mockedUri.query } returns "unsafe=test;"
|
||||
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||
every { mockedUri.getQueryParameter("unsafe") } returns "test;"
|
||||
|
|
|
|||
|
|
@ -30,10 +30,6 @@ sealed class AppRoute(val path: String) : Route {
|
|||
) : AppRoute(path = "/welcome"), RouteBundleParams {
|
||||
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
|
||||
companion object {
|
||||
const val INITIAL_INTENT_KEY = "intent"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
@ -177,9 +173,6 @@ sealed class AppRoute(val path: String) : Route {
|
|||
"/$isInitialReverseOrder",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data object TesterMenu : AppRoute(path = "/tester_menu")
|
||||
|
||||
@Serializable
|
||||
data object AppCurrencySelector : AppRoute(path = "/app_currency_selector")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.common.routing
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
|
||||
class RoutingFeatureToggle(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) {
|
||||
|
||||
val isDeepLinkNavigationEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "DEEPLINK_NAVIGATION_ENABLED")
|
||||
}
|
||||
|
|
@ -10,5 +10,6 @@ fun <B> assertEither(actual: Either<Throwable, B>, expected: Either<Throwable, B
|
|||
val expectedError = expected.leftOrNull() ?: error("Actual is Either.Left: $it")
|
||||
|
||||
Truth.assertThat(it::class.java).isEqualTo(expectedError::class.java)
|
||||
Truth.assertThat(it).hasMessageThat().isEqualTo(expectedError.message)
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ internal fun LazyListScope.amountFieldV2(
|
|||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = modifier
|
||||
.padding(top = 48.dp, bottom = 28.dp),
|
||||
) {
|
||||
|
|
@ -165,12 +165,12 @@ private fun AmountInfo(amountUM: AmountState, onMaxAmountClick: () -> Unit, modi
|
|||
.padding(end = 16.dp)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(),
|
||||
onClick = onMaxAmountClick,
|
||||
),
|
||||
)
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@
|
|||
"name": "NEW_CARD_SCANNING_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "ONRAMP_ENABLED",
|
||||
"version": "5.24.2"
|
||||
},
|
||||
{
|
||||
"name": "VISA_ONBOARDING_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -15,18 +11,6 @@
|
|||
"name": "STAKING_TON_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "NFT_ENABLED",
|
||||
"version": "5.25.0"
|
||||
},
|
||||
{
|
||||
"name": "NFT_EVM_ENABLED",
|
||||
"version": "5.25.0"
|
||||
},
|
||||
{
|
||||
"name": "NFT_SOLANA_ENABLED",
|
||||
"version": "5.25.0"
|
||||
},
|
||||
{
|
||||
"name": "NFT_MEDIA_CONTENT_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -35,14 +19,6 @@
|
|||
"name": "STAKING_CARDANO_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "NEW_ARTWORK_LOADING",
|
||||
"version": "5.25.0"
|
||||
},
|
||||
{
|
||||
"name": "NEW_ATTESTATION_ENABLED",
|
||||
"version": "5.24.0"
|
||||
},
|
||||
{
|
||||
"name": "WALLET_CONNECT_REDESIGN_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -51,10 +27,6 @@
|
|||
"name": "STAKING_LOADING_REFACTORING_ENABLED",
|
||||
"version": "5.25.0"
|
||||
},
|
||||
{
|
||||
"name": "DEEPLINK_NAVIGATION_ENABLED",
|
||||
"version": "5.25.0"
|
||||
},
|
||||
{
|
||||
"name": "PUSH_NOTIFICATIONS_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -77,6 +49,6 @@
|
|||
},
|
||||
{
|
||||
"name": "WALLET_BALANCE_FETCHER_ENABLED",
|
||||
"version": "undefined"
|
||||
"version": "5.27.0"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -92,4 +92,5 @@ dependencies {
|
|||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(projects.common.test)
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ sealed class ApiConfig {
|
|||
TangemTech,
|
||||
StakeKit,
|
||||
TangemPay,
|
||||
Attestation,
|
||||
BlockAid,
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +34,6 @@ sealed class ApiConfig {
|
|||
is TangemTech -> ID.TangemTech
|
||||
is StakeKit -> ID.StakeKit
|
||||
is TangemPay -> ID.TangemPay
|
||||
is Attestation -> ID.Attestation
|
||||
is BlockAid -> ID.BlockAid
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ enum class ApiEnvironment {
|
|||
@Json(name = "STAGE")
|
||||
STAGE,
|
||||
|
||||
@Json(name = "MOCK")
|
||||
MOCK,
|
||||
|
||||
@Json(name = "PROD")
|
||||
PROD,
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
/**
|
||||
* Attestation [ApiConfig] that used in CardSDK
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class Attestation : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
|
||||
|
||||
override val environmentConfigs: List<ApiEnvironmentConfig> = listOf(
|
||||
createDevEnvironment(),
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.tangem-tech.com/",
|
||||
)
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import com.tangem.utils.version.AppVersionProvider
|
|||
* @property environmentConfigStorage environment config storage
|
||||
* @property expressAuthProvider express auth provider
|
||||
* @property appVersionProvider app version provider
|
||||
* @property appInfoProvider app info provider
|
||||
*/
|
||||
internal class Express(
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||
|
|
@ -27,9 +28,25 @@ internal class Express(
|
|||
override val environmentConfigs: List<ApiEnvironmentConfig> = listOf(
|
||||
createDevEnvironment(),
|
||||
createStageEnvironment(),
|
||||
createMockedEnvironment(),
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
||||
private fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
DEBUG_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV
|
||||
INTERNAL_BUILD_TYPE,
|
||||
-> ApiEnvironment.STAGE
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.MOCK
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
|
|
@ -42,6 +59,12 @@ internal class Express(
|
|||
headers = createHeaders(isProd = false),
|
||||
)
|
||||
|
||||
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.MOCK,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(isProd = false),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://express.tangem.com/v1/",
|
||||
|
|
@ -63,21 +86,4 @@ internal class Express(
|
|||
?.apiKey
|
||||
?: error("No express config provided")
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
DEBUG_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV
|
||||
INTERNAL_BUILD_TYPE,
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.STAGE
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
|
||||
|
|
@ -7,11 +8,37 @@ internal class TangemPay(
|
|||
private val appVersionProvider: AppVersionProvider,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.DEV
|
||||
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
|
||||
|
||||
override val environmentConfigs = listOf(
|
||||
createProdEnvironment(),
|
||||
createDevEnvironment(),
|
||||
createMockedEnvironment(),
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
||||
private fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
|
||||
DEBUG_BUILD_TYPE,
|
||||
INTERNAL_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.MOCK,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
|
|
@ -20,12 +47,6 @@ internal class TangemPay(
|
|||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createHeaders() = mapOf(
|
||||
"version" to ProviderSuspend { appVersionProvider.versionName },
|
||||
"platform" to ProviderSuspend { "Android" },
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.utils.RequestHeader
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
|
|
@ -12,18 +13,27 @@ internal class TangemTech(
|
|||
private val appInfoProvider: AppInfoProvider,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
|
||||
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
|
||||
|
||||
override val environmentConfigs = listOf(
|
||||
createDevEnvironment(),
|
||||
createStageEnvironment(),
|
||||
createMockedEnvironment(),
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.tangem.org/v1/",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
private fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.MOCK
|
||||
DEBUG_BUILD_TYPE,
|
||||
INTERNAL_BUILD_TYPE,
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
|
|
@ -31,6 +41,24 @@ internal class TangemTech(
|
|||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.STAGE,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.MOCK,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.tangem.org/",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createHeaders() = buildMap {
|
||||
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
|
||||
putAll(from = RequestHeader.AuthenticationHeader(authProvider).values)
|
||||
|
|
|
|||
|
|
@ -20,44 +20,42 @@ import kotlinx.coroutines.flow.*
|
|||
* @property dispatchers coroutine dispatcher provider
|
||||
*/
|
||||
internal class DevApiConfigsManager(
|
||||
apiConfigs: ApiConfigs,
|
||||
private val apiConfigs: ApiConfigs,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : MutableApiConfigsManager {
|
||||
) : MutableApiConfigsManager() {
|
||||
|
||||
override val configs: Flow<Map<ApiConfig, ApiEnvironment>> get() = _apiConfigs
|
||||
override val configs: StateFlow<Map<ApiConfig, ApiEnvironment>>
|
||||
field = MutableStateFlow(value = getInitialConfigs())
|
||||
|
||||
private val _apiConfigs = MutableStateFlow(value = apiConfigs.associateWith { it.defaultEnvironment })
|
||||
|
||||
override val isInitialized: StateFlow<Boolean> get() = _isInitialized.asStateFlow()
|
||||
|
||||
private val _isInitialized = MutableStateFlow(value = false)
|
||||
override val isInitialized: StateFlow<Boolean>
|
||||
field = MutableStateFlow(value = false)
|
||||
|
||||
override fun initialize() {
|
||||
_isInitialized.value = false
|
||||
isInitialized.value = false
|
||||
|
||||
// We can't use appPreferencesStore.getObjectMap as base flow,
|
||||
// because we should keep possibility to work with configs synchronous.
|
||||
// See [getBaseUrl]
|
||||
appPreferencesStore.getObjectMap<ApiEnvironment>(PreferencesKeys.apiConfigsEnvironmentKey)
|
||||
.distinctUntilChanged()
|
||||
.onEach { savedEnvironments ->
|
||||
_apiConfigs.update { apiConfigs ->
|
||||
apiConfigs.mapValues {
|
||||
val (config, currentEnvironment) = it
|
||||
val apiConfigs = configs.value
|
||||
|
||||
savedEnvironments[config.id.name] ?: currentEnvironment
|
||||
}
|
||||
configs.value = apiConfigs.mapValues {
|
||||
val (config, currentEnvironment) = it
|
||||
|
||||
savedEnvironments[config.id.name] ?: currentEnvironment
|
||||
}
|
||||
|
||||
if (!_isInitialized.value) {
|
||||
_isInitialized.value = true
|
||||
if (!isInitialized.value) {
|
||||
isInitialized.value = true
|
||||
}
|
||||
|
||||
notifyListeners(apiConfigs = apiConfigs, savedEnvironments = savedEnvironments)
|
||||
}
|
||||
.launchIn(CoroutineScope(SupervisorJob() + dispatchers.default))
|
||||
}
|
||||
|
||||
override fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig {
|
||||
val apiConfigs = _apiConfigs.value
|
||||
val apiConfigs = configs.value
|
||||
|
||||
val config = apiConfigs.map { it }.firstOrNull { it.key.id == id }?.key
|
||||
?: error("Api config with id [$id] not found")
|
||||
|
|
@ -78,4 +76,56 @@ internal class DevApiConfigsManager(
|
|||
mutablePreferences.setObjectMap(PreferencesKeys.apiConfigsEnvironmentKey, updatedMap)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun changeEnvironment(environment: ApiEnvironment) {
|
||||
val supportedConfigs = apiConfigs
|
||||
.filter { config ->
|
||||
config.environmentConfigs.any { it.environment == environment }
|
||||
}
|
||||
.map { it.id.name }
|
||||
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
val updatedMap = mutablePreferences.getObjectMap<ApiEnvironment>(PreferencesKeys.apiConfigsEnvironmentKey)
|
||||
.mapValues { (configName, currentEnvironment) ->
|
||||
val isEnvSupported = supportedConfigs.contains(configName)
|
||||
|
||||
if (isEnvSupported) environment else currentEnvironment
|
||||
}
|
||||
|
||||
mutablePreferences.setObjectMap(key = PreferencesKeys.apiConfigsEnvironmentKey, value = updatedMap)
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyListeners(
|
||||
apiConfigs: Map<ApiConfig, ApiEnvironment>,
|
||||
savedEnvironments: Map<String, ApiEnvironment>,
|
||||
) {
|
||||
if (registerListeners.isNotEmpty()) {
|
||||
val changedConfigs = apiConfigs.mapNotNull { (config, prevEnvironment) ->
|
||||
val newEnvironment = savedEnvironments[config.id.name] ?: config.defaultEnvironment
|
||||
|
||||
if (prevEnvironment == newEnvironment) return@mapNotNull null
|
||||
|
||||
val environmentConfig = config.environmentConfigs
|
||||
.firstOrNull { it.environment == newEnvironment }
|
||||
?: return@mapNotNull null
|
||||
|
||||
config.id to environmentConfig
|
||||
}
|
||||
|
||||
if (changedConfigs.isNotEmpty()) {
|
||||
registerListeners.forEach { listener ->
|
||||
val changedConfig = changedConfigs.firstOrNull { it.first == listener.id }?.second
|
||||
|
||||
if (changedConfig != null) {
|
||||
listener.onChange(changedConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialConfigs(): Map<ApiConfig, ApiEnvironment> {
|
||||
return apiConfigs.associateWith(ApiConfig::defaultEnvironment)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.datasource.api.common.config.managers
|
||||
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironment
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Implementation of [ApiConfigsManager] in MOCK environment
|
||||
*
|
||||
* @property apiConfigs api configs
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class MockApiConfigsManager(
|
||||
private val apiConfigs: ApiConfigs,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
) : MutableApiConfigsManager() {
|
||||
|
||||
override val configs: StateFlow<Map<ApiConfig, ApiEnvironment>>
|
||||
field = MutableStateFlow(value = getInitialConfigs())
|
||||
|
||||
override val isInitialized: StateFlow<Boolean> = MutableStateFlow(value = true)
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + dispatchers.default)
|
||||
|
||||
override fun initialize() = Unit
|
||||
|
||||
override fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig {
|
||||
val apiConfigs = configs.value
|
||||
|
||||
val (config, currentEnvironment) = apiConfigs.entries.firstOrNull { it.key.id == id }
|
||||
?: error("Api config with id [$id] not found")
|
||||
|
||||
return config.environmentConfigs.firstOrNull { it.environment == currentEnvironment }
|
||||
?: error("Api config with id [$id] doesn't contain environment [$currentEnvironment]")
|
||||
}
|
||||
|
||||
override suspend fun changeEnvironment(id: String, environment: ApiEnvironment) {
|
||||
configs.update { apiConfigs ->
|
||||
val apiConfig = apiConfigs.keys.firstOrNull { it.id.name == id }
|
||||
?: error("Api config with id [$id] not found. Check that ApiConfig with id [$id] was provided into DI")
|
||||
|
||||
apiConfigs + (apiConfig to environment)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun changeEnvironment(environment: ApiEnvironment) {
|
||||
configs.update { apiConfigs ->
|
||||
apiConfigs.mapValues { (config, currentEnvironment) ->
|
||||
val isEnvSupported = config.environmentConfigs.any { it.environment == environment }
|
||||
|
||||
if (isEnvSupported) environment else currentEnvironment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun addListener(listener: ApiConfigEnvChangeListener) {
|
||||
super.addListener(listener)
|
||||
|
||||
configs
|
||||
.map { it.entries.firstOrNull { it.key.id == listener.id } }
|
||||
.filterNotNull()
|
||||
.onEach { (apiConfig, currentEnvironment) ->
|
||||
listener.onChange(
|
||||
environmentConfig = apiConfig.environmentConfigs.first { it.environment == currentEnvironment },
|
||||
)
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
private fun getInitialConfigs(): Map<ApiConfig, ApiEnvironment> {
|
||||
return apiConfigs.associateWith { it.defaultEnvironment }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.datasource.api.common.config.managers
|
|||
|
||||
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 kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
|
|
@ -9,11 +10,41 @@ import kotlinx.coroutines.flow.Flow
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface MutableApiConfigsManager : ApiConfigsManager {
|
||||
abstract class MutableApiConfigsManager : ApiConfigsManager {
|
||||
|
||||
/** Api configs with current [ApiEnvironment] */
|
||||
val configs: Flow<Map<ApiConfig, ApiEnvironment>>
|
||||
abstract val configs: Flow<Map<ApiConfig, ApiEnvironment>>
|
||||
|
||||
/**
|
||||
* A set of listeners registered to observe changes in API environment configurations.
|
||||
* These listeners are notified whenever an environment change occurs.
|
||||
*/
|
||||
protected val registerListeners: Set<ApiConfigEnvChangeListener>
|
||||
field = mutableSetOf<ApiConfigEnvChangeListener>()
|
||||
|
||||
/** Change api environment [environment] by [id] */
|
||||
suspend fun changeEnvironment(id: String, environment: ApiEnvironment)
|
||||
abstract suspend fun changeEnvironment(id: String, environment: ApiEnvironment)
|
||||
|
||||
/** Change api environment [environment] for all configs */
|
||||
abstract suspend fun changeEnvironment(environment: ApiEnvironment)
|
||||
|
||||
/** Adds a [listener] to observe changes in API environment configurations */
|
||||
open fun addListener(listener: ApiConfigEnvChangeListener) {
|
||||
registerListeners += listener
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for observing changes in API environment configurations
|
||||
*
|
||||
* @property id the identifier of the API configuration this listener is associated with
|
||||
*/
|
||||
abstract class ApiConfigEnvChangeListener(val id: ApiConfig.ID) {
|
||||
|
||||
/**
|
||||
* Called when the environment configuration changes
|
||||
*
|
||||
* @param environmentConfig the updated [ApiEnvironmentConfig] for the associated API configuration
|
||||
*/
|
||||
abstract fun onChange(environmentConfig: ApiEnvironmentConfig)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import retrofit2.http.Query
|
|||
interface TangemTechMarketsApi {
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@GET("coins/list")
|
||||
@GET("v1/coins/list")
|
||||
suspend fun getCoinsList(
|
||||
@Query("currency") currency: String,
|
||||
@Query("interval") interval: String,
|
||||
|
|
@ -20,24 +20,24 @@ interface TangemTechMarketsApi {
|
|||
@Query("timestamp") timestamp: Long?,
|
||||
): ApiResponse<TokenMarketListResponse>
|
||||
|
||||
@GET("coins/{coin_id}")
|
||||
@GET("v1/coins/{coin_id}")
|
||||
suspend fun getCoinMarketData(
|
||||
@Path("coin_id") coinId: String,
|
||||
@Query("currency") currency: String,
|
||||
@Query("language") language: String,
|
||||
): ApiResponse<TokenMarketInfoResponse>
|
||||
|
||||
@GET("coins/{coin_id}/history")
|
||||
@GET("v1/coins/{coin_id}/history")
|
||||
suspend fun getCoinChart(
|
||||
@Path("coin_id") coinId: String,
|
||||
@Query("currency") currency: String,
|
||||
@Query("interval") interval: String,
|
||||
): ApiResponse<TokenMarketChartResponse>
|
||||
|
||||
@GET("coins/{coin_id}/exchanges")
|
||||
@GET("v1/coins/{coin_id}/exchanges")
|
||||
suspend fun getCoinExchanges(@Path("coin_id") coinId: String): ApiResponse<TokenMarketExchangesResponse>
|
||||
|
||||
@GET("coins/history_preview")
|
||||
@GET("v1/coins/history_preview")
|
||||
suspend fun getCoinsListCharts(
|
||||
@Query("coin_ids") coinIds: String,
|
||||
@Query("currency") currency: String,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import java.util.concurrent.TimeUnit
|
|||
@Suppress("TooManyFunctions")
|
||||
interface TangemTechApi {
|
||||
|
||||
@GET("coins")
|
||||
@GET("v1/coins")
|
||||
suspend fun getCoins(
|
||||
@Header("Cache-Control") cacheControl: String = "max-age=600",
|
||||
@Query("contractAddress") contractAddress: String? = null,
|
||||
|
|
@ -30,150 +30,150 @@ interface TangemTechApi {
|
|||
@Query("limit") limit: Int? = null,
|
||||
): ApiResponse<CoinsResponse>
|
||||
|
||||
@GET("rates")
|
||||
@GET("v1/rates")
|
||||
suspend fun getRates(@Query("currencyId") currencyId: String, @Query("coinIds") coinIds: String): RatesResponse
|
||||
|
||||
@GET("currencies")
|
||||
@GET("v1/currencies")
|
||||
suspend fun getCurrencyList(
|
||||
@Header("Cache-Control") cacheControl: String = "max-age=600",
|
||||
): ApiResponse<CurrenciesResponse>
|
||||
|
||||
@GET("geo")
|
||||
@GET("v1/geo")
|
||||
suspend fun getUserCountryCode(): GeoResponse
|
||||
|
||||
@GET("user-tokens/{user-id}")
|
||||
@GET("v1/user-tokens/{user-id}")
|
||||
suspend fun getUserTokens(@Path(value = "user-id") userId: String): ApiResponse<UserTokensResponse>
|
||||
|
||||
@PUT("user-tokens/{user-id}")
|
||||
@PUT("v1/user-tokens/{user-id}")
|
||||
suspend fun saveUserTokens(
|
||||
@Path(value = "user-id") userId: String,
|
||||
@Body userTokens: UserTokensResponse,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("user-tokens")
|
||||
@POST("v1/user-tokens")
|
||||
suspend fun markUserWallerWasCreated(@Body body: MarkUserWalletWasCreatedBody): ApiResponse<Unit>
|
||||
|
||||
/** Returns referral status by [walletId] */
|
||||
@GET("referral/{walletId}")
|
||||
@GET("v1/referral/{walletId}")
|
||||
suspend fun getReferralStatus(@Path("walletId") walletId: String): ApiResponse<ReferralResponse>
|
||||
|
||||
/** Make user referral, requires [StartReferralBody] */
|
||||
@POST("referral")
|
||||
@POST("v1/referral")
|
||||
suspend fun startReferral(@Body startReferralBody: StartReferralBody): ApiResponse<ReferralResponse>
|
||||
|
||||
@GET("quotes")
|
||||
@GET("v1/quotes")
|
||||
suspend fun getQuotes(
|
||||
@Query("currencyId") currencyId: String,
|
||||
@Query("coinIds") coinIds: String,
|
||||
@Query("fields") fields: String,
|
||||
): ApiResponse<QuotesResponse>
|
||||
|
||||
@GET("promotion")
|
||||
@GET("v1/promotion")
|
||||
suspend fun getPromotionInfo(
|
||||
@Query("programName") name: String,
|
||||
@Header("Cache-Control") cacheControl: String = "max-age=600",
|
||||
): ApiResponse<PromotionInfoResponse>
|
||||
|
||||
@GET("settings/{wallet_id}")
|
||||
@GET("v1/settings/{wallet_id}")
|
||||
suspend fun getUserTokensSettings(@Path("wallet_id") walletId: String): ApiResponse<UserTokensSettingsResponse>
|
||||
|
||||
@PUT("settings/{wallet_id}")
|
||||
@PUT("v1/settings/{wallet_id}")
|
||||
suspend fun saveUserTokensSettings(
|
||||
@Path("wallet_id") walletId: String,
|
||||
@Body userTokensSettings: UserTokensSettingsResponse,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("user-network-account")
|
||||
@POST("v1/user-network-account")
|
||||
suspend fun createUserNetworkAccount(
|
||||
@Body body: CreateUserNetworkAccountBody,
|
||||
): ApiResponse<CreateUserNetworkAccountResponse>
|
||||
|
||||
@POST("account")
|
||||
@POST("v1/account")
|
||||
suspend fun createUserTokensAccount(
|
||||
@Body body: CreateUserTokensAccountBody,
|
||||
): ApiResponse<UserTokensAccountResponse>
|
||||
|
||||
@PUT("account/{account_id}")
|
||||
@PUT("v1/account/{account_id}")
|
||||
suspend fun updateUserTokensAccount(
|
||||
@Path("account_id") accountId: Int,
|
||||
@Body body: UpdateUserTokensAccountBody,
|
||||
): ApiResponse<UserTokensAccountResponse>
|
||||
|
||||
@PUT("account/{account_id}/archive")
|
||||
@PUT("v1/account/{account_id}/archive")
|
||||
suspend fun archiveUserTokensAccount(@Path("account_id") accountId: Int): ApiResponse<UserTokensAccountResponse>
|
||||
|
||||
@PUT("account/{account_id}/unarchive")
|
||||
@PUT("v1/account/{account_id}/unarchive")
|
||||
suspend fun restoreUserTokensAccount(@Path("account_id") accountId: Int): ApiResponse<UserTokensAccountResponse>
|
||||
|
||||
@GET("features")
|
||||
@GET("v1/features")
|
||||
suspend fun getFeatures(): ApiResponse<FeaturesResponse>
|
||||
|
||||
@ReadTimeout(duration = 5, unit = TimeUnit.SECONDS)
|
||||
@GET("networks/providers")
|
||||
@GET("v1/networks/providers")
|
||||
suspend fun getBlockchainProviders(): Map<String, List<ProviderModel>>
|
||||
|
||||
@GET("seedphrase-notification/{wallet_id}")
|
||||
@GET("v1/seedphrase-notification/{wallet_id}")
|
||||
suspend fun getSeedPhraseNotificationStatus(
|
||||
@Path("wallet_id") walletId: String,
|
||||
): ApiResponse<SeedPhraseNotificationDTO>
|
||||
|
||||
@PUT("seedphrase-notification/{wallet_id}")
|
||||
@PUT("v1/seedphrase-notification/{wallet_id}")
|
||||
suspend fun updateSeedPhraseNotificationStatus(
|
||||
@Path("wallet_id") walletId: String,
|
||||
@Body body: SeedPhraseNotificationDTO,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@GET("seedphrase-notification/{wallet_id}/confirmed")
|
||||
@GET("v1/seedphrase-notification/{wallet_id}/confirmed")
|
||||
suspend fun getSeedPhraseSecondNotificationStatus(
|
||||
@Path("wallet_id") walletId: String,
|
||||
): ApiResponse<SeedPhraseNotificationDTO>
|
||||
|
||||
@PUT("seedphrase-notification/{wallet_id}/confirmed")
|
||||
@PUT("v1/seedphrase-notification/{wallet_id}/confirmed")
|
||||
suspend fun updateSeedPhraseSecondNotificationStatus(
|
||||
@Path("wallet_id") walletId: String,
|
||||
@Body body: SeedPhraseNotificationDTO,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@GET("hot_crypto")
|
||||
@GET("v1/hot_crypto")
|
||||
suspend fun getHotCrypto(@Query("currency") currencyId: String): ApiResponse<HotCryptoResponse>
|
||||
|
||||
@GET("stories/{story_id}")
|
||||
@GET("v1/stories/{story_id}")
|
||||
suspend fun getStoryById(@Path("story_id") storyId: String): ApiResponse<StoryContentResponse>
|
||||
|
||||
// region push notifications
|
||||
@GET("notification/push_notifications_eligible_networks")
|
||||
@GET("v1/notification/push_notifications_eligible_networks")
|
||||
suspend fun getEligibleNetworksForPushNotifications(): ApiResponse<List<CryptoNetworkResponse>>
|
||||
|
||||
@POST("user-wallets/applications/")
|
||||
@POST("v1/user-wallets/applications/")
|
||||
suspend fun createApplicationId(
|
||||
@Body
|
||||
body: NotificationApplicationCreateBody,
|
||||
): ApiResponse<NotificationApplicationIdResponse>
|
||||
|
||||
@PATCH("user-wallets/applications/{application_id}")
|
||||
@PATCH("v1/user-wallets/applications/{application_id}")
|
||||
suspend fun updatePushTokenForApplicationId(
|
||||
@Path("application_id") applicationId: String,
|
||||
@Body body: NotificationApplicationCreateBody,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@PATCH("user-wallets/wallets/{wallet_id}/notify")
|
||||
@PATCH("v1/user-wallets/wallets/{wallet_id}/notify")
|
||||
suspend fun setNotificationsEnabled(@Path("wallet_id") walletId: String, @Body body: WalletBody): ApiResponse<Unit>
|
||||
// endregion
|
||||
|
||||
// region wallets
|
||||
@PATCH("user-wallets/wallets/{wallet_id}")
|
||||
@PATCH("v1/user-wallets/wallets/{wallet_id}")
|
||||
suspend fun updateWallet(@Path("wallet_id") walletId: String, @Body body: WalletBody): ApiResponse<Unit>
|
||||
|
||||
@POST("user-wallets/wallets/create-and-connect-by-appuid/{application_id}")
|
||||
@POST("v1/user-wallets/wallets/create-and-connect-by-appuid/{application_id}")
|
||||
suspend fun associateApplicationIdWithWallets(
|
||||
@Path("application_id") applicationId: String,
|
||||
@Body body: List<WalletIdBody>,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@GET("user-wallets/wallets/{wallet_id}")
|
||||
@GET("v1/user-wallets/wallets/{wallet_id}")
|
||||
suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse<WalletResponse>
|
||||
|
||||
@GET("user-wallets/wallets/by-app/{app_id}")
|
||||
@GET("v1/user-wallets/wallets/by-app/{app_id}")
|
||||
suspend fun getWallets(@Path("app_id") appId: String): ApiResponse<List<WalletResponse>>
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -60,8 +60,4 @@ internal object ApiConfigsModule {
|
|||
fun provideBlockAidConfig(environmentConfigStorage: EnvironmentConfigStorage): ApiConfig {
|
||||
return BlockAid(environmentConfigStorage)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideAttestationConfig(): ApiConfig = Attestation()
|
||||
}
|
||||
|
|
@ -6,18 +6,20 @@ import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
|||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.blockaid.BlockAidApi
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.api.common.config.managers.DevApiConfigsManager
|
||||
import com.tangem.datasource.api.common.config.managers.MockApiConfigsManager
|
||||
import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager
|
||||
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApiV2
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.utils.*
|
||||
|
|
@ -45,7 +47,7 @@ internal object NetworkModule {
|
|||
private const val STAKE_KIT_API_TIMEOUT_SECONDS = 60L
|
||||
|
||||
private val excludedApiForLogging: Set<ApiConfig.ID> = setOf(
|
||||
// ApiConfig.ID.StakeKit,
|
||||
ApiConfig.ID.StakeKit,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -55,10 +57,10 @@ internal object NetworkModule {
|
|||
appPreferencesStore: AppPreferencesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ApiConfigsManager {
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
DevApiConfigsManager(apiConfigs, appPreferencesStore, dispatchers)
|
||||
} else {
|
||||
ProdApiConfigsManager(apiConfigs)
|
||||
return when {
|
||||
BuildConfig.BUILD_TYPE == MOCKED_BUILD_TYPE -> MockApiConfigsManager(apiConfigs, dispatchers)
|
||||
BuildConfig.TESTER_MENU_ENABLED -> DevApiConfigsManager(apiConfigs, appPreferencesStore, dispatchers)
|
||||
else -> ProdApiConfigsManager(apiConfigs)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.chuckerteam.chucker.api.ChuckerInterceptor
|
|||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.SwitchEnvironmentInterceptor
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
||||
import com.tangem.datasource.api.utils.ConnectTimeout
|
||||
|
|
@ -93,7 +94,7 @@ internal fun OkHttpClient.Builder.applyApiConfig(
|
|||
id: ApiConfig.ID,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
): OkHttpClient.Builder {
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED || BuildConfig.BUILD_TYPE == MOCKED_BUILD_TYPE) {
|
||||
addInterceptor(
|
||||
interceptor = SwitchEnvironmentInterceptor(
|
||||
id = id,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class ApiConfigTest {
|
||||
|
||||
@Test
|
||||
fun `all baseUrls ends with slash`() {
|
||||
// Arrange
|
||||
val allBaseUrls = createApiConfigs().flatMap { it.environmentConfigs.map { it.baseUrl } }
|
||||
|
||||
// Actual
|
||||
val actual = allBaseUrls.all { it.endsWith("/") }
|
||||
|
||||
Timber.e(allBaseUrls.joinToString(separator = "\n"))
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
private fun createApiConfigs(): ApiConfigs {
|
||||
return ApiConfig.ID.entries.mapTo(destination = hashSetOf()) {
|
||||
when (it) {
|
||||
ApiConfig.ID.Express -> {
|
||||
Express(
|
||||
environmentConfigStorage = mockk(),
|
||||
expressAuthProvider = mockk(),
|
||||
appVersionProvider = mockk(),
|
||||
appInfoProvider = mockk(),
|
||||
)
|
||||
}
|
||||
ApiConfig.ID.TangemTech -> {
|
||||
TangemTech(
|
||||
appVersionProvider = mockk(),
|
||||
authProvider = mockk(),
|
||||
appInfoProvider = mockk(),
|
||||
)
|
||||
}
|
||||
ApiConfig.ID.StakeKit -> StakeKit(stakeKitAuthProvider = mockk())
|
||||
ApiConfig.ID.TangemPay -> TangemPay(appVersionProvider = mockk())
|
||||
ApiConfig.ID.BlockAid -> BlockAid(configStorage = mockk())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ internal class MockEnvironmentConfigStorage : EnvironmentConfigStorage {
|
|||
private val environmentConfig = EnvironmentConfig(
|
||||
express = ExpressModel(apiKey = EXPRESS_API_KEY, signVerifierPublicKey = "vocibus"),
|
||||
devExpress = ExpressModel(apiKey = EXPRESS_DEV_API_KEY, signVerifierPublicKey = "pellentesque"),
|
||||
blockAidApiKey = BLOCK_AID_API_KEY,
|
||||
)
|
||||
|
||||
override suspend fun initialize() = environmentConfig
|
||||
|
|
@ -24,5 +25,6 @@ internal class MockEnvironmentConfigStorage : EnvironmentConfigStorage {
|
|||
companion object {
|
||||
const val EXPRESS_API_KEY = "express_api_key"
|
||||
const val EXPRESS_DEV_API_KEY = "express_dev_api_key"
|
||||
const val BLOCK_AID_API_KEY = "block_aid_api_key"
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.datasource.api.common.config.managers
|
|||
|
||||
import android.os.Build
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.utils.ProvideTestModels
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.api.common.config.*
|
||||
|
|
@ -10,46 +11,47 @@ import com.tangem.datasource.api.common.config.ApiConfig.Companion.EXTERNAL_BUIL
|
|||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.INTERNAL_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.BLOCK_AID_API_KEY
|
||||
import com.tangem.lib.auth.ExpressAuthProvider
|
||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
private val configManager = MockEnvironmentConfigStorage()
|
||||
private val appVersionProvider = mockk<AppVersionProvider>()
|
||||
private val expressAuthProvider = mockk<ExpressAuthProvider>()
|
||||
private val stakeKitAuthProvider = mockk<StakeKitAuthProvider>()
|
||||
private val appAuthProvider = mockk<AuthProvider>()
|
||||
private val appInfoProvider = mockk<AppInfoProvider>()
|
||||
|
||||
// Don't forget to add new config !!!
|
||||
private val API_CONFIGS = setOf(
|
||||
Express(configManager, expressAuthProvider, appVersionProvider, appInfoProvider),
|
||||
TangemTech(appVersionProvider, appAuthProvider, appInfoProvider),
|
||||
StakeKit(stakeKitAuthProvider),
|
||||
TangemPay(appVersionProvider),
|
||||
)
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@RunWith(Parameterized::class)
|
||||
internal class ProdApiConfigsManagerTest(private val model: Model) {
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class ProdApiConfigsManagerTest {
|
||||
|
||||
private val manager = ProdApiConfigsManager(API_CONFIGS)
|
||||
private val environmentConfigStorage = MockEnvironmentConfigStorage()
|
||||
private val appVersionProvider = mockk<AppVersionProvider>()
|
||||
private val expressAuthProvider = mockk<ExpressAuthProvider>()
|
||||
private val stakeKitAuthProvider = mockk<StakeKitAuthProvider>()
|
||||
private val appAuthProvider = mockk<AuthProvider>()
|
||||
private val appInfoProvider = mockk<AppInfoProvider>()
|
||||
|
||||
@Before
|
||||
private val manager = ProdApiConfigsManager(apiConfigs = createApiConfigs())
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
clearMocks(
|
||||
appVersionProvider,
|
||||
expressAuthProvider,
|
||||
stakeKitAuthProvider,
|
||||
appAuthProvider,
|
||||
appInfoProvider,
|
||||
)
|
||||
|
||||
every { appVersionProvider.versionName } returns VERSION_NAME
|
||||
every { expressAuthProvider.getSessionId() } returns EXPRESS_SESSION_ID
|
||||
every { stakeKitAuthProvider.getApiKey() } returns STAKE_KIT_API_KEY
|
||||
|
|
@ -58,8 +60,9 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
every { appInfoProvider.osVersion } returns "Android 16"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_getEnvironmentConfig() {
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun getEnvironmentConfig(model: TestModel) {
|
||||
val actual = manager.getEnvironmentConfig(id = model.id)
|
||||
|
||||
Truth.assertThat(actual.environment).isEqualTo(model.expected.environment)
|
||||
|
|
@ -69,160 +72,175 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
.isEqualTo(model.expected.headers.mapValues { runBlocking { it.value() } })
|
||||
}
|
||||
|
||||
data class Model(val id: ApiConfig.ID, val expected: ApiEnvironmentConfig)
|
||||
private fun createApiConfigs(): ApiConfigs {
|
||||
return ApiConfig.ID.entries.mapTo(destination = hashSetOf()) {
|
||||
when (it) {
|
||||
ApiConfig.ID.Express -> {
|
||||
Express(
|
||||
environmentConfigStorage = environmentConfigStorage,
|
||||
expressAuthProvider = expressAuthProvider,
|
||||
appVersionProvider = appVersionProvider,
|
||||
appInfoProvider = appInfoProvider,
|
||||
)
|
||||
}
|
||||
ApiConfig.ID.TangemTech -> {
|
||||
TangemTech(
|
||||
appVersionProvider = appVersionProvider,
|
||||
authProvider = appAuthProvider,
|
||||
appInfoProvider = appInfoProvider,
|
||||
)
|
||||
}
|
||||
ApiConfig.ID.StakeKit -> StakeKit(stakeKitAuthProvider = stakeKitAuthProvider)
|
||||
ApiConfig.ID.TangemPay -> TangemPay(appVersionProvider = appVersionProvider)
|
||||
ApiConfig.ID.BlockAid -> BlockAid(configStorage = environmentConfigStorage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
private fun provideTestModels() = ApiConfig.ID.entries.map {
|
||||
when (it) {
|
||||
ApiConfig.ID.Express -> createExpressModel()
|
||||
ApiConfig.ID.TangemTech -> createTangemTechModel()
|
||||
ApiConfig.ID.StakeKit -> createStakeKitModel()
|
||||
ApiConfig.ID.TangemPay -> createTangemPayModel()
|
||||
ApiConfig.ID.BlockAid -> createBlockAidSdkModel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createExpressModel(): TestModel {
|
||||
val environment = when (BuildConfig.BUILD_TYPE) {
|
||||
DEBUG_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV
|
||||
INTERNAL_BUILD_TYPE,
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.STAGE
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
|
||||
return TestModel(
|
||||
id = ApiConfig.ID.Express,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = environment,
|
||||
baseUrl = when (BuildConfig.BUILD_TYPE) {
|
||||
DEBUG_BUILD_TYPE,
|
||||
-> "[REDACTED_ENV_URL]"
|
||||
INTERNAL_BUILD_TYPE,
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> "[REDACTED_ENV_URL]"
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> "https://express.tangem.com/v1/"
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
},
|
||||
headers = mapOf(
|
||||
"api-key" to ProviderSuspend {
|
||||
if (environment == ApiEnvironment.PROD) {
|
||||
MockEnvironmentConfigStorage.EXPRESS_API_KEY
|
||||
} else {
|
||||
MockEnvironmentConfigStorage.EXPRESS_DEV_API_KEY
|
||||
}
|
||||
},
|
||||
"session-id" to ProviderSuspend { EXPRESS_SESSION_ID },
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"system_version" to ProviderSuspend { "Android 16" },
|
||||
"platform" to ProviderSuspend { "android" },
|
||||
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
|
||||
"timezone" to ProviderSuspend {
|
||||
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
|
||||
},
|
||||
"device" to ProviderSuspend { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTangemTechModel(): TestModel {
|
||||
return TestModel(
|
||||
id = ApiConfig.ID.TangemTech,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.tangem.org/",
|
||||
headers = mapOf(
|
||||
"card_id" to ProviderSuspend { APP_CARD_ID },
|
||||
"card_public_key" to ProviderSuspend { APP_CARD_PUBLIC_KEY },
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"platform" to ProviderSuspend { "android" },
|
||||
"system_version" to ProviderSuspend { "Android 16" },
|
||||
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
|
||||
"timezone" to ProviderSuspend {
|
||||
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
|
||||
},
|
||||
"device" to ProviderSuspend { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createStakeKitModel(): TestModel {
|
||||
return TestModel(
|
||||
id = ApiConfig.ID.StakeKit,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.stakek.it/v1/",
|
||||
headers = mapOf(
|
||||
"X-API-KEY" to ProviderSuspend { STAKE_KIT_API_KEY },
|
||||
"accept" to ProviderSuspend { "application/json" },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTangemPayModel(): TestModel {
|
||||
return TestModel(
|
||||
id = ApiConfig.ID.TangemPay,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = mapOf(
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"platform" to ProviderSuspend { "Android" },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createBlockAidSdkModel(): TestModel {
|
||||
return TestModel(
|
||||
id = ApiConfig.ID.BlockAid,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.blockaid.io/v0/",
|
||||
headers = mapOf(
|
||||
"X-API-KEY" to ProviderSuspend { BLOCK_AID_API_KEY },
|
||||
"accept" to ProviderSuspend { "application/json" },
|
||||
"content-type" to ProviderSuspend { "application/json" },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.checkHeaderValueOrEmpty(): String {
|
||||
for (i in this.indices) {
|
||||
val c = this[i]
|
||||
val charCondition = c == '\t' || c in '\u0020'..'\u007e'
|
||||
if (!charCondition) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
data class TestModel(val id: ApiConfig.ID, val expected: ApiEnvironmentConfig)
|
||||
|
||||
private companion object {
|
||||
|
||||
const val VERSION_NAME = "debug"
|
||||
const val EXPRESS_USER_ID = "express_user_id"
|
||||
const val EXPRESS_SESSION_ID = "express_session_id"
|
||||
const val EXPRESS_REF_CODE = "express_ref_code"
|
||||
const val STAKE_KIT_API_KEY = "stake_kit_api_key"
|
||||
const val APP_CARD_ID = "app_card_id"
|
||||
const val APP_CARD_PUBLIC_KEY = "app_public_key"
|
||||
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters
|
||||
fun data(): Collection<Model> = API_CONFIGS.map {
|
||||
when (it) {
|
||||
is Express -> createExpressModel()
|
||||
is TangemTech -> createTangemTechModel()
|
||||
is StakeKit -> createStakeKitModel()
|
||||
is TangemPay -> createVisaModel()
|
||||
is Attestation -> createAttestationModel()
|
||||
is BlockAid -> createBlockAidSdkModel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createExpressModel(): Model {
|
||||
val environment = when (BuildConfig.BUILD_TYPE) {
|
||||
DEBUG_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV
|
||||
INTERNAL_BUILD_TYPE,
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.STAGE
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
|
||||
return Model(
|
||||
id = ApiConfig.ID.Express,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = environment,
|
||||
baseUrl = when (BuildConfig.BUILD_TYPE) {
|
||||
DEBUG_BUILD_TYPE,
|
||||
-> "[REDACTED_ENV_URL]"
|
||||
INTERNAL_BUILD_TYPE,
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> "[REDACTED_ENV_URL]"
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> "https://express.tangem.com/v1/"
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
},
|
||||
headers = mapOf(
|
||||
"api-key" to ProviderSuspend {
|
||||
if (environment == ApiEnvironment.PROD) {
|
||||
MockEnvironmentConfigStorage.EXPRESS_API_KEY
|
||||
} else {
|
||||
MockEnvironmentConfigStorage.EXPRESS_DEV_API_KEY
|
||||
}
|
||||
},
|
||||
"session-id" to ProviderSuspend { EXPRESS_SESSION_ID },
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"system_version" to ProviderSuspend { "Android 16" },
|
||||
"platform" to ProviderSuspend { "android" },
|
||||
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
|
||||
"timezone" to ProviderSuspend {
|
||||
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
|
||||
},
|
||||
"device" to ProviderSuspend { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTangemTechModel(): Model {
|
||||
return Model(
|
||||
id = ApiConfig.ID.TangemTech,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.tangem.org/v1/",
|
||||
headers = mapOf(
|
||||
"card_id" to ProviderSuspend { APP_CARD_ID },
|
||||
"card_public_key" to ProviderSuspend { APP_CARD_PUBLIC_KEY },
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"platform" to ProviderSuspend { "android" },
|
||||
"system_version" to ProviderSuspend { "Android 16" },
|
||||
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
|
||||
"timezone" to ProviderSuspend {
|
||||
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()
|
||||
},
|
||||
"device" to ProviderSuspend { "${Build.MANUFACTURER} ${Build.MODEL}".checkHeaderValueOrEmpty() },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createStakeKitModel(): Model {
|
||||
return Model(
|
||||
id = ApiConfig.ID.StakeKit,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.stakek.it/v1/",
|
||||
headers = mapOf(
|
||||
"X-API-KEY" to ProviderSuspend { STAKE_KIT_API_KEY },
|
||||
"accept" to ProviderSuspend { "application/json" },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createVisaModel(): Model {
|
||||
return Model(
|
||||
id = ApiConfig.ID.TangemPay,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = mapOf(
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"platform" to ProviderSuspend { "Android" },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createAttestationModel(): Model {
|
||||
return Model(
|
||||
id = ApiConfig.ID.Attestation,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.tangem-tech.com/",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createBlockAidSdkModel(): Model {
|
||||
return Model(
|
||||
id = ApiConfig.ID.BlockAid,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.blockaid.io/v0/",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.checkHeaderValueOrEmpty(): String {
|
||||
for (i in this.indices) {
|
||||
val c = this[i]
|
||||
val charCondition = c == '\t' || c in '\u0020'..'\u007e'
|
||||
if (!charCondition) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.core.decompose.utils
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.arkivanov.essenty.instancekeeper.getOrCreateSimple
|
||||
import com.arkivanov.essenty.lifecycle.subscribe
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class ActivityInstanceHolder<T : Any> {
|
||||
private var instance: T? = null
|
||||
|
||||
lateinit var instanceAccess: WeakReference<T>
|
||||
private set
|
||||
|
||||
fun set(instance: T) {
|
||||
this.instance = instance
|
||||
instanceAccess = WeakReference(instance)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
instance = null
|
||||
instanceAccess.clear()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> AppComponentContext.getOrCreateActivityInstanceHolder(
|
||||
noinline factory: (AppCompatActivity) -> T,
|
||||
): ActivityInstanceHolder<T> {
|
||||
val holder = instanceKeeper.getOrCreateSimple {
|
||||
ActivityInstanceHolder<T>()
|
||||
}
|
||||
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
holder.set(factory(activity))
|
||||
},
|
||||
onDestroy = {
|
||||
holder.clear()
|
||||
},
|
||||
)
|
||||
|
||||
return holder
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.core.deeplink.global
|
||||
|
||||
import com.tangem.core.deeplink.DeepLink
|
||||
|
||||
@Deprecated("Use BuyDeepLinkHandler")
|
||||
class BuyCurrencyDeepLink(
|
||||
val onReceive: () -> Unit,
|
||||
) : DeepLink() {
|
||||
|
||||
override val uri = BUY_REDIRECT_DEEPLINK
|
||||
|
||||
override fun onReceive(params: Map<String, String>) {
|
||||
onReceive()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BUY_REDIRECT_DEEPLINK = "tangem://redirect?action=dismissBrowser"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.core.deeplink.global
|
||||
|
||||
import com.tangem.core.deeplink.DeepLink
|
||||
|
||||
@Deprecated("Use ReferralDeepLinkHandler")
|
||||
class ReferralDeepLink(
|
||||
val onReceive: () -> Unit,
|
||||
) : DeepLink(shouldHandleDelayed = true) {
|
||||
override val uri: String = "tangem://referral"
|
||||
|
||||
override fun onReceive(params: Map<String, String>) {
|
||||
onReceive()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package com.tangem.core.deeplink.global
|
||||
|
||||
import com.tangem.core.deeplink.DeepLink
|
||||
|
||||
@Deprecated("Use SellDeepLinkHandler")
|
||||
class SellCurrencyDeepLink(
|
||||
val onReceive: (data: Data) -> Unit,
|
||||
shouldHandleDelayed: Boolean,
|
||||
) : DeepLink(shouldHandleDelayed) {
|
||||
|
||||
override val uri: String = "tangem://redirect_sell"
|
||||
|
||||
override fun onReceive(params: Map<String, String>) {
|
||||
val data = Data(
|
||||
transactionId = params["transactionId"] ?: return,
|
||||
baseCurrencyAmount = params["baseCurrencyAmount"] ?: return,
|
||||
depositWalletAddress = params["depositWalletAddress"] ?: return,
|
||||
currencyId = params["currency_id"] ?: return,
|
||||
depositWalletAddressTag = params["depositWalletAddressTag"],
|
||||
)
|
||||
|
||||
onReceive(data)
|
||||
}
|
||||
|
||||
data class Data(
|
||||
val transactionId: String,
|
||||
val baseCurrencyAmount: String,
|
||||
val depositWalletAddress: String,
|
||||
val currencyId: String,
|
||||
val depositWalletAddressTag: String?,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.core.deeplink.utils
|
||||
|
||||
import com.arkivanov.essenty.lifecycle.subscribe
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.deeplink.DeepLink
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
|
||||
fun AppComponentContext.registerDeepLinks(registry: DeepLinksRegistry, vararg deepLinks: DeepLink) {
|
||||
registerDeepLinks(registry, deepLinks.toList())
|
||||
}
|
||||
|
||||
fun AppComponentContext.registerDeepLinks(registry: DeepLinksRegistry, deepLinks: Collection<DeepLink>) {
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
registry.register(deepLinks)
|
||||
},
|
||||
onDestroy = {
|
||||
registry.unregister(deepLinks)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="access_code_alert_skip_description">Ohne Zugangscode ist Deine Wallet nicht geschützt.</string>
|
||||
<string name="access_code_alert_skip_ok">Trotzdem überspringen</string>
|
||||
<string name="access_code_alert_skip_title">Zugangscode nicht festgelegt</string>
|
||||
<string name="access_code_check_title">Zugangscode eingeben</string>
|
||||
<string name="access_code_confirm_description">Bestätige Deinen zuvor eingegebenen Code, um fortzufahren</string>
|
||||
<string name="access_code_confirm_title">Zugangscode erneut eingeben</string>
|
||||
<string name="access_code_create_description">Lege einen %s -stelligen Zugangscode fest, um Deine Wallet zu entsperren.</string>
|
||||
<string name="access_code_create_title">Zugangscode erstellen</string>
|
||||
<string name="access_code_navtitle">Zugangscode</string>
|
||||
<string name="action_buttons_buy_empty_search_message">Du kannst Deinen Token nicht finden? Gehe zum Bereich „Markt“ auf der Hauptseite und fügen diesen zum kaufen im Portfolio hinzu.</string>
|
||||
<string name="action_buttons_sell_empty_search_message">Du kannst Deinen Token nicht finden? Gehe zum Bereich „Markt“ auf der Hauptseite und fügen diesen zum verkaufen im Portfolio hinzu.</string>
|
||||
<string name="action_buttons_sell_navigation_bar_title">Verkaufen</string>
|
||||
|
|
@ -49,6 +58,19 @@
|
|||
<string name="app_settings_theme_mode_system">Systemstandard</string>
|
||||
<string name="app_settings_theme_selector_title">Thema</string>
|
||||
<string name="app_settings_title">App Einstellungen</string>
|
||||
<string name="backup_complete_description">Du hast Deine Wallet erfolgreich gesichert.</string>
|
||||
<string name="backup_complete_seed_description">Diese Wörter können bei Verlust nicht wiederhergestellt werden. Bewahre diese an einem sicheren Ort auf.</string>
|
||||
<string name="backup_complete_title">Sicherung abgeschlossen</string>
|
||||
<string name="backup_info_description">Deine geheime Wiederherstellungsphrase ist ein fester Satz von %s Zufällige Wörter, die für den Zugriff auf und die Wiederherstellung Deiner Wallet verwendet werden.</string>
|
||||
<string name="backup_info_keep_description">Diese Wörter können bei Verlust nicht wiederhergestellt werden. Bewahre diese an einem sicheren Ort auf.</string>
|
||||
<string name="backup_info_keep_title">Sicher aufbewahren</string>
|
||||
<string name="backup_info_save_description">Bewahre diese %s Wörter an einem sicheren Ort auf , z. B. in einem Passwort-Manager, und teile diese niemals mit Dritten.</string>
|
||||
<string name="backup_info_save_title">Keine Wiederherstellung möglich</string>
|
||||
<string name="backup_info_title">Wiederherstellungsphrase</string>
|
||||
<string name="backup_seed_caution">Gebe diese Wörter niemals weiter. Jeder, der sie erfährt, kann Deine gesamten Kryptowährungen stehlen. Tangem wird Dich niemals danach fragen. Die folgenden %s Wörter sind die Wiederherstellungsphrase Deiner Wallets. Mit dieser Phrase kannst Du Deine Wallet wiederherstellen, falls Du Dein Gerät verlierst.</string>
|
||||
<string name="backup_seed_description">Schreibe diese %s Wörter der Reihe nach auf und bewahre sie sicher und vertraulich auf</string>
|
||||
<string name="backup_seed_responsibility">Die volle Verantwortung für die Sicherheit und Sicherung der Wallet und der Wiederherstellungsphrase liegt beim Benutzer, nicht bei Tangem.</string>
|
||||
<string name="backup_seed_title">Wiederherstellungsphrase</string>
|
||||
<string name="balance_hidden_description">Um deine Kontostände ein- oder auszublenden, flippe einfach das Display deines Geräts nach unten, oder schalte es in den Einstellungen aus.</string>
|
||||
<string name="balance_hidden_do_not_show_button">Nicht mehr anzeigen</string>
|
||||
<string name="balance_hidden_got_it_button">Verstanden</string>
|
||||
|
|
@ -103,6 +125,7 @@
|
|||
<string name="common_approval">Genehmigung</string>
|
||||
<string name="common_approve">Genehmigen</string>
|
||||
<string name="common_attention">Achtung</string>
|
||||
<string name="common_backup">Sicherungskopie</string>
|
||||
<string name="common_balance">Bilanz: %s</string>
|
||||
<string name="common_balance_title">Saldo</string>
|
||||
<string name="common_biometric_authentication">biometrische Authentifizierung</string>
|
||||
|
|
@ -119,10 +142,12 @@
|
|||
<string name="common_claim">Beanspruchen</string>
|
||||
<string name="common_claim_rewards">Stakingbelohnungen beanspruchen</string>
|
||||
<string name="common_close">Schließen</string>
|
||||
<string name="common_coming_soon">Demnächst verfügbar</string>
|
||||
<string name="common_confirm">Bestätigen</string>
|
||||
<string name="common_contact_tangem_support">Kontakt zum Tangem-Support</string>
|
||||
<string name="common_contact_visa_support">Kontakt zum Visa-Support</string>
|
||||
<string name="common_continue">Weiter</string>
|
||||
<string name="common_convert">Umwandeln</string>
|
||||
<string name="common_copy">Kopieren</string>
|
||||
<string name="common_copy_address">Adresse kopieren</string>
|
||||
<string name="common_create">Erstellen</string>
|
||||
|
|
@ -154,6 +179,7 @@
|
|||
<string name="common_fee_selector_option_slow">Langsam</string>
|
||||
<string name="common_fee_selector_title">Gebühren</string>
|
||||
<string name="common_finish">Beenden</string>
|
||||
<string name="common_free">Kostenlos</string>
|
||||
<string name="common_generate_addresses">Adressen synchronisieren</string>
|
||||
<string name="common_go_to_provider">Zum Anbieter gehen</string>
|
||||
<string name="common_go_to_token">Zum Token</string>
|
||||
|
|
@ -206,6 +232,7 @@
|
|||
<string name="common_show_more">Mehr anzeigen</string>
|
||||
<string name="common_sign">Signieren</string>
|
||||
<string name="common_sign_and_send">Signieren und senden</string>
|
||||
<string name="common_skip">Überspringen</string>
|
||||
<string name="common_stake">Staken</string>
|
||||
<string name="common_staking">Staking</string>
|
||||
<string name="common_start">Start</string>
|
||||
|
|
@ -226,6 +253,7 @@
|
|||
<string name="common_unreachable">Nicht erreichbar</string>
|
||||
<string name="common_unstake">Staking beenden</string>
|
||||
<string name="common_utxo_validate_withdrawal_message_warning">Aufgrund der Beschränkungen von %1$s können nur %2$d UTXOs in eine einzige Transaktion passen. Das bedeutet, dass du nur %3$s oder weniger senden kannst. Du musst den Betrag reduzieren.</string>
|
||||
<string name="common_value_copied">Wert kopiert</string>
|
||||
<string name="common_week">woche</string>
|
||||
<string name="common_with">mit</string>
|
||||
<string name="common_yes">Ja</string>
|
||||
|
|
@ -308,7 +336,7 @@
|
|||
<string name="express_exchange_notification_verification_title">KYC-Überprüfung durch den Anbieter erforderlich</string>
|
||||
<string name="express_exchange_status_bought">Gekauft</string>
|
||||
<string name="express_exchange_status_buying">Einkaufen</string>
|
||||
<string name="express_exchange_status_buying_active">Einkaufen...</string>
|
||||
<string name="express_exchange_status_buying_active">Warten auf Kauf...</string>
|
||||
<string name="express_exchange_status_canceled">Transaktion abgebrochen</string>
|
||||
<string name="express_exchange_status_confirmed">Anzahlung bestätigt</string>
|
||||
<string name="express_exchange_status_confirming">Warten auf Bestätigung</string>
|
||||
|
|
@ -386,6 +414,23 @@
|
|||
<string name="home_button_scan">Karte oder Ring scannen</string>
|
||||
<string name="hot_crypto_add_token_subtitle">An %s</string>
|
||||
<string name="hot_crypto_token_network">Im %s Netzwerk</string>
|
||||
<string name="hw_activation_need_backup">Jetzt sichern</string>
|
||||
<string name="hw_activation_need_description">Um die Einrichtung abzuschließen, sicher Deine Wallet und sicher den App-Zugriff mit einem Access Code.</string>
|
||||
<string name="hw_activation_need_finish">Jetzt beenden</string>
|
||||
<string name="hw_activation_need_title">Wallet-Aktivierung abschließen</string>
|
||||
<string name="hw_backup_icloud_description">Stelle eine vorhandene Wallet wieder her, die in Deinem iCloud-Backup gespeichert ist</string>
|
||||
<string name="hw_backup_icloud_title">iCloud-Sicherung</string>
|
||||
<string name="hw_backup_need_action">Gehe zum Backup</string>
|
||||
<string name="hw_backup_need_description">Um Deine Wallet mit einem Zugangscode zu sichern, führe zuerst die Sicherung durch.</string>
|
||||
<string name="hw_backup_need_title">Sicherung zuerst beenden</string>
|
||||
<string name="hw_backup_no_backup">Kein Backup</string>
|
||||
<string name="hw_backup_seed_description">Physische Karten, die Deine Kryptowährung sicher offline speichern.</string>
|
||||
<string name="hw_backup_seed_title">Wiederherstellungs-Phrase</string>
|
||||
<string name="hw_create_keys_description">Über eingehende Transaktionen benachrichtigt werden</string>
|
||||
<string name="hw_create_keys_title">Schlüssel werden in der App gespeichert</string>
|
||||
<string name="hw_create_seed_description">Bleibe über die neuesten Funktionen und Neuigkeiten auf dem Laufenden</string>
|
||||
<string name="hw_create_seed_title">Sicherung der Seed-Phrase</string>
|
||||
<string name="hw_create_title">Mobile Wallet erstellen</string>
|
||||
<string name="information_generated_with_ai">Diese Informationen wurden mithilfe von KI generiert.\nTippe hier, wenn Du Fehler finden.</string>
|
||||
<string name="initial_message_change_access_code_body">Um den Zugangscode zu ändern, halte die Karte oder Ring wie oben gezeigt an das Gerät und entferne sie erst am Ende des Vorgangs.</string>
|
||||
<string name="initial_message_change_passcode_body">Um den Passcode zu ändern, halte die Karte oder Ring wie oben gezeigt an das Gerät und entferne sie erst am Ende des Vorgangs.</string>
|
||||
|
|
@ -631,6 +676,7 @@
|
|||
<string name="onboarding_create_wallet_options_title">Schlüssel anonym generieren</string>
|
||||
<string name="onboarding_done_body">Deine Karte oder Ring ist aktiviert und einsatzbereit</string>
|
||||
<string name="onboarding_done_header">Erfolgreich!</string>
|
||||
<string name="onboarding_done_wallet">Deine Wallet ist eingerichtet und einsatzbereit!</string>
|
||||
<string name="onboarding_exit_alert_message">In diesem Fall musst du ganz von vorne anfangen.</string>
|
||||
<string name="onboarding_exit_alert_title">Möchtest du den Aktivierungsprozess abbrechen?</string>
|
||||
<string name="onboarding_getting_started">Erste Schritte</string>
|
||||
|
|
@ -797,7 +843,9 @@
|
|||
<string name="send_alert_fee_too_low_text">Du hast eine Kommission angegeben, die unter dem empfohlenen Betrag liegt, was zu einer Verzögerung Ihrer Transaktion führen könnte. Weiter?</string>
|
||||
<string name="send_alert_transaction_failed_text">Grund: %1$s\nCode: %2$s</string>
|
||||
<string name="send_alert_transaction_failed_title">Die Transaktion ist nicht abgeschlossen</string>
|
||||
<string name="send_amount_convert_to_another_token">In ein anderes Token umwandeln</string>
|
||||
<string name="send_amount_label">Betrag</string>
|
||||
<string name="send_amount_receive_token_subtitle">Wird an den Empfänger gesendet</string>
|
||||
<string name="send_bitcoin_custom_fee_footer">Du kannst deine Transaktionsgebühr festlegen, indem du den Wert im Feld Satoshi pro vByte anpasst.</string>
|
||||
<string name="send_custom_amount_fee_footer">Die Gebühr, die für deine Transaktion erhoben wird. Du kannst einen eigenen Wert festlegen.</string>
|
||||
<string name="send_custom_evm_max_fee">Maximale Gebühr</string>
|
||||
|
|
@ -843,6 +891,7 @@
|
|||
<string name="send_notification_fee_too_high_title">Die individuelle Gebühr ist hoch</string>
|
||||
<string name="send_notification_high_fee_text">Aufgrund der Besonderheiten des Netzes %1$s ist die Gebühr für die Überweisung des gesamten Guthabens höher. Um die Kommission zu reduzieren, Kannst du %2$s verlassen.</string>
|
||||
<string name="send_notification_high_fee_title">Die Gebühr ist höher</string>
|
||||
<string name="send_notification_invalid_amount_rent_destination">Das Empfängerkonto ist nicht aktiviert. Der Mindestbetrag der Übertragung muss gleich oder höher sein als der mietfreie Restbetrag: %1$s.</string>
|
||||
<string name="send_notification_invalid_amount_rent_fee">Dein Kontostand darf nicht niedriger sein als die Miete. Bitte halte mindestens %1$s auf Deinem Konto oder ziehe den gesamten Betrag ab.</string>
|
||||
<string name="send_notification_invalid_amount_text">Die enthaltene Kommission übersteigt den Überweisungsbetrag, was zu einem negativen Wert führt</string>
|
||||
<string name="send_notification_invalid_amount_title">Ungültige Menge</string>
|
||||
|
|
@ -861,7 +910,7 @@
|
|||
<string name="send_recent_transactions">Jüngste</string>
|
||||
<string name="send_recipient">Empfänger</string>
|
||||
<string name="send_recipient_address_error">Keine gültige Adresse</string>
|
||||
<string name="send_recipient_address_footer">Vergewisser dich, dass die empfangende Wallet-Adresse im %s Netzwerk ist, damit du deine Token nicht verlierst.</string>
|
||||
<string name="send_recipient_address_footer">Stelle sicher, dass sich die empfangende Wallet-Adresse im ** %s -Netzwerk** befindet, um den Verlust Deiner Token zu vermeiden.</string>
|
||||
<string name="send_recipient_address_footer_highlighted_part">senden an %s</string>
|
||||
<string name="send_recipient_address_footer_v2">Stelle sicher, dass Du %s Netzwerkadresse hast, da Fehler zu verlorenen Übertragungen führen können</string>
|
||||
<string name="send_recipient_label">Senden an</string>
|
||||
|
|
@ -889,8 +938,10 @@
|
|||
<string name="send_validation_invalid_amount">Ungültiger Betrag</string>
|
||||
<string name="send_validation_invalid_fee">Die Gebühr geht über die Bilanz hinaus</string>
|
||||
<string name="send_validation_invalid_total">Der Gesamtbetrag geht über die Bilanz hinaus</string>
|
||||
<string name="send_with_swap_confirm_title">Tauschen und senden</string>
|
||||
<string name="send_with_swap_notification_text">Sende uns ein Token, und wir konvertieren es unterwegs. Dein Empfänger erhält genau das, was er braucht – nahtlos.</string>
|
||||
<string name="send_with_swap_recipient_amount_text">Wird an den Empfänger gesendet</string>
|
||||
<string name="send_with_swap_recipient_amount_title">Zu erhaltender Betrag</string>
|
||||
<string name="send_with_swap_title">Senden mit Swap</string>
|
||||
<string name="sent_transaction_sent_title">Transaktion gesendet</string>
|
||||
<string name="settings_card_settings_footer">Bereite das Scannen der Karte oder Ring vor, die du einrichten möchtest.</string>
|
||||
|
|
@ -1046,6 +1097,7 @@
|
|||
<string name="swap_story_second_title">Unschlagbare Preise</string>
|
||||
<string name="swap_story_third_subtitle">Problemlos und intuitiv, sodass Deine Token mit nur wenigen Handgriffen getauscht werden können</string>
|
||||
<string name="swap_story_third_title">Einfach bequem</string>
|
||||
<string name="swap_via_provider">Tausch über Anbieter</string>
|
||||
<string name="swapping_alert_cex_description">Der Betrag umfasst:\n- Gebühr des Dienstanbieters\n- Netzgebühr für die Rücksendung von %s von der Vermittlungsstelle an die Adresse des Nutzers.</string>
|
||||
<string name="swapping_alert_cex_description_with_slippage">Der Betrag beinhaltet:\n• Honorar des Dienstleisters\n• Netzwerkgebühr für das Senden %1$s von der Börse zurück an die Adresse des Benutzers. \n\n Provider-Slippage kann bis zu %2$s</string>
|
||||
<string name="swapping_alert_dex_description">Der Betrag enthält die Gebühren des Dienstleisters.</string>
|
||||
|
|
@ -1153,6 +1205,7 @@
|
|||
<string name="visa_onboarding_in_progress_description">Das wird nicht lange dauern. Wir richten gerade Dein Konto ein.</string>
|
||||
<string name="visa_onboarding_in_progress_issuer_description">Das wird nicht lange dauern. Wir schließen die Aktivierung ab.</string>
|
||||
<string name="visa_onboarding_in_progress_title">Alles wird vorbereitet!</string>
|
||||
<string name="visa_onboarding_pin_code_title">PIN-Code erstellen</string>
|
||||
<string name="visa_onboarding_pin_not_accepted">PIN wurde nicht akzeptiert. Versuche es erneut oder verwende einen anderen Code.</string>
|
||||
<string name="visa_onboarding_pin_validation_error_message">Ungültige PIN: Sequenzen oder Wiederholungen vermeiden</string>
|
||||
<string name="visa_onboarding_success_screen_description">Schon kann es losgehen!</string>
|
||||
|
|
@ -1201,6 +1254,29 @@
|
|||
<string name="wallet_connect_subtitle">Mit dApps verbinden</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
<string name="wallet_connect_toast_awaiting_session_proposal">Das Herstellen der Verbindung kann einige Sekunden dauern</string>
|
||||
<string name="wallet_create_hardware_badge">Von %s</string>
|
||||
<string name="wallet_create_hardware_description">Kaufe eine Tangem Wallet – physische Karten, die Deine Kryptowährungen sicher offline speichern.</string>
|
||||
<string name="wallet_create_hardware_title">Hardware-Wallet</string>
|
||||
<string name="wallet_create_mobile_description">Eine sichere Wallet wird in Sekundenschnelle auf Deinem Telefon erstellt.</string>
|
||||
<string name="wallet_create_mobile_title">Mobile Wallet</string>
|
||||
<string name="wallet_create_nav_info_title">Was soll ich wählen?</string>
|
||||
<string name="wallet_create_scan_question">Besitzt Du bereits eine Tangem Wallet?</string>
|
||||
<string name="wallet_create_scan_title">Jetzt scannen</string>
|
||||
<string name="wallet_create_title">Wähle, wie Du Deine Wallet erstellen möchten</string>
|
||||
<string name="wallet_import_buy_question">Möchtest Du eine Tangem-Wallet kaufen?</string>
|
||||
<string name="wallet_import_buy_title">Jetzt kaufen</string>
|
||||
<string name="wallet_import_google_drive_description">Eine vorhandene Wallet wiederherstellen, die Deinem Google Drive-Backup gespeichert ist</string>
|
||||
<string name="wallet_import_google_drive_title">Importieren aus Google Drive</string>
|
||||
<string name="wallet_import_navtitle">Vorhandenes Wallet hinzufügen</string>
|
||||
<string name="wallet_import_scan_description">Physische Karten, die Deine Kryptowährung sicher offline speichern.</string>
|
||||
<string name="wallet_import_scan_title">Scanne eine Tangem-Wallet</string>
|
||||
<string name="wallet_import_seed_description">Deine Seed-Phrase, um Deine Wallet wiederherzustellen</string>
|
||||
<string name="wallet_import_seed_navtitle">Wallet importieren</string>
|
||||
<string name="wallet_import_seed_title">Importieren einer Wiederherstellungsphrase</string>
|
||||
<string name="wallet_import_success_description">Du hast Deine Wallet erfolgreich gesichert.</string>
|
||||
<string name="wallet_import_success_navtitle">Wallet importieren</string>
|
||||
<string name="wallet_import_success_title">Import abgeschlossen</string>
|
||||
<string name="wallet_import_title">Wallet importieren</string>
|
||||
<string name="wallet_marketplace_block_title">%s Marktpreis</string>
|
||||
<string name="wallet_marketprice_block_update_time">letzte 24h</string>
|
||||
<string name="wallet_network_group_title">%s Netzwerk</string>
|
||||
|
|
@ -1209,6 +1285,8 @@
|
|||
<string name="wallet_promo_banner_button_title">Jetzt mit 10 % Rabatt kaufen</string>
|
||||
<string name="wallet_promo_banner_description">Greife auf 13.000+ Kryptowährungen zu. Kaufe, verkaufe, tausche und stake Sie mit einem einzigen Fingertipp.\nVerbinde bis zu drei Karten für ein Backup.</string>
|
||||
<string name="wallet_promo_banner_title">Entdecke die Tangem Wallet</string>
|
||||
<string name="wallet_settings_access_code_description">Geheimcode zum Schutz dieser Wallet. Wird für Anmeldung und Signaturen verwendet.</string>
|
||||
<string name="wallet_settings_access_code_title">Zugangscode festlegen/ändern</string>
|
||||
<string name="wallet_settings_push_notifications_description">Bleib stets über eingehende Wallet-Transaktionen und Tangem-Updates auf dem Laufenden.</string>
|
||||
<string name="wallet_settings_push_notifications_title">Transaktionsbenachrichtigungen</string>
|
||||
<string name="wallet_settings_title">Wallet-Einstellungen</string>
|
||||
|
|
@ -1298,13 +1376,15 @@
|
|||
<string name="warning_some_networks_unreachable_message">Wischen Sie nach unten, um zu aktualisieren, oder versuchen Sie es später erneut.</string>
|
||||
<string name="warning_some_networks_unreachable_title">Einige Netzwerke sind nicht erreichbar</string>
|
||||
<string name="warning_some_token_balances_not_updated">Einige Token-Guthaben konnten nicht aktualisiert werden</string>
|
||||
<string name="warning_stellar_token_trustline_not_enough_xlm">Nicht genug %s. Lade Dein XLM-Konto auf, um dieses Token zu verknüpfen</string>
|
||||
<string name="warning_testnet_card_message">Dies ist eine Testnet-Karte. Sie kann keine Transaktionen verarbeiten und sollte nur zu Test- und Entwicklungszwecken verwendet werden.</string>
|
||||
<string name="warning_testnet_card_title">Nur für Testzwecke</string>
|
||||
<string name="warning_token_balance_not_updated">Der Kontostand ist möglicherweise veraltet. Aktualisiere bittre die Seite.</string>
|
||||
<string name="warning_token_required_min_coin_reserve">Nicht genug %1$s. Lade Dein XLM-Konto auf, um dieses Token zu verknüpfen</string>
|
||||
<string name="warning_token_required_min_coin_reserve">Nicht genügend %1$s. Lade Dein %2$s -Konto auf, um dieses Token zu verknüpfen</string>
|
||||
<string name="warning_token_trustline_button_title">Trustline aktivieren</string>
|
||||
<string name="warning_token_trustline_subtitle">Um dieses Token zu erhalten, muss eine Trustline aktiviert sein. Das Netzwerk benötigt eine Reserve von %1$s %2$s.</string>
|
||||
<string name="warning_token_trustline_title">Trustline erforderlich</string>
|
||||
<string name="wc_alert_audit_malicious_domain">Bösartige/ verdächtige Domäne</string>
|
||||
<string name="wc_alert_audit_unknown_domain">Unbekannte Domäne</string>
|
||||
<string name="wc_alert_connect_anyway">Trotzdem verbinden</string>
|
||||
<string name="wc_alert_connection_timeout_description">Zeitüberschreitungsfehler. Bitte versuche es später erneut.</string>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<string name="action_buttons_service_loading_alert_message">Esto puede tardar unos segundos. Inténtelo de nuevo más tarde.</string>
|
||||
<string name="action_buttons_service_loading_alert_title">Los datos aún no se han cargado.</string>
|
||||
<string name="action_buttons_something_wrong_alert_message">La acción no está disponible actualmente. Inténtalo de nuevo más tarde o actualiza los datos deslizando el dedo hacia abajo en la pantalla.</string>
|
||||
<string name="action_buttons_something_wrong_alert_title">No disponible</string>
|
||||
<string name="action_buttons_something_wrong_alert_title">La acción no está disponible</string>
|
||||
<string name="action_buttons_swap_choose_token">Elige el token</string>
|
||||
<string name="action_buttons_swap_empty_search_message">¿No encuentra el token en tu billetera? Consulte los mercados para encontrarlo y agregarlo al intercambio</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">No hay tokens disponibles para intercambiar con el token seleccionado. Por favor elige otro.</string>
|
||||
|
|
@ -49,6 +49,17 @@
|
|||
<string name="app_settings_theme_mode_system">Predeterminado del sistema</string>
|
||||
<string name="app_settings_theme_selector_title">Tema</string>
|
||||
<string name="app_settings_title">Ajustes de la app</string>
|
||||
<string name="backup_complete_description">Copia de seguridad de tu billetera realizada con éxito.</string>
|
||||
<string name="backup_complete_title">Copia de seguridad completada</string>
|
||||
<string name="backup_info_description">Su frase secreta de recuperación es un conjunto fijo de %s palabras aleatorias que se utilizan para acceder a tu billetera y recuperarla.</string>
|
||||
<string name="backup_info_keep_description">Estas palabras no pueden recuperarse si se pierden. Asegúrate de guardarlas en un lugar seguro.</string>
|
||||
<string name="backup_info_keep_title">Mantenlo seguro</string>
|
||||
<string name="backup_info_save_description">Guarda estas %s palabras en un lugar seguro, como un administrador de contraseñas, y nunca las compartas con nadie.</string>
|
||||
<string name="backup_info_save_title">No hay recuperación posible</string>
|
||||
<string name="backup_info_title">Frase de recuperación</string>
|
||||
<string name="backup_seed_description">Escribe estas palabras %s en orden y guárdalas en un lugar seguro y privado</string>
|
||||
<string name="backup_seed_responsibility">La responsabilidad total de la seguridad y la copia de seguridad de la billetera y la frase de recuperación recae en el usuario, no en Tangem.</string>
|
||||
<string name="backup_seed_title">Frase de recuperación</string>
|
||||
<string name="balance_hidden_description">Para ocultar o mostrar tus saldos, simplemente gira la pantalla de tu dispositivo hacia abajo, o desactívalo en Ajustes</string>
|
||||
<string name="balance_hidden_do_not_show_button">No mostrar de nuevo</string>
|
||||
<string name="balance_hidden_got_it_button">Entendido</string>
|
||||
|
|
@ -65,6 +76,10 @@
|
|||
<item quantity="one">%d tarjeta</item>
|
||||
<item quantity="other">%d tarjetas</item>
|
||||
</plurals>
|
||||
<plurals name="card_label_token_count">
|
||||
<item quantity="one">%s token</item>
|
||||
<item quantity="other">%s tokens</item>
|
||||
</plurals>
|
||||
<string name="card_settings_access_code_recovery_disabled_description">Desactive esta opción si no quiere que esta tarjeta se use para reiniciar códigos de acceso en otras tarjetas de esta billetera. Tenga en cuenta que esto también evitará que reinicie el código de acceso en esta tarjeta.</string>
|
||||
<string name="card_settings_access_code_recovery_enabled_description">Permite usar esta tarjeta para reiniciar el código de acceso en otras tarjetas de esta billetera</string>
|
||||
<string name="card_settings_access_code_recovery_title">Recuperación de código de acceso</string>
|
||||
|
|
@ -110,12 +125,17 @@
|
|||
<string name="common_cancel">Cancelar</string>
|
||||
<string name="common_change">Cambie</string>
|
||||
<string name="common_choose_action">Elige una acción</string>
|
||||
<string name="common_choose_network">Elegir red</string>
|
||||
<string name="common_choose_token">Elige token</string>
|
||||
<string name="common_claim">Reclamar</string>
|
||||
<string name="common_claim_rewards">Reclame recompensas</string>
|
||||
<string name="common_close">Cerrar</string>
|
||||
<string name="common_coming_soon">Próximamente</string>
|
||||
<string name="common_confirm">Confirme</string>
|
||||
<string name="common_contact_tangem_support">Contacte con el soporte de Tangem</string>
|
||||
<string name="common_contact_visa_support">Contacte con el soporte de Visa</string>
|
||||
<string name="common_continue">Continuar</string>
|
||||
<string name="common_convert">Convertir</string>
|
||||
<string name="common_copy">Copiar</string>
|
||||
<string name="common_copy_address">Copiar la dirección</string>
|
||||
<string name="common_create">Crear</string>
|
||||
|
|
@ -150,6 +170,7 @@
|
|||
<string name="common_generate_addresses">Sincronizar direcciones</string>
|
||||
<string name="common_go_to_provider">Ir al proveedor</string>
|
||||
<string name="common_go_to_token">Ir al token</string>
|
||||
<string name="common_got_it">Entiendo</string>
|
||||
<string name="common_hide">Ocultar</string>
|
||||
<string name="common_hour">hora</string>
|
||||
<string name="common_import">Importe</string>
|
||||
|
|
@ -194,6 +215,7 @@
|
|||
<string name="common_server_unavailable">El servidor no está disponible, por favor inténtelo de nuevo más tarde</string>
|
||||
<string name="common_share">Compartir</string>
|
||||
<string name="common_share_link">Compartir enlace</string>
|
||||
<string name="common_show_less">Mostrar menos</string>
|
||||
<string name="common_show_more">Mostrar más</string>
|
||||
<string name="common_sign">Firme</string>
|
||||
<string name="common_sign_and_send">Firme y envíe</string>
|
||||
|
|
@ -203,6 +225,7 @@
|
|||
<string name="common_submit">Enviar</string>
|
||||
<string name="common_success">Con éxito</string>
|
||||
<string name="common_support">Soporte</string>
|
||||
<string name="common_supported_networks">Redes compatibles</string>
|
||||
<string name="common_swap">Swap</string>
|
||||
<string name="common_terms_and_conditions">términos y condiciones</string>
|
||||
<string name="common_terms_of_use">Condiciones de uso</string>
|
||||
|
|
@ -216,6 +239,7 @@
|
|||
<string name="common_unreachable">Inaccesible</string>
|
||||
<string name="common_unstake">Termine el staking</string>
|
||||
<string name="common_utxo_validate_withdrawal_message_warning">Debido a limitaciónes sobre %1$s, solos %2$d UTXO pueden caber en una sola transacción. Esto significa que solo puedes enviar %3$s o menos. Debe reducir la cantidad.</string>
|
||||
<string name="common_value_copied">Valor copiado</string>
|
||||
<string name="common_week">semana</string>
|
||||
<string name="common_with">con</string>
|
||||
<string name="common_yes">Sí</string>
|
||||
|
|
@ -262,6 +286,7 @@
|
|||
<string name="details_row_subtitle_signed_hashes_format">%s hashes</string>
|
||||
<string name="details_row_title_cid">ID del dispositivo</string>
|
||||
<string name="details_row_title_contact_to_support">Contactar con el equipo de soporte</string>
|
||||
<string name="details_row_title_contact_to_support_chat">Abrir chat de soporte</string>
|
||||
<string name="details_row_title_create_backup">Vincular más tarjetas</string>
|
||||
<string name="details_row_title_currency">Moneda de la aplicación</string>
|
||||
<string name="details_row_title_flip_to_hide">Girar para ocultar saldos</string>
|
||||
|
|
@ -278,7 +303,8 @@
|
|||
<string name="exchange_tokens_unavailable_tokens_header">No se puede intercambiar por %s</string>
|
||||
<string name="express_by_provider">Proporcionado por</string>
|
||||
<string name="express_cex_status_button_title">Estado</string>
|
||||
<string name="express_choose_providers_subtitle">Tangem ofrece swaps de tokens a través de proveedores externos según los términos de cada proveedor</string>
|
||||
<string name="express_choose_provider">Elegir proveedor</string>
|
||||
<string name="express_choose_providers_subtitle">Tangem ofrece swaps de tokens a través de proveedores externos</string>
|
||||
<string name="express_choose_providers_title">Elija proveedor</string>
|
||||
<string name="express_error_code">Ha ocurrido un error. Código: %s</string>
|
||||
<string name="express_error_provider_amount_roundup">Error %1$s. El proveedor seleccionado no puede procesar la transacción especificada. Por favor, redondee el valor a %2$s o cámbielo</string>
|
||||
|
|
@ -294,37 +320,39 @@
|
|||
<string name="express_exchange_notification_refund_title">El monto fue reembolsado en %1$s (red %2$s)</string>
|
||||
<string name="express_exchange_notification_verification_text">Visite el sitio web del proveedor para la verificación</string>
|
||||
<string name="express_exchange_notification_verification_title">Verificación KYC requerida por el proveedor</string>
|
||||
<string name="express_exchange_status_bought">Comprado</string>
|
||||
<string name="express_exchange_status_buying">Comprando</string>
|
||||
<string name="express_exchange_status_bought">Compra completada</string>
|
||||
<string name="express_exchange_status_buying">A la espera de la compra</string>
|
||||
<string name="express_exchange_status_buying_active">Comprando...</string>
|
||||
<string name="express_exchange_status_canceled">Cancelado</string>
|
||||
<string name="express_exchange_status_confirmed">Confirmado</string>
|
||||
<string name="express_exchange_status_canceled">Transacción cancelada</string>
|
||||
<string name="express_exchange_status_confirmed">Depósito confirmado</string>
|
||||
<string name="express_exchange_status_confirming">Esperando confirmación</string>
|
||||
<string name="express_exchange_status_confirming_active">En proceso de confirmación</string>
|
||||
<string name="express_exchange_status_exchanged">Intercambio</string>
|
||||
<string name="express_exchange_status_exchanging">En intercambio</string>
|
||||
<string name="express_exchange_status_exchanging_active">En intercambio</string>
|
||||
<string name="express_exchange_status_failed">Fallido</string>
|
||||
<string name="express_exchange_status_paused">En pausa</string>
|
||||
<string name="express_exchange_status_confirming_active">Esperando confirmación...</string>
|
||||
<string name="express_exchange_status_exchanged">Intercambio completado</string>
|
||||
<string name="express_exchange_status_exchanging">A la espera del intercambio</string>
|
||||
<string name="express_exchange_status_exchanging_active">Esperando el intercambio...</string>
|
||||
<string name="express_exchange_status_failed">Intercambio fallido</string>
|
||||
<string name="express_exchange_status_paused">Intercambio en pausa</string>
|
||||
<string name="express_exchange_status_received">Depósito recibido</string>
|
||||
<string name="express_exchange_status_receiving">Esperando depósito</string>
|
||||
<string name="express_exchange_status_receiving_active">Esperando depósito</string>
|
||||
<string name="express_exchange_status_refunded">Reembolsado</string>
|
||||
<string name="express_exchange_status_refunded">Reembolso completado</string>
|
||||
<string name="express_exchange_status_refunding">A la espera del reembolso</string>
|
||||
<string name="express_exchange_status_sending">Transfiriendo a su cuenta</string>
|
||||
<string name="express_exchange_status_sending_active">Enviando...</string>
|
||||
<string name="express_exchange_status_sent">Enviado</string>
|
||||
<string name="express_exchange_status_sending_active">Enviando fondos...</string>
|
||||
<string name="express_exchange_status_sent">Fondos enviados</string>
|
||||
<string name="express_exchange_status_subtitle">Datos proporcionados por el proveedor. La cantidad estimada puede cambiar debido a las condiciones del mercado.</string>
|
||||
<string name="express_exchange_status_title">Estado del intercambio</string>
|
||||
<string name="express_exchange_status_verifying">Verificación requerida</string>
|
||||
<string name="express_exchange_status_waiting_tx_hash">Esperando hash de transacción</string>
|
||||
<string name="express_exchange_token_list_subtitle">Lista de todos los tokens agregados a su billetera</string>
|
||||
<string name="express_fetch_best_rates">Buscando las mejores tarifas…</string>
|
||||
<string name="express_fetch_best_rates">Buscar tarifas actuales...</string>
|
||||
<string name="express_floating_rate">Tasa flotante</string>
|
||||
<string name="express_legal_one_placeholder">Al utilizar la función de intercambio, acepta el %s</string>
|
||||
<string name="express_legal_two_placeholders">Al utilizar la función de intercambio, aceptas las condiciones de %1$s y %2$s del proveedor</string>
|
||||
<string name="express_more_providers_soon">Próximamente habrá más proveedores.\n¡Estén atentos!</string>
|
||||
<string name="express_more_providers_soon">Pronto habrá más proveedores disponibles</string>
|
||||
<string name="express_provider">Proveedor</string>
|
||||
<string name="express_provider_best_rate">Mejor tarifa</string>
|
||||
<string name="express_provider_fca_warning_list">Lista de advertencias de la FCA</string>
|
||||
<string name="express_provider_max_amount">Disponible hasta %s</string>
|
||||
<string name="express_provider_min_amount">Disponible desde %s</string>
|
||||
<string name="express_provider_not_available">No disponible para este par</string>
|
||||
|
|
@ -336,6 +364,9 @@
|
|||
<string name="express_status_hide_button_text">Ocultar esta transacción</string>
|
||||
<string name="express_status_hide_dialog_text">Una vez oculto, el estado de la transacción no se puede volver a ver. En su lugar, basta con deslizar el dedo para descartarla.</string>
|
||||
<string name="express_status_hide_dialog_title">¿Ocultar el estado de la transacción?</string>
|
||||
<string name="express_swap_not_supported_text">Este token no es compatible. Por favor, elige un token diferente para intercambiar.</string>
|
||||
<string name="express_swap_not_supported_title">%s no es compatible</string>
|
||||
<string name="express_swap_with">Intercambiar con</string>
|
||||
<string name="express_token_list_empty_search">No se encontraron fichas. Por favor intenta con otra solicitud</string>
|
||||
<string name="express_transaction_id">ID : %s</string>
|
||||
<string name="express_transaction_id_copied">ID de transacción copiado</string>
|
||||
|
|
@ -363,6 +394,8 @@
|
|||
<string name="give_permission_swap_subtitle" formatted="false">Para continuar, de autorización a los smart contracts de %1s para usar su %2s</string>
|
||||
<string name="give_permission_title">Dar autorización</string>
|
||||
<string name="give_permission_unlimited">Ilimitado</string>
|
||||
<string name="home_button_add_existing_wallet">Agregar billetera existente</string>
|
||||
<string name="home_button_create_new_wallet">Crear nueva billetera</string>
|
||||
<string name="home_button_order">Pedir Tangem</string>
|
||||
<string name="home_button_scan">Escanee</string>
|
||||
<string name="hot_crypto_add_token_subtitle">a %s</string>
|
||||
|
|
@ -418,6 +451,7 @@
|
|||
<string name="markets_add_to_my_portfolio_unavailable_description">Este activo no está disponible en la billetera</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_for_wallet_description">Este activo no está disponible para esta billetera</string>
|
||||
<string name="markets_add_token">Añadir</string>
|
||||
<string name="markets_apy_placeholder">APY %s</string>
|
||||
<string name="markets_available_networks">Redes disponibles</string>
|
||||
<string name="markets_common_my_portfolio">Mi portafolio</string>
|
||||
<string name="markets_common_title">Mercado</string>
|
||||
|
|
@ -671,7 +705,7 @@
|
|||
<string name="onboarding_wallet_info_title_second">Tarjetas idénticas</string>
|
||||
<string name="onboarding_wallet_info_title_third">Código de acceso</string>
|
||||
<string name="onramp_avaiable_with_payment_methods">Disponible con %s</string>
|
||||
<string name="onramp_choose_provider_title_hint">La compra de criptomonedas en Tangem se lleva a cabo utilizando proveedores externos usando sus propios términos.</string>
|
||||
<string name="onramp_choose_provider_title_hint">La compra de criptomonedas en Tangem se lleva a cabo utilizando proveedores externos.</string>
|
||||
<string name="onramp_country_search">Buscar por país</string>
|
||||
<string name="onramp_country_unavailable">Indisponible</string>
|
||||
<string name="onramp_currency_other">Otras monedas</string>
|
||||
|
|
@ -698,6 +732,13 @@
|
|||
<string name="organize_tokens_sort_by_balance">Por saldo</string>
|
||||
<string name="organize_tokens_title">Organizar tokens</string>
|
||||
<string name="organize_tokens_ungroup">Desagrupar</string>
|
||||
<string name="push_notifications_more_info">Más información</string>
|
||||
<string name="push_notifications_permission_alert_description">Puedes activar las Notificaciones para Tangem en Ajustes.</string>
|
||||
<string name="push_notifications_permission_alert_negative_button">Activar más tarde</string>
|
||||
<string name="push_notifications_permission_alert_positive_button">Ajustes</string>
|
||||
<string name="push_notifications_permission_alert_title">Activar notificaciones</string>
|
||||
<string name="push_transactions_notifications_description">Reciba alertas de transacciones entrantes en las redes compatibles</string>
|
||||
<string name="push_transactions_notifications_title">Notificaciones de transacciones</string>
|
||||
<string name="qr_scanner_camera_denied_gallery_button">Seleccione de la galería</string>
|
||||
<string name="qr_scanner_camera_denied_settings_button">Ajustes</string>
|
||||
<string name="qr_scanner_camera_denied_text">No ha dado acceso a su cámara</string>
|
||||
|
|
@ -770,7 +811,9 @@
|
|||
<string name="send_alert_fee_too_low_text">Ha especificado una comisión inferior a la cantidad recomendada, lo que podría provocar un retraso en su transacción. ¿Continuar?</string>
|
||||
<string name="send_alert_transaction_failed_text">Razón: %1$s\nCódigo: %2$s</string>
|
||||
<string name="send_alert_transaction_failed_title">La transacción está incompleta</string>
|
||||
<string name="send_amount_convert_to_another_token">Convertir a otro token</string>
|
||||
<string name="send_amount_label">Montante</string>
|
||||
<string name="send_amount_receive_token_subtitle">Se enviará al destinatario</string>
|
||||
<string name="send_bitcoin_custom_fee_footer">Puede establecer tu tarifa de transacción ajustando el valor en el campo\nSatoshi por vByte.</string>
|
||||
<string name="send_custom_amount_fee_footer">La tarifa que se cobrará por tu transacción. Puedes establecer tu propio valor.</string>
|
||||
<string name="send_custom_evm_max_fee">Tarifa máxima</string>
|
||||
|
|
@ -794,6 +837,7 @@
|
|||
<string name="send_fee_unreachable_error_text">Compruebe su conexión de red</string>
|
||||
<string name="send_fee_unreachable_error_title">Información de tarifa de red no accesible</string>
|
||||
<string name="send_from_wallet_android">Desde</string>
|
||||
<string name="send_from_wallet_name">De %s</string>
|
||||
<string name="send_gas_limit">Límite de gas</string>
|
||||
<string name="send_gas_limit_footer">Este es el máximo de gas que se gastará para completar una transacción o contrato. Un límite de gas evita cargos inesperados o ilimitados al ejecutar una transacción.</string>
|
||||
<string name="send_gas_price">Precios del gas</string>
|
||||
|
|
@ -801,8 +845,12 @@
|
|||
<string name="send_max_amount">Max</string>
|
||||
<string name="send_max_amount_label">Importe máximo</string>
|
||||
<string name="send_max_fee">Tarifa hasta</string>
|
||||
<string name="send_memo">Memo: %s</string>
|
||||
<string name="send_memo_destination_tag_error">Memo no válido</string>
|
||||
<string name="send_network_fee_warning_title">Cobertura de tarifa de red</string>
|
||||
<string name="send_nonce">Nonce</string>
|
||||
<string name="send_nonce_footer">Número único para cada transacción. Utilízalo para reenviar o cancelar una transacción pendiente.</string>
|
||||
<string name="send_nonce_hint">Introduzca nonce..</string>
|
||||
<string name="send_notification_exceed_balance_text">Fondos insuficientes para la transferencia, ya que el total de la tarifa y el importe de la transferencia supera el saldo existente</string>
|
||||
<string name="send_notification_exceed_balance_title">El total supera el saldo</string>
|
||||
<string name="send_notification_existential_deposit_text">Se requiere un saldo de al menos %s para mantener su cuenta en blockchain para evitar riesgos de seguridad. Este monto permanecerá en su saldo y no podrá retirarse.</string>
|
||||
|
|
@ -830,6 +878,8 @@
|
|||
<string name="send_recipient">Destinatario</string>
|
||||
<string name="send_recipient_address_error">No es una dirección válida</string>
|
||||
<string name="send_recipient_address_footer">Asegúrese de que la dirección de la billetera receptora esté en la red %s para evitar perder sus tokens</string>
|
||||
<string name="send_recipient_address_footer_highlighted_part">enviar a %s</string>
|
||||
<string name="send_recipient_address_footer_v2">Asegúrese de que %s dirección de red, ya que los errores pueden resultar en transferencias perdidas</string>
|
||||
<string name="send_recipient_label">Destinatario</string>
|
||||
<string name="send_recipient_memo_footer">Un Memo/ID de destino es un ID único para diferenciar transacciones enviadas al mismo destinatario en la misma red. Precaución: Omitir un memo puede llevar a la pérdida de fondos</string>
|
||||
<string name="send_recipient_memo_footer_v2">Memo / Destination Tag es un código que separa las transacciones dirigidas a un destinatario compartido en una red de criptomonedas.</string>
|
||||
|
|
@ -846,6 +896,7 @@
|
|||
<string name="send_summary_transaction_description_suffix_fee_covered">La tarifa de red se cubrirá al usar %1$s energía</string>
|
||||
<string name="send_summary_transaction_description_suffix_fee_reduced">La tarifa de red se reducirá al usar %1$s energía</string>
|
||||
<string name="send_summary_transaction_description_suffix_including">incluida una tarifa de red de %1$s</string>
|
||||
<string name="send_to_address">A la dirección</string>
|
||||
<string name="send_transaction_success">La transacción se firmó con éxito y se envió al nodo blockchain. El saldo de la billetera se actualizará después de un tiempo.</string>
|
||||
<string name="send_tron_account_activation_error">%1$s es un activo en la red Tron. Para calcular la tarifa y realizar una transacción, deba depositar algo de Tron (TRX) en su cuenta.</string>
|
||||
<string name="send_validation_amount_exceeds_balance">El monto excede el saldo</string>
|
||||
|
|
@ -854,6 +905,10 @@
|
|||
<string name="send_validation_invalid_amount">Cantidad no válida</string>
|
||||
<string name="send_validation_invalid_fee">La tarifa excede el saldo</string>
|
||||
<string name="send_validation_invalid_total">El monto total excede el saldo</string>
|
||||
<string name="send_with_swap_notification_text">Envía cualquier token y lo convertiremos en el camino. Su destinatario obtiene exactamente lo que necesita, sin problemas.</string>
|
||||
<string name="send_with_swap_recipient_amount_text">Se enviará un destinatario</string>
|
||||
<string name="send_with_swap_recipient_amount_title">Cantidad a recibir</string>
|
||||
<string name="send_with_swap_title">Enviar con swap</string>
|
||||
<string name="sent_transaction_sent_title">Transacción enviada</string>
|
||||
<string name="settings_card_settings_footer">Escanee la tarjeta/anillo que quiere configurar</string>
|
||||
<string name="settings_forget_wallet">Olvidar la billetera</string>
|
||||
|
|
@ -872,7 +927,7 @@
|
|||
<string name="staking_details_auto_claiming_rewards_daily_text">Las recompensas se acumulan automáticamente en su saldo de apustaking esta cada día.</string>
|
||||
<string name="staking_details_available">Disponible</string>
|
||||
<string name="staking_details_average_reward_rate">Tasa de recompensa promedio</string>
|
||||
<string name="staking_details_banner_text">¿Qué es el Staking?</string>
|
||||
<string name="staking_details_banner_text">¿Cómo funciona el staking?</string>
|
||||
<string name="staking_details_estimated_profit">%s beneficio estimado</string>
|
||||
<string name="staking_details_market_rating">Calificación de mercado</string>
|
||||
<string name="staking_details_metrics_block_header">Métrica</string>
|
||||
|
|
@ -901,14 +956,14 @@
|
|||
<string name="staking_notification_additional_ada_deposit_title">Detalles del staking de ADA</string>
|
||||
<string name="staking_notification_claim_rewards_text">Recomp. won se le enviará y estará disponible para su uso inmediato</string>
|
||||
<string name="staking_notification_earn_rewards_text">Haga staking de forma segura y comience a ganar recompensas</string>
|
||||
<string name="staking_notification_earn_rewards_text_daily">Haga de forma segura y comience a ganar recompensas diarias</string>
|
||||
<string name="staking_notification_earn_rewards_text_hourly">Haga staking de forma segura y comience a ganar recompensas por hora</string>
|
||||
<string name="staking_notification_earn_rewards_text_monthly">Haga staking de forma segura y comience a ganar recompensas mensuales</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_day">El staking le permite ganar %1$s. Sus recompensas por apostar llegan todos los días.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_hour">El staking le permite ganar %1$s. Sus recompensas del staking llegan cada hora.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_month">El staking le permite ganar %1$s. Sus recompensas del staking llegan todos los meses.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_week">El staking le permite ganar %1$s. Sus recompensas por apostar llegan todas las semanas.</string>
|
||||
<string name="staking_notification_earn_rewards_text_weekly">Haga staking de forma segura y comience a ganar recompensas semanales</string>
|
||||
<string name="staking_notification_earn_rewards_text_daily">Tangem permite a los usuarios hacer staking de sus criptomonedas</string>
|
||||
<string name="staking_notification_earn_rewards_text_hourly">Tangem permite a los usuarios hacer staking de sus criptomonedas</string>
|
||||
<string name="staking_notification_earn_rewards_text_monthly">Tangem permite a los usuarios hacer staking de sus criptomonedas</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_day">El staking le permite ganar %1$s. Sus recompensas por staking llegan cada día.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_hour">El staking le permite ganar %1$s. Sus recompensas por staking llegan cada hora.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_month">El staking le permite ganar %1$s. Sus recompensas por staking llegan cada mes.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_week">El staking le permite ganar %1$s. Sus recompensas por staking llegan cada semana.</string>
|
||||
<string name="staking_notification_earn_rewards_text_weekly">Tangem permite a los usuarios hacer staking de sus criptomonedas</string>
|
||||
<string name="staking_notification_earn_rewards_title">Gane recompensas por staking</string>
|
||||
<string name="staking_notification_low_staked_balance_text">Su saldo de staking será demasiado bajo para cancelar el staking. Necesitará hacer staking más para alcanzar la cantidad mínima para cancelarlo.</string>
|
||||
<string name="staking_notification_low_staked_balance_title">Saldo de staking bajo</string>
|
||||
|
|
@ -994,6 +1049,7 @@
|
|||
<string name="story_web3_title">Compatible con Web 3.0</string>
|
||||
<string name="sui_not_enough_coin_for_fee_description">Se requiere una transacción entrante de al menos %1$s para proceder</string>
|
||||
<string name="sui_not_enough_coin_for_fee_title">Fondos insuficientes</string>
|
||||
<string name="swap_fixed_rate">Tasa fija</string>
|
||||
<string name="swap_give_permission_fee_footer">La red cobrará una tarifa de aprobación de token para verificar que está autorizando el uso de su token para el swap.</string>
|
||||
<string name="swap_promo_text">Intercambie más tokens a mejores tasas directamente en su billetera.</string>
|
||||
<string name="swap_promo_title">¡Nuevo proveedor de intercambio disponible!</string>
|
||||
|
|
@ -1007,6 +1063,7 @@
|
|||
<string name="swap_story_second_title">Tarifas inmejorables</string>
|
||||
<string name="swap_story_third_subtitle">Sencillo e intuitivo, permite cambiar tokens con solo unos cuantos toques</string>
|
||||
<string name="swap_story_third_title">Simplemente cómodo</string>
|
||||
<string name="swap_via_provider">Intercambio a través del proveedor</string>
|
||||
<string name="swapping_alert_cex_description">La cantidad incluye:\n• Tarifas del proveedor de servicios\n• Tarifas de red por enviar %s desde el intercambio a la dirección del usuario.</string>
|
||||
<string name="swapping_alert_cex_description_with_slippage">El monto incluye:\n• tarifas del proveedor de servicios\n• tarifas de red por enviar %1$s desde el intercambio a la dirección del usuario. \n\nEl slippage del proveedor puede alcanzar el %2$s</string>
|
||||
<string name="swapping_alert_dex_description">El importe incluye los honorarios del proveedor de servicios.</string>
|
||||
|
|
@ -1067,8 +1124,12 @@
|
|||
<string name="transaction_history_transaction_from_address">desde: %s</string>
|
||||
<string name="transaction_history_transaction_to_address">a: %s</string>
|
||||
<string name="transaction_history_transaction_validator">validador: %s</string>
|
||||
<string name="transaction_notifications_warning_description">Las notificaciones están activadas, pero no funcionarán hasta que permitas las notificaciones en la configuración de tu dispositivo.</string>
|
||||
<string name="transaction_notifications_warning_title">Notificaciones de transacciones</string>
|
||||
<string name="transfer_min_amount_error">Mínimo %s</string>
|
||||
<string name="transfer_notification_invalid_minimum_transaction_amount_text">El monto mínimo para realizar esta transacción es %1$s.</string>
|
||||
<string name="tron_will_be_send_token_fee_description">Las tarifas de la red Tron para tokens populares pueden ser más altas. Hacer staking de TRX puede ayudar a reducir los costos de transacción.</string>
|
||||
<string name="tron_will_be_send_token_fee_title">Ahorra en las tarifas de la red Tron</string>
|
||||
<string name="try_to_load_data_again_button_title">Inténtelo de nuevo</string>
|
||||
<string name="twin_error_same_card">Ha escaneado la misma tarjeta. Para crear una billetera gemela, necesite escanear la tarjeta con no. %d</string>
|
||||
<string name="twin_error_wrong_twin">Ha escaneado la tarjeta gemela incorrecta. Por favor, intente con otra</string>
|
||||
|
|
@ -1081,14 +1142,17 @@
|
|||
<string name="twins_recreate_toolbar">Tangem Twin</string>
|
||||
<string name="twins_recreate_warning">Esta acción es irreversible. No tendrá acceso la billetera antigua.</string>
|
||||
<string name="twins_scan_twin_with_number">Toque la tarjeta gemela con el número %s y no la retira hasta el final de la operación</string>
|
||||
<string name="unexpected_error_description">Por favor, inténtelo de nuevo más tarde. Si el problema persiste, póngase en contacto con el servicio de asistencia.</string>
|
||||
<string name="unexpected_error_title">¡Algo salió mal!</string>
|
||||
<string name="universal_error">Hemos encontrado un error. Código de error: %s. Póngase en contacto con nuestro servicio de asistencia.</string>
|
||||
<string name="unlock_wallet_description_full">Use %s o escanee una tarjeta/anillo para tener acceso a su billetera</string>
|
||||
<string name="unsupported_wc_version">Error de conexión: Esta dApp utiliza la versión 1.0 de Wallet Connect, que no es compatible. Asegúrese de que la dApp sea compatible con la versión 2.0 de Wallet Connect para conectarse correctamente.</string>
|
||||
<string name="user_push_notification_agreement_argument_one">Manténgase actualizado con las últimas funciones y noticias</string>
|
||||
<string name="user_push_notification_agreement_argument_three">Recibir notificaciones de las transacciones entrantes</string>
|
||||
<string name="user_push_notification_agreement_argument_two">Sea el primero en enterarte de nuevas promociones</string>
|
||||
<string name="user_push_notification_agreement_header">¿Quiere utilizar\nnotificaciones push?</string>
|
||||
<string name="user_wallet_list_add_button">Agregar una nueva billetera</string>
|
||||
<string name="user_wallet_list_delete_prompt">¿Está seguro(a) de que quieres eliminar esta billetera?</string>
|
||||
<string name="user_wallet_list_delete_prompt">¿Estás seguro de que deseas olvidar esta billetera?</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">Ha ocurrido un error, por favor escanee su tarjeta o anillo para iniciar sesión</string>
|
||||
<string name="user_wallet_list_error_wallet_already_saved">Esta billetera ya se ha guardado, puede agregar otro</string>
|
||||
<string name="user_wallet_list_rename_popup_error_already_exists">La billetera con el nombre %s ya existe</string>
|
||||
|
|
@ -1131,6 +1195,10 @@
|
|||
<string name="wallet_connect_subtitle">Conectar a dApps</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
<string name="wallet_connect_toast_awaiting_session_proposal">La conexión puede tardar unos segundos.</string>
|
||||
<string name="wallet_import_seed_navtitle">Importar billetera</string>
|
||||
<string name="wallet_import_success_description">La copia de seguridad de tu billetera se realizó con éxito.</string>
|
||||
<string name="wallet_import_success_navtitle">Importar billetera</string>
|
||||
<string name="wallet_import_success_title">Importación completada</string>
|
||||
<string name="wallet_marketplace_block_title">%s Precio de mercado</string>
|
||||
<string name="wallet_marketprice_block_update_time">últimas 24h</string>
|
||||
<string name="wallet_network_group_title">%s red</string>
|
||||
|
|
@ -1139,6 +1207,8 @@
|
|||
<string name="wallet_promo_banner_button_title">Consíguelo ahora con un 10 % de descuento</string>
|
||||
<string name="wallet_promo_banner_description">Accede a más de 13 000 criptomonedas. Compra, vende, intercambia y realiza staking con un solo toque.\nVincula hasta tres tarjetas para hacer copias de seguridad.</string>
|
||||
<string name="wallet_promo_banner_title">Descubre Tangem Wallet</string>
|
||||
<string name="wallet_settings_push_notifications_description">Manténteinformado sobre las transacciones entrantes de la billetera y las actualizaciones de Tangem.</string>
|
||||
<string name="wallet_settings_push_notifications_title">Notificaciones de transacciones</string>
|
||||
<string name="wallet_settings_title">Ajustes de la wallet</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_access_denied_message">Use %s o escanee una tarjeta/anillo para desbloquear el acceso a su billetera</string>
|
||||
|
|
@ -1209,6 +1279,7 @@
|
|||
<string name="warning_rate_app_message">Su opinión nos motiva a hacer Tangem Wallet aún mejor</string>
|
||||
<string name="warning_rate_app_title">¿Disfrutando de Tangem?</string>
|
||||
<string name="warning_receive_blocked_hedera_token_association_required_message">Deba asociar su token antes de recibir tokens</string>
|
||||
<string name="warning_receive_blocked_token_trustline_required_message">Debes abrir una línea de confianza para tu token antes de recibirlo</string>
|
||||
<string name="warning_rent_fee_title">Se requiere tarifa de alquiler de red</string>
|
||||
<string name="warning_seedphrase_action_required_title">Acción requerida</string>
|
||||
<string name="warning_seedphrase_contacted_support">¿Te pusiste en contacto con el servicio de asistencia a través de la aplicación o por correo electrónico en los 7 días posteriores a la creación de la billetera? Si lo hiciste o no estás seguro, sigue y completa las instrucciones.</string>
|
||||
|
|
@ -1224,9 +1295,58 @@
|
|||
<string name="warning_some_networks_unreachable_message">Deslice hacia abajo para refrescar o inténtelo de nuevo más tarde.</string>
|
||||
<string name="warning_some_networks_unreachable_title">Algunas redes no están disponibles</string>
|
||||
<string name="warning_some_token_balances_not_updated">No se han podido actualizar los saldos de algunos tokens</string>
|
||||
<string name="warning_stellar_token_trustline_not_enough_xlm">No hay suficiente %s. Recarga tu cuenta XLM para asociar este token</string>
|
||||
<string name="warning_testnet_card_message">Esta es una tarjeta de Testnet. No puede procesar transacciones y solo deba usarse para pruebas y desarrollo.</string>
|
||||
<string name="warning_testnet_card_title">Solo para fines de prueba</string>
|
||||
<string name="warning_token_balance_not_updated">El saldo podría estar desactualizado. Actualice la página.</string>
|
||||
<string name="warning_token_required_min_coin_reserve">No hay suficiente %1$s. Recarga tu %2$s cuenta para asociar este token</string>
|
||||
<string name="warning_token_trustline_button_title">Habilitar línea de confianza</string>
|
||||
<string name="warning_token_trustline_subtitle">Una línea de confianza debe estar habilitada para recibir este token. La red requiere un %1$s %2$s reserva.</string>
|
||||
<string name="warning_token_trustline_title">Se requiere línea de confianza</string>
|
||||
<string name="wc_alert_audit_malicious_domain">Dominio malicioso</string>
|
||||
<string name="wc_alert_audit_unknown_domain">Dominio desconocido</string>
|
||||
<string name="wc_alert_domain_issues_description">Este dominio no puede ser verificado. Compruebe cuidadosamente la solicitud de aprobación.</string>
|
||||
<string name="wc_alert_session_disconnected_description">Vuelva a su navegador y vuelva a conectarse a través de WalletConnect.</string>
|
||||
<string name="wc_alert_sign_anyway">Firmar de todos modos</string>
|
||||
<string name="wc_alert_unknown_error_description">Código de error: %s. Si el problema persiste - no dude en ponerse en contacto con nuestro soporte.</string>
|
||||
<string name="wc_alert_unknown_error_description_no_error_code">Si el problema persiste, no dudes en ponerte en contacto con nuestro soporte.</string>
|
||||
<string name="wc_alert_unsupported_method_description">Código de error: 8 005. Si el problema persiste, no dudes en contactar con nuestro soporte.</string>
|
||||
<string name="wc_alert_unsupported_method_title">Hemos encontrado un error desconocido</string>
|
||||
<string name="wc_alert_unsupported_networks_description">Actualmente, Tangem no es compatible con una red requerida por %s.</string>
|
||||
<string name="wc_alert_verified_domain_description">Tangem soporta una red requerida por %s</string>
|
||||
<string name="wc_alert_wrong_card_description">Se seleccionó una tarjeta o un anillo incorrectos en la app</string>
|
||||
<string name="wc_allow_to_spend">Permitir gastar</string>
|
||||
<string name="wc_common_address">Dirección</string>
|
||||
<string name="wc_common_connect">Conectar</string>
|
||||
<string name="wc_common_loading">Cargando</string>
|
||||
<string name="wc_common_network">Red</string>
|
||||
<string name="wc_common_networks">Redes</string>
|
||||
<string name="wc_common_unlimited">Ilimitado</string>
|
||||
<string name="wc_common_wallet">Billetera</string>
|
||||
<string name="wc_connected_networks">Redes conectadas</string>
|
||||
<string name="wc_connection_reqeust_can_view_balance">Ver el saldo y la actividad de su billetera</string>
|
||||
<string name="wc_connection_reqeust_cant_sign">Firmar transacciones sin previo aviso</string>
|
||||
<string name="wc_connection_reqeust_request_approval">Solicitar aprobación para transacciones</string>
|
||||
<string name="wc_connection_reqeust_will_not">No podrá</string>
|
||||
<string name="wc_connection_reqeust_would_like">Me gustaría</string>
|
||||
<string name="wc_connection_request">Solicitud de conexión</string>
|
||||
<string name="wc_contents">Contenido</string>
|
||||
<string name="wc_copy_data_button_text">Copiar datos</string>
|
||||
<string name="wc_custom_allowance_title">Asignación personalizada</string>
|
||||
<string name="wc_estimated_wallet_changes">Cambios estimados en la billetera</string>
|
||||
<string name="wc_estimated_wallet_changes_not_simulated">La transacción no ha podido ser simulada. Por favor, proceda con precaución.</string>
|
||||
<string name="wc_malicious_transaction">Transacción maliciosa</string>
|
||||
<string name="wc_missing_required_network_description">Añada la red %s a su perfil para esta billetera</string>
|
||||
<string name="wc_missing_required_network_title">La billetera no tiene las redes requeridas</string>
|
||||
<string name="wc_notification_security_risk_subtitle">Este dominio está marcado como no seguro por varios proveedores de seguridad. Salga de inmediato para proteger sus activos</string>
|
||||
<string name="wc_notification_security_risk_title">Riesgo de seguridad conocido</string>
|
||||
<string name="wc_request_from">Solicitud de</string>
|
||||
<string name="wc_signature_type">Tipo de firma</string>
|
||||
<string name="wc_transaction_info_to_title">A</string>
|
||||
<string name="wc_transaction_request">Solicitud de transacción</string>
|
||||
<string name="wc_transaction_request_title">Solicitud de transacción</string>
|
||||
<string name="wc_unlimited_amount">Cantidad ilimitada</string>
|
||||
<string name="wc_wallet_connect">Wallet connect</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">Ignorar</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">Tiene un backup interrumpido. ¿Quiere reanudarlo?</string>
|
||||
<string name="welcome_interrupted_backup_alert_resume">Sí, reanudar</string>
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
<string name="app_settings_theme_selector_title">テーマ</string>
|
||||
<string name="app_settings_title">アプリ設定</string>
|
||||
<string name="backup_complete_description">ウォレットのバックアップが正常に完了しました。</string>
|
||||
<string name="backup_complete_seed_description">これらの単語は紛失した場合、復元できません。必ず安全な場所に保管してください。</string>
|
||||
<string name="backup_complete_title">バックアップが完了しました</string>
|
||||
<string name="backup_info_description">シークレットリカバリーフレーズは、ウォレットへのアクセスと復元のために使用される%sのランダムな単語のセットです。</string>
|
||||
<string name="backup_info_keep_description">これらの単語は紛失した場合、復元できません。必ず安全な場所に保管してください。</string>
|
||||
|
|
@ -408,6 +409,10 @@
|
|||
<string name="home_button_scan">Tangemをスキャン</string>
|
||||
<string name="hot_crypto_add_token_subtitle">%sへ</string>
|
||||
<string name="hot_crypto_token_network">%sネットワーク</string>
|
||||
<string name="hw_activation_need_backup">今すぐバックアップ</string>
|
||||
<string name="hw_activation_need_description">セットアップを完了するには、ウォレットをバックアップし、アクセスコードを使用してアプリへのアクセスを保護します。</string>
|
||||
<string name="hw_activation_need_finish">今すぐ実施</string>
|
||||
<string name="hw_activation_need_title">ウォレットのアクティベーションを完了する</string>
|
||||
<string name="hw_backup_icloud_description">iCloudバックアップに保存されている既存のウォレットを復元する</string>
|
||||
<string name="hw_backup_icloud_title">iCloudバックアップ</string>
|
||||
<string name="hw_backup_need_action">バックアップへ移動</string>
|
||||
|
|
@ -1156,6 +1161,7 @@
|
|||
<string name="twins_recreate_toolbar">Tangemツイン</string>
|
||||
<string name="twins_recreate_warning">この操作は元に戻せません。古いウォレットにはアクセスできなくなります。</string>
|
||||
<string name="twins_scan_twin_with_number">番号%sのツインカードをタップし、操作が終了するまで取り外さないでください。</string>
|
||||
<string name="unexpected_error_description">しばらくしてからもう一度お試しください。問題が解決しない場合は、サポートにお問い合わせください。</string>
|
||||
<string name="unexpected_error_title">問題が発生しました。</string>
|
||||
<string name="universal_error">エラーが発生しました。エラー コード:%s。サポートにお問い合わせください。</string>
|
||||
<string name="unlock_wallet_description_full">%sを使用するか、カード / リングをスキャンしてウォレットにアクセスしてください</string>
|
||||
|
|
@ -1349,17 +1355,15 @@
|
|||
<string name="warning_some_networks_unreachable_message">下にスワイプして更新するか、後でもう一度お試しください。</string>
|
||||
<string name="warning_some_networks_unreachable_title">一部のネットワークにアクセスできません</string>
|
||||
<string name="warning_some_token_balances_not_updated">一部のトークン残高を更新できませんでした</string>
|
||||
<string name="warning_stellar_token_trustline_button_title">トラストラインを有効にする</string>
|
||||
<string name="warning_stellar_token_trustline_not_enough_xlm">%sが足りません。このトークンを関連付けるには、XLMアカウントに資金を追加してください。</string>
|
||||
<string name="warning_stellar_token_trustline_subtitle">このトークンを受け取るには、トラストラインを有効にする必要があります。ネットワークには 0.5 XLMの準備金が必要です。</string>
|
||||
<string name="warning_stellar_token_trustline_title">トラストラインが必要</string>
|
||||
<string name="warning_testnet_card_message">これはテストネットカードです。取引処理はできませんので、テストおよび開発目的でのみご利用ください。</string>
|
||||
<string name="warning_testnet_card_title">テスト目的のみ</string>
|
||||
<string name="warning_token_balance_not_updated">残高が古い可能性があります。ページを更新してください。</string>
|
||||
<string name="warning_token_required_min_coin_reserve">%1$sが足りません。このトークンを関連付けるには、XLMアカウントにチャージしてください。</string>
|
||||
<string name="warning_token_required_min_coin_reserve">%1$sが足りません。このトークンを関連付けるには、 %2$sアカウントにチャージしてください。</string>
|
||||
<string name="warning_token_trustline_button_title">トラストラインを有効にする</string>
|
||||
<string name="warning_token_trustline_subtitle">このトークンを受け取るには、トラストラインを有効にする必要があります。ネットワークには%1$s %2$s予備金が必要です。</string>
|
||||
<string name="warning_token_trustline_title">トラストラインが必要</string>
|
||||
<string name="wc_alert_audit_malicious_domain">悪意のあるドメイン</string>
|
||||
<string name="wc_alert_audit_unknown_domain">不明なドメイン</string>
|
||||
<string name="wc_alert_connect_anyway">とにかく接続する</string>
|
||||
<string name="wc_alert_connection_timeout_description">タイムアウトエラーが発生しました。しばらくしてからもう一度お試しください。</string>
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@
|
|||
<string name="common_locked">Заблокирован</string>
|
||||
<string name="common_main_network">Основная сеть</string>
|
||||
<string name="common_month">месяц</string>
|
||||
<string name="common_network_fee_title">Сетевая комиссия</string>
|
||||
<string name="common_network_fee_title">Комиссия сети</string>
|
||||
<string name="common_network_fee_warning_content">Сумма отправки будет уменьшена на %1$s (%2$s) для покрытия выбранного уровня комиссии</string>
|
||||
<string name="common_next">Далее</string>
|
||||
<string name="common_nft">NFT</string>
|
||||
|
|
@ -532,7 +532,7 @@
|
|||
<string name="markets_tooltip_message">Потяните вверх или коснитесь поисковой строки, чтобы добавить токены напрямую из рынка</string>
|
||||
<string name="markets_tooltip_title">Добавить токены</string>
|
||||
<string name="nfc_error_unavailable">Функция NFC недоступна на вашем устройстве</string>
|
||||
<string name="nft_about_title">Об NFT</string>
|
||||
<string name="nft_about_title">О NFT</string>
|
||||
<string name="nft_asset">NFT</string>
|
||||
<plurals name="nft_collections_count">
|
||||
<item quantity="one">%d NFT</item>
|
||||
|
|
@ -1261,9 +1261,6 @@
|
|||
<string name="warning_some_networks_unreachable_message">Свайпните вниз для обновления или попробуйте позже.</string>
|
||||
<string name="warning_some_networks_unreachable_title">Некоторые сети недоступны</string>
|
||||
<string name="warning_some_token_balances_not_updated">Некоторые балансы токенов не удалось обновить</string>
|
||||
<string name="warning_stellar_token_trustline_button_title">Открыть Trustline</string>
|
||||
<string name="warning_stellar_token_trustline_subtitle">Чтобы получить этот токен, необходимо открыть Trustline. Сеть удержит резерв в размере 0.5 XLM.</string>
|
||||
<string name="warning_stellar_token_trustline_title">Откройте Trustline</string>
|
||||
<string name="warning_testnet_card_message">Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки.</string>
|
||||
<string name="warning_testnet_card_title">Только для целей тестирования</string>
|
||||
<string name="warning_token_balance_not_updated">Возможно, баланс устарел. Обновите страницу.</string>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue