Updated on 2026-08-14
This commit is contained in:
commit
c2cd7e6b4c
675 changed files with 9577 additions and 4375 deletions
|
|
@ -215,6 +215,7 @@ dependencies {
|
||||||
implementation(projects.data.walletManager)
|
implementation(projects.data.walletManager)
|
||||||
implementation(projects.data.yieldSupply)
|
implementation(projects.data.yieldSupply)
|
||||||
implementation(projects.data.hotWallet)
|
implementation(projects.data.hotWallet)
|
||||||
|
implementation(projects.data.news)
|
||||||
|
|
||||||
/** Features */
|
/** Features */
|
||||||
implementation(projects.features.referral.impl)
|
implementation(projects.features.referral.impl)
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,91 @@ import dagger.hilt.android.testing.OnComponentReadyRunner
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
import org.junit.runner.Description
|
import org.junit.runner.Description
|
||||||
import org.junit.runners.model.Statement
|
import org.junit.runners.model.Statement
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class ApplicationInjectionExecutionRule : TestRule {
|
class ApplicationInjectionExecutionRule(
|
||||||
|
private val toggleStates: Map<String, Boolean>
|
||||||
|
) : TestRule {
|
||||||
|
|
||||||
private val tangemApplication: TangemApplication
|
private val tangemApplication: TangemApplication
|
||||||
get() = ApplicationProvider.getApplicationContext()
|
get() = ApplicationProvider.getApplicationContext()
|
||||||
|
|
||||||
|
private var originalFeatureTogglesValues: Map<String, String>? = null
|
||||||
|
|
||||||
override fun apply(base: Statement, description: Description): Statement {
|
override fun apply(base: Statement, description: Description): Statement {
|
||||||
return object : Statement() {
|
return object : Statement() {
|
||||||
override fun evaluate() {
|
override fun evaluate() {
|
||||||
|
saveOriginalFeatureToggles()
|
||||||
|
overrideFeatureToggles()
|
||||||
|
|
||||||
OnComponentReadyRunner.addListener(
|
OnComponentReadyRunner.addListener(
|
||||||
tangemApplication, ApplicationEntryPoint::class.java
|
tangemApplication, ApplicationEntryPoint::class.java
|
||||||
) { _: ApplicationEntryPoint ->
|
) { _: ApplicationEntryPoint ->
|
||||||
tangemApplication.preInit()
|
tangemApplication.preInit()
|
||||||
tangemApplication.init()
|
tangemApplication.init()
|
||||||
}
|
}
|
||||||
base.evaluate()
|
|
||||||
|
try {
|
||||||
|
base.evaluate()
|
||||||
|
} finally {
|
||||||
|
restoreOriginalFeatureToggles()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun saveOriginalFeatureToggles() {
|
||||||
|
try {
|
||||||
|
val featureTogglesClass = Class.forName("com.tangem.core.configtoggle.FeatureToggles")
|
||||||
|
val valuesField = featureTogglesClass.getDeclaredField("values")
|
||||||
|
valuesField.isAccessible = true
|
||||||
|
originalFeatureTogglesValues = valuesField.get(null) as Map<String, String>
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e("Failed to save original toggles values: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun overrideFeatureToggles() {
|
||||||
|
try {
|
||||||
|
val featureTogglesClass = Class.forName("com.tangem.core.configtoggle.FeatureToggles")
|
||||||
|
val valuesField = featureTogglesClass.getDeclaredField("values")
|
||||||
|
valuesField.isAccessible = true
|
||||||
|
|
||||||
|
val originalValues = originalFeatureTogglesValues ?:
|
||||||
|
(valuesField.get(null) as Map<String, String>)
|
||||||
|
|
||||||
|
val newValues = originalValues.toMutableMap()
|
||||||
|
|
||||||
|
toggleStates.forEach { (toggle, enabled) ->
|
||||||
|
if (enabled) {
|
||||||
|
newValues[toggle] = "1.0.0"
|
||||||
|
} else {
|
||||||
|
newValues.remove(toggle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
valuesField.set(null, newValues)
|
||||||
|
|
||||||
|
Timber.i("FeatureToggles.values updated: $toggleStates")
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e("FeatureToggles.values didn't change with error: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreOriginalFeatureToggles() {
|
||||||
|
try {
|
||||||
|
if (originalFeatureTogglesValues != null) {
|
||||||
|
val featureTogglesClass = Class.forName("com.tangem.core.configtoggle.FeatureToggles")
|
||||||
|
val valuesField = featureTogglesClass.getDeclaredField("values")
|
||||||
|
valuesField.isAccessible = true
|
||||||
|
valuesField.set(null, originalFeatureTogglesValues)
|
||||||
|
Timber.i("FeatureToggles.values restored")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e("FeatureToggles.values didn't restored with error: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,8 +16,6 @@ import com.tangem.common.allure.FailedStepScreenshotInterceptor
|
||||||
import com.tangem.common.constants.TestConstants.ALLURE_LABEL_NAME
|
import com.tangem.common.constants.TestConstants.ALLURE_LABEL_NAME
|
||||||
import com.tangem.common.constants.TestConstants.ALLURE_LABEL_VALUE
|
import com.tangem.common.constants.TestConstants.ALLURE_LABEL_VALUE
|
||||||
import com.tangem.common.rules.ApiEnvironmentRule
|
import com.tangem.common.rules.ApiEnvironmentRule
|
||||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
|
||||||
import com.tangem.core.configtoggle.feature.MutableFeatureTogglesManager
|
|
||||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
|
|
@ -53,9 +51,6 @@ abstract class BaseTestCase : TestCase(
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appPreferencesStore: AppPreferencesStore
|
lateinit var appPreferencesStore: AppPreferencesStore
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var featureTogglesManager: FeatureTogglesManager
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var promoRepository: PromoRepository
|
lateinit var promoRepository: PromoRepository
|
||||||
|
|
||||||
|
|
@ -75,7 +70,7 @@ abstract class BaseTestCase : TestCase(
|
||||||
@JvmField
|
@JvmField
|
||||||
val ruleChain: TestRule = RuleChain
|
val ruleChain: TestRule = RuleChain
|
||||||
.outerRule(hiltRule)
|
.outerRule(hiltRule)
|
||||||
.around(ApplicationInjectionExecutionRule())
|
.around(applicationInjectionRule())
|
||||||
.around(permissionRule)
|
.around(permissionRule)
|
||||||
.around(apiEnvironmentRule)
|
.around(apiEnvironmentRule)
|
||||||
.around(composeTestRule)
|
.around(composeTestRule)
|
||||||
|
|
@ -110,7 +105,6 @@ abstract class BaseTestCase : TestCase(
|
||||||
apiEnvironmentRule.setup(apiConfigsManager)
|
apiEnvironmentRule.setup(apiConfigsManager)
|
||||||
ActivityScenario.launch(MainActivity::class.java)
|
ActivityScenario.launch(MainActivity::class.java)
|
||||||
Intents.init()
|
Intents.init()
|
||||||
setFeatureToggles()
|
|
||||||
additionalBeforeSection()
|
additionalBeforeSection()
|
||||||
}.after {
|
}.after {
|
||||||
additionalAfterSection()
|
additionalAfterSection()
|
||||||
|
|
@ -141,14 +135,15 @@ abstract class BaseTestCase : TestCase(
|
||||||
|
|
||||||
fun waitForIdle() = composeTestRule.waitForIdle()
|
fun waitForIdle() = composeTestRule.waitForIdle()
|
||||||
|
|
||||||
private fun setFeatureToggles() {
|
private fun applicationInjectionRule(): ApplicationInjectionExecutionRule {
|
||||||
runBlocking {
|
return ApplicationInjectionExecutionRule(
|
||||||
with(featureTogglesManager as MutableFeatureTogglesManager) {
|
toggleStates = mapOf(
|
||||||
changeToggle("NEW_TOKEN_RECEIVE_ENABLED", true)
|
"NEW_TOKEN_RECEIVE_ENABLED" to true,
|
||||||
changeToggle("WALLET_BALANCE_FETCHER_ENABLED", true)
|
"WALLET_BALANCE_FETCHER_ENABLED" to true,
|
||||||
changeToggle("SWAP_REDESIGN_ENABLED", true)
|
"SWAP_REDESIGN_ENABLED" to true,
|
||||||
changeToggle("NEW_ONRAMP_MAIN_ENABLED", true)
|
"NEW_ONRAMP_MAIN_ENABLED" to true,
|
||||||
}
|
"HOT_WALLET_ENABLED" to true
|
||||||
}
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -26,8 +26,11 @@ fun BaseTestCase.scanCard(
|
||||||
step("Click on 'Accept' button") {
|
step("Click on 'Accept' button") {
|
||||||
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Click on 'Scan' button") {
|
step("Click on 'Get started' button") {
|
||||||
onStoriesScreen { scanButton.clickWithAssertion() }
|
onStoriesScreen { getStartedButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Click on 'Scan card or ring' button") {
|
||||||
|
onCreateWalletStartScreen { scanCardOrRingButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
if (alreadyActivatedDialogIsShown) {
|
if (alreadyActivatedDialogIsShown) {
|
||||||
step("Click on 'This is my wallet' button") {
|
step("Click on 'This is my wallet' button") {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import com.tangem.features.onboarding.v2.impl.R as OnboardingImplR
|
||||||
|
|
||||||
|
class CreateWalletStartPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<CreateWalletStartPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
|
val scanCardOrRingButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.TEXT)
|
||||||
|
hasText(getResourceString(OnboardingImplR.string.welcome_unlock_card))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun BaseTestCase.onCreateWalletStartScreen(function: CreateWalletStartPageObject.() -> Unit) =
|
||||||
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -42,6 +42,7 @@ class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||||
hasText(getResourceString(R.string.app_settings_title))
|
hasText(getResourceString(R.string.app_settings_title))
|
||||||
}
|
}
|
||||||
|
|
||||||
val contactSupportButton: KNode = child {
|
val contactSupportButton: KNode = child {
|
||||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||||
hasText(getResourceString(R.string.common_contact_support))
|
hasText(getResourceString(R.string.common_contact_support))
|
||||||
|
|
@ -50,6 +51,11 @@ class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||||
hasText(getResourceString(R.string.disclaimer_title))
|
hasText(getResourceString(R.string.disclaimer_title))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val versionName: KNode = child {
|
||||||
|
hasTestTag(DetailsScreenTestTags.VERSION_NAME)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun BaseTestCase.onDetailsScreen(function: DetailsPageObject.() -> Unit) =
|
internal fun BaseTestCase.onDetailsScreen(function: DetailsPageObject.() -> Unit) =
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose
|
||||||
import io.github.kakaocup.compose.node.element.KNode
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
import com.tangem.features.send.v2.impl.R as SendR
|
import com.tangem.features.send.v2.impl.R as SendR
|
||||||
|
import androidx.compose.ui.test.hasText as withText
|
||||||
|
|
||||||
class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
ComposeScreen<SendPageObject>(semanticsProvider = semanticsProvider) {
|
ComposeScreen<SendPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
@ -38,6 +39,11 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val amountErrorText: KNode = child {
|
||||||
|
hasTestTag(SendScreenTestTags.AMOUNT_ERROR_TEXT)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
val equivalentInputAmount: KNode = child {
|
val equivalentInputAmount: KNode = child {
|
||||||
hasTestTag(SendScreenTestTags.EQUIVALENT_INPUT_AMOUNT)
|
hasTestTag(SendScreenTestTags.EQUIVALENT_INPUT_AMOUNT)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
|
|
@ -69,8 +75,8 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
}
|
}
|
||||||
|
|
||||||
val nextButton: KNode = child {
|
val nextButton: KNode = child {
|
||||||
hasTestTag(BaseButtonTestTags.TEXT)
|
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||||
hasText(getResourceString(SendR.string.common_next))
|
hasAnyDescendant(withText(getResourceString(SendR.string.common_next)))
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,20 @@ package com.tangem.screens
|
||||||
|
|
||||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.core.ui.test.StoriesScreenTestTags
|
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||||
|
import com.tangem.features.onboarding.v2.impl.R
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.KNode
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
|
||||||
class StoriesPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
class StoriesPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
ComposeScreen<StoriesPageObject>(semanticsProvider = semanticsProvider) {
|
ComposeScreen<StoriesPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
val scanButton: KNode = child {
|
val getStartedButton: KNode = child {
|
||||||
hasTestTag(StoriesScreenTestTags.SCAN_BUTTON)
|
hasTestTag(BaseButtonTestTags.TEXT)
|
||||||
}
|
hasText(getResourceString(R.string.common_get_started))
|
||||||
|
useUnmergedTree = true
|
||||||
val orderButton: KNode = child {
|
|
||||||
hasTestTag(StoriesScreenTestTags.ORDER_BUTTON)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,7 @@ import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.common.extensions.clickWithAssertion
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
import com.tangem.domain.models.scan.ProductType
|
import com.tangem.domain.models.scan.ProductType
|
||||||
import com.tangem.scenarios.openMainScreen
|
import com.tangem.scenarios.openMainScreen
|
||||||
import com.tangem.screens.onDetailsScreen
|
import com.tangem.screens.*
|
||||||
import com.tangem.screens.onReferralProgramScreen
|
|
||||||
import com.tangem.screens.onTopBar
|
|
||||||
import com.tangem.screens.onWalletSettingsScreen
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import io.qameta.allure.kotlin.AllureId
|
import io.qameta.allure.kotlin.AllureId
|
||||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
|
@ -31,9 +28,6 @@ class DetailsTest : BaseTestCase() {
|
||||||
step("Assert 'Wallet connect' button is visible") {
|
step("Assert 'Wallet connect' button is visible") {
|
||||||
walletConnectButton.assertIsDisplayed()
|
walletConnectButton.assertIsDisplayed()
|
||||||
}
|
}
|
||||||
step("Assert 'Scan card' button is visible") {
|
|
||||||
scanCardButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert 'Buy Tangem card' button is visible") {
|
step("Assert 'Buy Tangem card' button is visible") {
|
||||||
buyTangemButton.assertIsDisplayed()
|
buyTangemButton.assertIsDisplayed()
|
||||||
}
|
}
|
||||||
|
|
@ -131,9 +125,6 @@ class DetailsTest : BaseTestCase() {
|
||||||
step("Assert 'Wallet connect' button does not exist") {
|
step("Assert 'Wallet connect' button does not exist") {
|
||||||
walletConnectButton.assertIsNotDisplayed()
|
walletConnectButton.assertIsNotDisplayed()
|
||||||
}
|
}
|
||||||
step("Assert 'Scan card' button is visible") {
|
|
||||||
scanCardButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert 'Buy Tangem card' button is visible") {
|
step("Assert 'Buy Tangem card' button is visible") {
|
||||||
buyTangemButton.assertIsDisplayed()
|
buyTangemButton.assertIsDisplayed()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,8 +139,11 @@ class FeedbackTest : BaseTestCase() {
|
||||||
step("Set scanning error") {
|
step("Set scanning error") {
|
||||||
MockProvider.setEmulateError(TangemSdkError.TagLost())
|
MockProvider.setEmulateError(TangemSdkError.TagLost())
|
||||||
}
|
}
|
||||||
step("Click on 'Scan' button") {
|
step("Click on 'Get started' button") {
|
||||||
onStoriesScreen { scanButton.performClick() }
|
onStoriesScreen { getStartedButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Click on 'Scan card or ring' button") {
|
||||||
|
onCreateWalletStartScreen { scanCardOrRingButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Force show 'Scan warning' dialog"){
|
step("Force show 'Scan warning' dialog"){
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
|
|
@ -178,8 +181,11 @@ class FeedbackTest : BaseTestCase() {
|
||||||
step("Click on 'Accept' button") {
|
step("Click on 'Accept' button") {
|
||||||
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Click on 'Scan' button") {
|
step("Click on 'Get started' button") {
|
||||||
onStoriesScreen { scanButton.clickWithAssertion() }
|
onStoriesScreen { getStartedButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Click on 'Scan card or ring' button") {
|
||||||
|
onCreateWalletStartScreen { scanCardOrRingButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Check 'Already used Wallet' dialog") {
|
step("Check 'Already used Wallet' dialog") {
|
||||||
checkAlreadyUsedWalletDialog()
|
checkAlreadyUsedWalletDialog()
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package com.tangem.tests
|
|
||||||
|
|
||||||
import com.tangem.common.BaseTestCase
|
|
||||||
import com.tangem.common.extensions.clickWithAssertion
|
|
||||||
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 org.junit.Test
|
|
||||||
|
|
||||||
@HiltAndroidTest
|
|
||||||
class ScanErrorTest : BaseTestCase() {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun goToMainTest() =
|
|
||||||
setupHooks().run {
|
|
||||||
onDisclaimerScreen {
|
|
||||||
step("Click on 'Accept' button") {
|
|
||||||
acceptButton.clickWithAssertion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onStoriesScreen {
|
|
||||||
step("Click on 'Scan' button emulating scan error") {
|
|
||||||
MockProvider.setEmulateError()
|
|
||||||
scanButton.clickWithAssertion()
|
|
||||||
}
|
|
||||||
step("Click on 'Scan' button again without emulating error") {
|
|
||||||
MockProvider.resetEmulateError()
|
|
||||||
scanButton.clickWithAssertion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onMainScreen {
|
|
||||||
step("Make sure wallet screen is visible") {
|
|
||||||
assertIsDisplayed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
package com.tangem.tests
|
|
||||||
|
|
||||||
import android.content.Intent.ACTION_VIEW
|
|
||||||
import com.tangem.common.BaseTestCase
|
|
||||||
import com.tangem.common.extensions.clickWithAssertion
|
|
||||||
import com.tangem.screens.onDisclaimerScreen
|
|
||||||
import com.tangem.screens.onStoriesScreen
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
|
||||||
import io.github.kakaocup.kakao.intent.KIntent
|
|
||||||
|
|
||||||
@HiltAndroidTest
|
|
||||||
class StoriesTest : BaseTestCase() {
|
|
||||||
|
|
||||||
// @Test
|
|
||||||
fun clickOnOrderButtonTest() =
|
|
||||||
setupHooks().run {
|
|
||||||
val buyWalletUrl = "https://buy.tangem.com/"
|
|
||||||
onDisclaimerScreen {
|
|
||||||
step("Click on 'Accept' button") {
|
|
||||||
acceptButton.clickWithAssertion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onStoriesScreen {
|
|
||||||
step("Click on 'Order' button") {
|
|
||||||
orderButton.clickWithAssertion()
|
|
||||||
}
|
|
||||||
step("Assert: browser opened") {
|
|
||||||
val expectedIntent = KIntent {
|
|
||||||
hasAction(ACTION_VIEW)
|
|
||||||
hasData { toString().startsWith(buyWalletUrl) }
|
|
||||||
}
|
|
||||||
expectedIntent.intended()
|
|
||||||
device.uiDevice.pressBack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -37,8 +37,7 @@ class TermsOfServiceTest : BaseTestCase() {
|
||||||
}
|
}
|
||||||
step("Assert 'Stories' screen is opened") {
|
step("Assert 'Stories' screen is opened") {
|
||||||
onStoriesScreen {
|
onStoriesScreen {
|
||||||
scanButton.assertIsDisplayed()
|
getStartedButton.assertIsDisplayed()
|
||||||
orderButton.assertIsDisplayed()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,8 +90,7 @@ class TermsOfServiceTest : BaseTestCase() {
|
||||||
}
|
}
|
||||||
step("Assert 'Stories' screen is opened") {
|
step("Assert 'Stories' screen is opened") {
|
||||||
onStoriesScreen {
|
onStoriesScreen {
|
||||||
scanButton.assertIsDisplayed()
|
getStartedButton.assertIsDisplayed()
|
||||||
orderButton.assertIsDisplayed()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
step("Stop app") {
|
step("Stop app") {
|
||||||
|
|
@ -103,8 +101,7 @@ class TermsOfServiceTest : BaseTestCase() {
|
||||||
}
|
}
|
||||||
step("Assert 'Stories' screen is opened") {
|
step("Assert 'Stories' screen is opened") {
|
||||||
onStoriesScreen {
|
onStoriesScreen {
|
||||||
scanButton.assertIsDisplayed()
|
getStartedButton.assertIsDisplayed()
|
||||||
orderButton.assertIsDisplayed()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -276,7 +276,10 @@ class RecentBlockTest : BaseTestCase() {
|
||||||
}
|
}
|
||||||
step("Swipe up") {
|
step("Swipe up") {
|
||||||
waitForIdle()
|
waitForIdle()
|
||||||
swipeVertical(SwipeDirection.UP, startHeightRatio = 0.6f)
|
onSendAddressScreen {
|
||||||
|
swipeVertical(SwipeDirection.UP, startHeightRatio = 0.6f)
|
||||||
|
swipeVertical(SwipeDirection.UP, startHeightRatio = 0.6f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
step("Check recent address item №7") {
|
step("Check recent address item №7") {
|
||||||
checkRecentAddressItem(address = recipientAddressBase + "f", description = recentTransactionAmount2)
|
checkRecentAddressItem(address = recipientAddressBase + "f", description = recentTransactionAmount2)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
package com.tangem.tests.send.amountScreen
|
||||||
|
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.utils.setClipboardText
|
||||||
|
import com.tangem.scenarios.*
|
||||||
|
import com.tangem.screens.*
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class SendAmountScreenTest : BaseTestCase() {
|
||||||
|
|
||||||
|
@AllureId("4761")
|
||||||
|
@DisplayName("Send (amount screen): validate different amounts")
|
||||||
|
@Test
|
||||||
|
fun validateDifferentAmountsTest() {
|
||||||
|
val tokenName = "Ethereum"
|
||||||
|
val manualSendAmount = "1"
|
||||||
|
val clipboardSendAmount = "0.5"
|
||||||
|
val invalidAmount = "2"
|
||||||
|
val errorText = getResourceString(R.string.send_validation_amount_exceeds_balance)
|
||||||
|
val context = device.context
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Send' screen") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$manualSendAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(manualSendAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert send amount = '$manualSendAmount'") {
|
||||||
|
onSendScreen { amountInputTextField.assertTextContains(manualSendAmount, substring = true) }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is enabled") {
|
||||||
|
onSendScreen { nextButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
step("Press system 'Delete' button") {
|
||||||
|
waitForIdle()
|
||||||
|
device.uiDevice.pressDelete()
|
||||||
|
}
|
||||||
|
step("Set clipboard text") {
|
||||||
|
setClipboardText(context, clipboardSendAmount)
|
||||||
|
}
|
||||||
|
step("Click on input text field") {
|
||||||
|
onSendScreen { amountInputTextField.performClick() }
|
||||||
|
}
|
||||||
|
step("Paste from clipboard") {
|
||||||
|
device.uiDevice.pressKeyCode(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON)
|
||||||
|
}
|
||||||
|
step("Assert send amount = '$clipboardSendAmount'") {
|
||||||
|
onSendScreen { amountInputTextField.assertTextContains(clipboardSendAmount, substring = true) }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is enabled") {
|
||||||
|
onSendScreen { nextButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
step("Press system 'Delete' button") {
|
||||||
|
waitForIdle()
|
||||||
|
device.uiDevice.pressDelete()
|
||||||
|
}
|
||||||
|
step("Type '$invalidAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(invalidAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert send amount = '$invalidAmount'") {
|
||||||
|
onSendScreen { amountInputTextField.assertTextContains(invalidAmount, substring = true) }
|
||||||
|
}
|
||||||
|
step("Assert amount error contains text '$errorText'") {
|
||||||
|
onSendScreen { amountErrorText.assertTextContains(errorText) }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is disabled") {
|
||||||
|
onSendScreen { nextButton.assertIsNotEnabled() }
|
||||||
|
}
|
||||||
|
step("Click on 'Max' button") {
|
||||||
|
onSendScreen { maxButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert send amount = '$manualSendAmount'") {
|
||||||
|
onSendScreen { amountInputTextField.assertTextContains(manualSendAmount, substring = true) }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is enabled") {
|
||||||
|
onSendScreen { nextButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
step("Click on 'Close' button") {
|
||||||
|
onSendScreen { closeButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Token Details' screen is displayed") {
|
||||||
|
onTokenDetailsScreen { screenContainer.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4762")
|
||||||
|
@DisplayName("Send (amount screen): switch equivalent")
|
||||||
|
@Test
|
||||||
|
fun switchEquivalentTest() {
|
||||||
|
val tokenName = "Ethereum"
|
||||||
|
val tokenAmount = "1"
|
||||||
|
val fiatAmount = "$2,535.63"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Send' screen") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$tokenAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(tokenAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert send amount = '$tokenAmount'") {
|
||||||
|
onSendScreen { amountInputTextField.assertTextContains(tokenAmount, substring = true) }
|
||||||
|
}
|
||||||
|
step("Assert equivalent amount = '$fiatAmount'") {
|
||||||
|
onSendScreen { equivalentInputAmount.assertTextContains(fiatAmount, substring = true) }
|
||||||
|
}
|
||||||
|
step("Click on 'Exchange' button") {
|
||||||
|
onSendScreen { exchangeIcon.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert send amount = '$fiatAmount'") {
|
||||||
|
onSendScreen { amountInputTextField.assertTextContains(fiatAmount, substring = true) }
|
||||||
|
}
|
||||||
|
step("Assert equivalent amount = '$tokenAmount'") {
|
||||||
|
onSendScreen { equivalentInputAmount.assertTextContains(tokenAmount, substring = true) }
|
||||||
|
}
|
||||||
|
step("Click on 'Exchange' button") {
|
||||||
|
onSendScreen { exchangeIcon.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert send amount = '$tokenAmount'") {
|
||||||
|
onSendScreen { amountInputTextField.assertTextContains(tokenAmount, substring = true) }
|
||||||
|
}
|
||||||
|
step("Assert equivalent amount = '$fiatAmount'") {
|
||||||
|
onSendScreen { equivalentInputAmount.assertTextContains(fiatAmount, substring = true) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ class SolanaWarningsTest : BaseTestCase() {
|
||||||
private val tokenName = "Solana"
|
private val tokenName = "Solana"
|
||||||
private val amountToLeaveLessThanRent = "0.0016941"
|
private val amountToLeaveLessThanRent = "0.0016941"
|
||||||
private val amountToLeaveGreaterThanRent = "0.0000941"
|
private val amountToLeaveGreaterThanRent = "0.0000941"
|
||||||
private val amountToLeaveRentOnly = "0.00168934"
|
private val amountToLeaveRentOnly = "0.001689338"
|
||||||
private val rentAmount = "SOL 0.00089088"
|
private val rentAmount = "SOL 0.00089088"
|
||||||
|
|
||||||
private val invalidAmountTitle = getResourceString(R.string.send_notification_invalid_amount_title)
|
private val invalidAmountTitle = getResourceString(R.string.send_notification_invalid_amount_title)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.nfc"
|
android:name="android.hardware.nfc"
|
||||||
android:required="true" />
|
android:required="false" />
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
|
|
|
||||||
|
|
@ -367,7 +367,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
|
|
||||||
val isFromPush = intent.extras?.containsKey(OPENED_FROM_GCM_PUSH) == true
|
val isFromPush = intent.extras?.containsKey(OPENED_FROM_GCM_PUSH) == true
|
||||||
if (isFromPush) {
|
if (isFromPush) {
|
||||||
analyticsEventsHandler.send(Push.PushNotificationOpened)
|
analyticsEventsHandler.send(Push.PushNotificationOpened())
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeepLink(intent = intent, isFromOnNewIntent = true)
|
handleDeepLink(intent = intent, isFromOnNewIntent = true)
|
||||||
|
|
|
||||||
|
|
@ -328,7 +328,7 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
||||||
|
|
||||||
ExceptionHandler.append(blockchainExceptionHandler)
|
ExceptionHandler.append(blockchainExceptionHandler)
|
||||||
|
|
||||||
if (LogConfig.network.blockchainSdkNetwork) {
|
if (LogConfig.network.isBlockchainSdkNetworkLogEnabled) {
|
||||||
BlockchainSdkRetrofitBuilder.interceptors = listOf(
|
BlockchainSdkRetrofitBuilder.interceptors = listOf(
|
||||||
createNetworkLoggingInterceptor(),
|
createNetworkLoggingInterceptor(),
|
||||||
ChuckerInterceptor(this),
|
ChuckerInterceptor(this),
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,6 @@ internal class DefaultTrackingContextProxy(private val abTestsManager: ABTestsMa
|
||||||
override fun setContext(scanResponse: ScanResponse) {
|
override fun setContext(scanResponse: ScanResponse) {
|
||||||
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||||
|
|
||||||
Analytics.setContext(userWalletId, scanResponse)
|
|
||||||
|
|
||||||
abTestsManager.setUserProperties(
|
abTestsManager.setUserProperties(
|
||||||
userId = calculateUserIdHash(userWalletId),
|
userId = calculateUserIdHash(userWalletId),
|
||||||
batch = scanResponse.card.batchId,
|
batch = scanResponse.card.batchId,
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,6 @@ sealed class AnalyticsParam {
|
||||||
class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
|
class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class CardBalanceState(val value: String) {
|
|
||||||
data object Empty : CardBalanceState("Empty")
|
|
||||||
data object Full : CardBalanceState("Full")
|
|
||||||
companion object
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class RateApp(val value: String) {
|
sealed class RateApp(val value: String) {
|
||||||
data object Liked : RateApp("Liked")
|
data object Liked : RateApp("Liked")
|
||||||
data object Closed : RateApp("Close")
|
data object Closed : RateApp("Close")
|
||||||
|
|
|
||||||
|
|
@ -11,85 +11,5 @@ sealed class Onboarding(
|
||||||
params: Map<String, String> = emptyMap(),
|
params: Map<String, String> = emptyMap(),
|
||||||
) : AnalyticsEvent(category, event, params) {
|
) : AnalyticsEvent(category, event, params) {
|
||||||
|
|
||||||
class Started : Onboarding("Onboarding", "Onboarding Started")
|
|
||||||
class Finished : Onboarding("Onboarding", "Onboarding Finished")
|
class Finished : Onboarding("Onboarding", "Onboarding Finished")
|
||||||
|
|
||||||
sealed class CreateWallet(
|
|
||||||
event: String,
|
|
||||||
params: Map<String, String> = emptyMap(),
|
|
||||||
) : Onboarding("Onboarding / Create Wallet", event, params) {
|
|
||||||
|
|
||||||
class ScreenOpened : CreateWallet("Create Wallet Screen Opened")
|
|
||||||
class ButtonCreateWallet : CreateWallet("Button - Create Wallet")
|
|
||||||
class WalletCreatedSuccessfully(
|
|
||||||
creationType: AnalyticsParam.WalletCreationType = AnalyticsParam.WalletCreationType.PrivateKey,
|
|
||||||
seedPhraseLength: Int? = null,
|
|
||||||
) : CreateWallet(
|
|
||||||
event = "Wallet Created Successfully",
|
|
||||||
params = buildMap {
|
|
||||||
put(AnalyticsParam.CREATION_TYPE, creationType.value)
|
|
||||||
|
|
||||||
if (seedPhraseLength != null) put(AnalyticsParam.SEED_PHRASE_LENGTH, seedPhraseLength.toString())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Backup(
|
|
||||||
event: String,
|
|
||||||
params: Map<String, String> = emptyMap(),
|
|
||||||
) : Onboarding("Onboarding / Backup", event, params) {
|
|
||||||
|
|
||||||
class ScreenOpened : Backup("Backup Screen Opened")
|
|
||||||
class Started : Backup("Backup Started")
|
|
||||||
class Skipped : Backup("Backup Skipped")
|
|
||||||
class SettingAccessCodeStarted : Backup("Setting Access Code Started")
|
|
||||||
class AccessCodeEntered : Backup("Access Code Entered")
|
|
||||||
class AccessCodeReEntered : Backup("Access Code Re-entered")
|
|
||||||
|
|
||||||
class Finished(cardsCount: Int) : Backup(
|
|
||||||
event = "Backup Finished",
|
|
||||||
params = mapOf("Cards count" to "$cardsCount"),
|
|
||||||
)
|
|
||||||
|
|
||||||
object ResetCancelEvent : Backup(
|
|
||||||
event = "Reset Card Notification",
|
|
||||||
params = mapOf("Option" to "Cancel"),
|
|
||||||
)
|
|
||||||
|
|
||||||
object ResetPerformEvent : Backup(
|
|
||||||
event = "Reset Card Notification",
|
|
||||||
params = mapOf("Option" to "Reset"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Topup(
|
|
||||||
event: String,
|
|
||||||
params: Map<String, String> = emptyMap(),
|
|
||||||
) : Onboarding("Onboarding / Top Up", event, params) {
|
|
||||||
|
|
||||||
class ScreenOpened : Topup("Activation Screen Opened")
|
|
||||||
|
|
||||||
class ButtonBuyCrypto(currency: AnalyticsParam.CurrencyType) : Topup(
|
|
||||||
event = "Button - Buy Crypto",
|
|
||||||
params = mapOf(AnalyticsParam.CURRENCY to currency.value),
|
|
||||||
)
|
|
||||||
|
|
||||||
class ButtonShowWalletAddress : Topup("Button - Show the Wallet Address")
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Twins(
|
|
||||||
event: String,
|
|
||||||
params: Map<String, String> = emptyMap(),
|
|
||||||
) : Onboarding("Onboarding / Twins", event, params) {
|
|
||||||
|
|
||||||
class ScreenOpened : Twins("Twinning Screen Opened")
|
|
||||||
class SetupStarted : Twins("Twin Setup Started")
|
|
||||||
class SetupFinished : Twins("Twin Setup Finished")
|
|
||||||
}
|
|
||||||
|
|
||||||
class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding(
|
|
||||||
category = "Onboarding / Biometric",
|
|
||||||
event = "Enable Biometric",
|
|
||||||
params = mapOf("State" to state.value),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
@ -8,5 +8,5 @@ internal sealed class Push(event: String) : AnalyticsEvent(
|
||||||
params = emptyMap(),
|
params = emptyMap(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data object PushNotificationOpened : Push(event = "Push Notification Opened")
|
class PushNotificationOpened : Push(event = "Push Notification Opened")
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +67,7 @@ sealed class Settings(
|
||||||
params = mapOf("State" to state.value),
|
params = mapOf("State" to state.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
object ButtonEnableBiometricAuthentication : AppSettings(event = "Button - Enable Biometric Authentication")
|
class ButtonEnableBiometricAuthentication : AppSettings(event = "Button - Enable Biometric Authentication")
|
||||||
|
|
||||||
class MainCurrencyChanged(currencyType: String) : AppSettings(
|
class MainCurrencyChanged(currencyType: String) : AppSettings(
|
||||||
event = "Main Currency Changed",
|
event = "Main Currency Changed",
|
||||||
|
|
@ -79,7 +79,7 @@ sealed class Settings(
|
||||||
params = mapOf("State" to theme.value),
|
params = mapOf("State" to theme.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
object EnableBiometrics : AppSettings(event = "Notice - Enable Biometric")
|
class EnableBiometrics : AppSettings(event = "Notice - Enable Biometric")
|
||||||
|
|
||||||
class HideBalanceChanged(state: AnalyticsParam.OnOffState) : AppSettings(
|
class HideBalanceChanged(state: AnalyticsParam.OnOffState) : AppSettings(
|
||||||
event = "Hide Balance Changed",
|
event = "Hide Balance Changed",
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ sealed class SignIn(
|
||||||
params: Map<String, String> = emptyMap(),
|
params: Map<String, String> = emptyMap(),
|
||||||
) : AnalyticsEvent("Sign In", event, params) {
|
) : AnalyticsEvent("Sign In", event, params) {
|
||||||
|
|
||||||
class ScreenOpened : SignIn(event = "Sign In Screen Opened")
|
|
||||||
|
|
||||||
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
|
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
|
||||||
class ButtonCardSignIn : SignIn(event = "Button - Card Sign In")
|
class ButtonCardSignIn : SignIn(event = "Button - Card Sign In")
|
||||||
}
|
}
|
||||||
|
|
@ -29,7 +29,7 @@ class AmplitudeAnalyticsHandler(
|
||||||
class Builder : AnalyticsHandlerBuilder {
|
class Builder : AnalyticsHandlerBuilder {
|
||||||
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler {
|
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler {
|
||||||
return AmplitudeAnalyticsHandler(
|
return AmplitudeAnalyticsHandler(
|
||||||
client = if (data.logConfig.amplitude) {
|
client = if (data.logConfig.isAmplitudeLogEnabled) {
|
||||||
AmplitudeLogClient(data.jsonConverter)
|
AmplitudeLogClient(data.jsonConverter)
|
||||||
} else {
|
} else {
|
||||||
AmplitudeClient(data.application, data.config.amplitudeApiKey)
|
AmplitudeClient(data.application, data.config.amplitudeApiKey)
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ class FirebaseAnalyticsHandler(
|
||||||
class Builder : AnalyticsHandlerBuilder {
|
class Builder : AnalyticsHandlerBuilder {
|
||||||
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
|
override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
|
||||||
!data.isDebug -> FirebaseClient()
|
!data.isDebug -> FirebaseClient()
|
||||||
data.isDebug && data.logConfig.firebase -> FirebaseLogClient(data.jsonConverter)
|
data.isDebug && data.logConfig.isFirebaseLogEnabled -> FirebaseLogClient(data.jsonConverter)
|
||||||
else -> null
|
else -> null
|
||||||
}?.let { FirebaseAnalyticsHandler(it) }
|
}?.let { FirebaseAnalyticsHandler(it) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.common.analytics.paramsInterceptor
|
||||||
import com.tangem.core.analytics.api.ParamsInterceptor
|
import com.tangem.core.analytics.api.ParamsInterceptor
|
||||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
|
import com.tangem.core.analytics.models.event.SignIn
|
||||||
import com.tangem.domain.card.analytics.IntroductionProcess
|
import com.tangem.domain.card.analytics.IntroductionProcess
|
||||||
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
||||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||||
|
|
@ -30,7 +31,11 @@ class CardContextInterceptor(
|
||||||
|
|
||||||
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean {
|
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean {
|
||||||
return when (event) {
|
return when (event) {
|
||||||
is IntroductionProcess.ButtonScanCard -> false
|
is IntroductionProcess.ButtonScanCard,
|
||||||
|
is IntroductionProcess.ButtonScanCardLegacy,
|
||||||
|
is SignIn.ScreenOpened,
|
||||||
|
is SignIn.ButtonAddWallet,
|
||||||
|
-> false
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package com.tangem.tap.common.analytics.paramsInterceptor
|
||||||
import com.tangem.core.analytics.api.ParamsInterceptor
|
import com.tangem.core.analytics.api.ParamsInterceptor
|
||||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
|
import com.tangem.core.analytics.models.event.SignIn
|
||||||
|
import com.tangem.domain.card.analytics.IntroductionProcess
|
||||||
|
|
||||||
class HotWalletContextInterceptor(
|
class HotWalletContextInterceptor(
|
||||||
val parent: ParamsInterceptor? = null,
|
val parent: ParamsInterceptor? = null,
|
||||||
|
|
@ -10,10 +12,22 @@ class HotWalletContextInterceptor(
|
||||||
|
|
||||||
override fun id(): String = HotWalletContextInterceptor.id()
|
override fun id(): String = HotWalletContextInterceptor.id()
|
||||||
|
|
||||||
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true
|
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean {
|
||||||
|
return when (event) {
|
||||||
|
is SignIn.ScreenOpened,
|
||||||
|
is SignIn.ButtonAddWallet,
|
||||||
|
is SignIn.ButtonUnlockAllWithBiometric,
|
||||||
|
is IntroductionProcess.ButtonScanCard,
|
||||||
|
-> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun intercept(params: MutableMap<String, String>) {
|
override fun intercept(params: MutableMap<String, String>) {
|
||||||
params[AnalyticsParam.PRODUCT_TYPE] = AnalyticsParam.ProductType.MobileWallet.value
|
params[AnalyticsParam.PRODUCT_TYPE] = AnalyticsParam.ProductType.MobileWallet.value
|
||||||
|
params.remove(AnalyticsParam.BATCH)
|
||||||
|
params.remove(AnalyticsParam.FIRMWARE)
|
||||||
|
params.remove(AnalyticsParam.CURRENCY)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,13 @@ internal object AccountDomainModule {
|
||||||
accountsCRUDRepository: AccountsCRUDRepository,
|
accountsCRUDRepository: AccountsCRUDRepository,
|
||||||
mainAccountTokensMigration: MainAccountTokensMigration,
|
mainAccountTokensMigration: MainAccountTokensMigration,
|
||||||
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
||||||
|
singleAccountListFetcher: SingleAccountListFetcher,
|
||||||
): RecoverCryptoPortfolioUseCase {
|
): RecoverCryptoPortfolioUseCase {
|
||||||
return RecoverCryptoPortfolioUseCase(
|
return RecoverCryptoPortfolioUseCase(
|
||||||
crudRepository = accountsCRUDRepository,
|
crudRepository = accountsCRUDRepository,
|
||||||
mainAccountTokensMigration = mainAccountTokensMigration,
|
mainAccountTokensMigration = mainAccountTokensMigration,
|
||||||
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
|
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
|
||||||
|
singleAccountListFetcher = singleAccountListFetcher,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
import com.tangem.domain.hotwallet.GetAccessCodeSkippedUseCase
|
import com.tangem.domain.hotwallet.GetAccessCodeSkippedUseCase
|
||||||
|
import com.tangem.domain.hotwallet.IsHotWalletCreationSupported
|
||||||
|
import com.tangem.domain.hotwallet.IsAccessCodeSimpleUseCase
|
||||||
import com.tangem.domain.hotwallet.SetAccessCodeSkippedUseCase
|
import com.tangem.domain.hotwallet.SetAccessCodeSkippedUseCase
|
||||||
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -24,4 +26,18 @@ internal object HotWalletDomainModule {
|
||||||
fun provideSetAccessCodeSkippedUseCase(hotWalletRepository: HotWalletRepository): SetAccessCodeSkippedUseCase {
|
fun provideSetAccessCodeSkippedUseCase(hotWalletRepository: HotWalletRepository): SetAccessCodeSkippedUseCase {
|
||||||
return SetAccessCodeSkippedUseCase(hotWalletRepository)
|
return SetAccessCodeSkippedUseCase(hotWalletRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideIsAccessCodeSimpleUseCase(): IsAccessCodeSimpleUseCase {
|
||||||
|
return IsAccessCodeSimpleUseCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideIsWalletCreationSupportedUseCase(
|
||||||
|
hotWalletRepository: HotWalletRepository,
|
||||||
|
): IsHotWalletCreationSupported {
|
||||||
|
return IsHotWalletCreationSupported(hotWalletRepository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||||
import com.tangem.domain.staking.StakingIdFactory
|
import com.tangem.domain.staking.StakingIdFactory
|
||||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||||
|
|
@ -74,7 +74,7 @@ internal object ManageTokensDomainModule {
|
||||||
derivationsRepository: DerivationsRepository,
|
derivationsRepository: DerivationsRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
multiStakingBalanceFetcher: MultiStakingBalanceFetcher,
|
||||||
stakingIdFactory: StakingIdFactory,
|
stakingIdFactory: StakingIdFactory,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): SaveManagedTokensUseCase {
|
): SaveManagedTokensUseCase {
|
||||||
|
|
@ -85,7 +85,7 @@ internal object ManageTokensDomainModule {
|
||||||
derivationsRepository = derivationsRepository,
|
derivationsRepository = derivationsRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
multiStakingBalanceFetcher = multiStakingBalanceFetcher,
|
||||||
stakingIdFactory = stakingIdFactory,
|
stakingIdFactory = stakingIdFactory,
|
||||||
parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.default),
|
parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.default),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||||
import com.tangem.domain.staking.StakingIdFactory
|
import com.tangem.domain.staking.StakingIdFactory
|
||||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||||
|
|
@ -65,20 +66,22 @@ object MarketsDomainModule {
|
||||||
fun provideSaveMarketTokensUseCase(
|
fun provideSaveMarketTokensUseCase(
|
||||||
derivationsRepository: DerivationsRepository,
|
derivationsRepository: DerivationsRepository,
|
||||||
marketsTokenRepository: MarketsTokenRepository,
|
marketsTokenRepository: MarketsTokenRepository,
|
||||||
|
walletManagersFacade: WalletManagersFacade,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
multiStakingBalanceFetcher: MultiStakingBalanceFetcher,
|
||||||
stakingIdFactory: StakingIdFactory,
|
stakingIdFactory: StakingIdFactory,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): SaveMarketTokensUseCase {
|
): SaveMarketTokensUseCase {
|
||||||
return SaveMarketTokensUseCase(
|
return SaveMarketTokensUseCase(
|
||||||
derivationsRepository = derivationsRepository,
|
derivationsRepository = derivationsRepository,
|
||||||
marketsTokenRepository = marketsTokenRepository,
|
marketsTokenRepository = marketsTokenRepository,
|
||||||
|
walletManagersFacade = walletManagersFacade,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
multiStakingBalanceFetcher = multiStakingBalanceFetcher,
|
||||||
stakingIdFactory = stakingIdFactory,
|
stakingIdFactory = stakingIdFactory,
|
||||||
parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.default),
|
parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.default),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
|
||||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||||
import com.tangem.domain.nft.*
|
import com.tangem.domain.nft.*
|
||||||
import com.tangem.domain.nft.repository.NFTRepository
|
import com.tangem.domain.nft.repository.NFTRepository
|
||||||
|
import com.tangem.domain.nft.utils.NFTCleaner
|
||||||
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
|
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
|
||||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||||
|
|
@ -27,12 +27,12 @@ internal object NFTDomainModule {
|
||||||
fun providesGetNFTCollectionsUseCase(
|
fun providesGetNFTCollectionsUseCase(
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
nftRepository: NFTRepository,
|
nftRepository: NFTRepository,
|
||||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
singleAccountListSupplier: SingleAccountListSupplier,
|
||||||
accountsFeatureToggles: AccountsFeatureToggles,
|
accountsFeatureToggles: AccountsFeatureToggles,
|
||||||
): GetNFTCollectionsUseCase = GetNFTCollectionsUseCase(
|
): GetNFTCollectionsUseCase = GetNFTCollectionsUseCase(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
nftRepository = nftRepository,
|
nftRepository = nftRepository,
|
||||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
singleAccountListSupplier = singleAccountListSupplier,
|
||||||
accountsFeatureToggles = accountsFeatureToggles,
|
accountsFeatureToggles = accountsFeatureToggles,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -67,12 +67,12 @@ internal object NFTDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesGetNFTAvailableNetworksUseCase(
|
fun providesGetNFTAvailableNetworksUseCase(
|
||||||
nftRepository: NFTRepository,
|
nftRepository: NFTRepository,
|
||||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
singleAccountListSupplier: SingleAccountListSupplier,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
): GetNFTNetworksUseCase = GetNFTNetworksUseCase(
|
): GetNFTNetworksUseCase = GetNFTNetworksUseCase(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
nftRepository = nftRepository,
|
nftRepository = nftRepository,
|
||||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
singleAccountListSupplier = singleAccountListSupplier,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
@ -126,13 +126,13 @@ internal object NFTDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideDisableWalletNFTUseCase(
|
fun provideDisableWalletNFTUseCase(
|
||||||
walletsRepository: WalletsRepository,
|
walletsRepository: WalletsRepository,
|
||||||
nftRepository: NFTRepository,
|
|
||||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||||
|
nftCleaner: NFTCleaner,
|
||||||
): DisableWalletNFTUseCase {
|
): DisableWalletNFTUseCase {
|
||||||
return DisableWalletNFTUseCase(
|
return DisableWalletNFTUseCase(
|
||||||
walletsRepository = walletsRepository,
|
walletsRepository = walletsRepository,
|
||||||
nftRepository = nftRepository,
|
|
||||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||||
|
nftCleaner = nftCleaner,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,13 +145,13 @@ internal object NFTDomainModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideClearNFTCacheUseCase(
|
fun provideClearNFTCacheUseCase(
|
||||||
nftRepository: NFTRepository,
|
nftCleaner: NFTCleaner,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
accountsFeatureToggles: AccountsFeatureToggles,
|
accountsFeatureToggles: AccountsFeatureToggles,
|
||||||
singleAccountListSupplier: SingleAccountListSupplier,
|
singleAccountListSupplier: SingleAccountListSupplier,
|
||||||
): ObserveAndClearNFTCacheIfNeedUseCase {
|
): ObserveAndClearNFTCacheIfNeedUseCase {
|
||||||
return ObserveAndClearNFTCacheIfNeedUseCase(
|
return ObserveAndClearNFTCacheIfNeedUseCase(
|
||||||
nftRepository = nftRepository,
|
nftCleaner = nftCleaner,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
accountsFeatureToggles = accountsFeatureToggles,
|
accountsFeatureToggles = accountsFeatureToggles,
|
||||||
singleAccountListSupplier = singleAccountListSupplier,
|
singleAccountListSupplier = singleAccountListSupplier,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
import com.tangem.domain.news.repository.NewsRepository
|
import com.tangem.domain.news.repository.NewsRepository
|
||||||
import com.tangem.domain.news.usecase.GetNewsCategoriesUseCase
|
import com.tangem.domain.news.usecase.*
|
||||||
import com.tangem.domain.news.usecase.GetNewsListBatchFlowUseCase
|
|
||||||
import com.tangem.domain.news.usecase.ObserveNewsDetailsUseCase
|
|
||||||
import com.tangem.domain.news.usecase.ManageTrendingNewsUseCase
|
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -38,4 +35,10 @@ internal object NewsDomainModule {
|
||||||
fun provideGetNewsListBatchFlowUseCase(repository: NewsRepository): GetNewsListBatchFlowUseCase {
|
fun provideGetNewsListBatchFlowUseCase(repository: NewsRepository): GetNewsListBatchFlowUseCase {
|
||||||
return GetNewsListBatchFlowUseCase(repository)
|
return GetNewsListBatchFlowUseCase(repository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideFetchTrendingNewsUseCase(repository: NewsRepository): FetchTrendingNewsUseCase {
|
||||||
|
return FetchTrendingNewsUseCase(repository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.di.domain
|
||||||
|
|
||||||
import com.tangem.domain.onramp.*
|
import com.tangem.domain.onramp.*
|
||||||
import com.tangem.domain.onramp.repositories.*
|
import com.tangem.domain.onramp.repositories.*
|
||||||
|
import com.tangem.domain.promo.PromoRepository
|
||||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -264,12 +265,14 @@ internal object OnrampDomainModule {
|
||||||
onrampErrorResolver: OnrampErrorResolver,
|
onrampErrorResolver: OnrampErrorResolver,
|
||||||
onrampTransactionRepository: OnrampTransactionRepository,
|
onrampTransactionRepository: OnrampTransactionRepository,
|
||||||
settingsRepository: SettingsRepository,
|
settingsRepository: SettingsRepository,
|
||||||
|
promoRepository: PromoRepository,
|
||||||
): GetOnrampOffersUseCase {
|
): GetOnrampOffersUseCase {
|
||||||
return GetOnrampOffersUseCase(
|
return GetOnrampOffersUseCase(
|
||||||
onrampRepository = onrampRepository,
|
onrampRepository = onrampRepository,
|
||||||
errorResolver = onrampErrorResolver,
|
errorResolver = onrampErrorResolver,
|
||||||
onrampTransactionRepository = onrampTransactionRepository,
|
onrampTransactionRepository = onrampTransactionRepository,
|
||||||
settingsRepository = settingsRepository,
|
settingsRepository = settingsRepository,
|
||||||
|
promoRepository = promoRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,8 +7,8 @@ import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||||
import com.tangem.domain.staking.repositories.StakeKitRepository
|
import com.tangem.domain.staking.repositories.StakeKitRepository
|
||||||
import com.tangem.domain.staking.repositories.StakingRepository
|
import com.tangem.domain.staking.repositories.StakingRepository
|
||||||
import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository
|
import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository
|
||||||
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
|
||||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||||
|
import com.tangem.domain.staking.single.SingleStakingBalanceFetcher
|
||||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -113,11 +113,11 @@ internal object StakingDomainModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideFetchStakingYieldBalanceUseCase(
|
fun provideFetchStakingYieldBalanceUseCase(
|
||||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
singleStakingBalanceFetcher: SingleStakingBalanceFetcher,
|
||||||
stakingIdFactory: StakingIdFactory,
|
stakingIdFactory: StakingIdFactory,
|
||||||
): FetchStakingYieldBalanceUseCase {
|
): FetchStakingYieldBalanceUseCase {
|
||||||
return FetchStakingYieldBalanceUseCase(
|
return FetchStakingYieldBalanceUseCase(
|
||||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
singleStakingBalanceFetcher = singleStakingBalanceFetcher,
|
||||||
stakingIdFactory = stakingIdFactory,
|
stakingIdFactory = stakingIdFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,18 @@ import com.tangem.domain.quotes.QuotesRepository
|
||||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||||
import com.tangem.domain.staking.StakingIdFactory
|
import com.tangem.domain.staking.StakingIdFactory
|
||||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
||||||
import com.tangem.domain.staking.multi.MultiYieldBalanceSupplier
|
import com.tangem.domain.staking.multi.MultiStakingBalanceSupplier
|
||||||
import com.tangem.domain.staking.repositories.StakingRepository
|
import com.tangem.domain.staking.repositories.StakingRepository
|
||||||
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
import com.tangem.domain.staking.single.SingleStakingBalanceFetcher
|
||||||
import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
import com.tangem.domain.staking.single.SingleStakingBalanceSupplier
|
||||||
import com.tangem.domain.tokens.*
|
import com.tangem.domain.tokens.*
|
||||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||||
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
||||||
import com.tangem.domain.tokens.repository.*
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||||
|
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
|
||||||
|
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
|
||||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
|
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
|
||||||
|
|
@ -40,17 +43,19 @@ internal object TokensDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideAddCryptoCurrenciesUseCase(
|
fun provideAddCryptoCurrenciesUseCase(
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
|
walletManagersFacade: WalletManagersFacade,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
singleStakingBalanceFetcher: SingleStakingBalanceFetcher,
|
||||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||||
stakingIdFactory: StakingIdFactory,
|
stakingIdFactory: StakingIdFactory,
|
||||||
): AddCryptoCurrenciesUseCase {
|
): AddCryptoCurrenciesUseCase {
|
||||||
return AddCryptoCurrenciesUseCase(
|
return AddCryptoCurrenciesUseCase(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
|
walletManagersFacade = walletManagersFacade,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
singleStakingBalanceFetcher = singleStakingBalanceFetcher,
|
||||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||||
stakingIdFactory = stakingIdFactory,
|
stakingIdFactory = stakingIdFactory,
|
||||||
)
|
)
|
||||||
|
|
@ -148,7 +153,7 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
singleStakingBalanceFetcher: SingleStakingBalanceFetcher,
|
||||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||||
stakingIdFactory: StakingIdFactory,
|
stakingIdFactory: StakingIdFactory,
|
||||||
): FetchCurrencyStatusUseCase {
|
): FetchCurrencyStatusUseCase {
|
||||||
|
|
@ -156,7 +161,7 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
singleStakingBalanceFetcher = singleStakingBalanceFetcher,
|
||||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||||
stakingIdFactory = stakingIdFactory,
|
stakingIdFactory = stakingIdFactory,
|
||||||
)
|
)
|
||||||
|
|
@ -339,8 +344,8 @@ internal object TokensDomainModule {
|
||||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||||
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
singleStakingBalanceSupplier: SingleStakingBalanceSupplier,
|
||||||
multiYieldBalanceSupplier: MultiYieldBalanceSupplier,
|
multiStakingBalanceSupplier: MultiStakingBalanceSupplier,
|
||||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||||
stakingIdFactory: StakingIdFactory,
|
stakingIdFactory: StakingIdFactory,
|
||||||
): BaseCurrencyStatusOperations {
|
): BaseCurrencyStatusOperations {
|
||||||
|
|
@ -350,8 +355,8 @@ internal object TokensDomainModule {
|
||||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||||
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
|
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
|
||||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
singleStakingBalanceSupplier = singleStakingBalanceSupplier,
|
||||||
multiYieldBalanceSupplier = multiYieldBalanceSupplier,
|
multiStakingBalanceSupplier = multiStakingBalanceSupplier,
|
||||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||||
stakingIdFactory = stakingIdFactory,
|
stakingIdFactory = stakingIdFactory,
|
||||||
)
|
)
|
||||||
|
|
@ -371,7 +376,7 @@ internal object TokensDomainModule {
|
||||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
multiStakingBalanceFetcher: MultiStakingBalanceFetcher,
|
||||||
stakingIdFactory: StakingIdFactory,
|
stakingIdFactory: StakingIdFactory,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): WalletBalanceFetcher {
|
): WalletBalanceFetcher {
|
||||||
|
|
@ -381,7 +386,7 @@ internal object TokensDomainModule {
|
||||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
multiStakingBalanceFetcher = multiStakingBalanceFetcher,
|
||||||
stakingIdFactory = stakingIdFactory,
|
stakingIdFactory = stakingIdFactory,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ internal class LegacyScanProcessor @Inject constructor(
|
||||||
) {
|
) {
|
||||||
if (error is TangemSdkError.CardVerificationFailed) {
|
if (error is TangemSdkError.CardVerificationFailed) {
|
||||||
analyticsEventHandler.send(
|
analyticsEventHandler.send(
|
||||||
event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed(
|
event = OnboardingAnalyticsEvent.Error.OfflineAttestationFailed(
|
||||||
analyticsSource,
|
analyticsSource,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ import com.tangem.common.authentication.storage.AuthenticatedStorage
|
||||||
import com.tangem.common.json.TangemSdkAdapter
|
import com.tangem.common.json.TangemSdkAdapter
|
||||||
import com.tangem.common.services.secure.SecureStorage
|
import com.tangem.common.services.secure.SecureStorage
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
|
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
|
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||||
import com.tangem.domain.models.scan.serialization.*
|
import com.tangem.domain.models.scan.serialization.*
|
||||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||||
|
|
@ -125,6 +127,9 @@ internal object UserWalletsListManagerModule {
|
||||||
appPreferencesStore: AppPreferencesStore,
|
appPreferencesStore: AppPreferencesStore,
|
||||||
hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
||||||
tangemHotSdk: TangemHotSdk,
|
tangemHotSdk: TangemHotSdk,
|
||||||
|
trackingContextProxy: TrackingContextProxy,
|
||||||
|
analyticsEventHandler: AnalyticsEventHandler,
|
||||||
|
hotWalletRepository: HotWalletRepository,
|
||||||
): UserWalletsListRepository {
|
): UserWalletsListRepository {
|
||||||
val moshi = buildMoshi()
|
val moshi = buildMoshi()
|
||||||
val secureStorage = buildSecureStorage(applicationContext = applicationContext)
|
val secureStorage = buildSecureStorage(applicationContext = applicationContext)
|
||||||
|
|
@ -172,6 +177,9 @@ internal object UserWalletsListManagerModule {
|
||||||
savePersistentInformation = ProviderSuspend { true }, // Always save persistent information for now
|
savePersistentInformation = ProviderSuspend { true }, // Always save persistent information for now
|
||||||
hotWalletAccessCodeAttemptsRepository = hotWalletAccessCodeAttemptsRepository,
|
hotWalletAccessCodeAttemptsRepository = hotWalletAccessCodeAttemptsRepository,
|
||||||
tangemHotSdk = tangemHotSdk,
|
tangemHotSdk = tangemHotSdk,
|
||||||
|
trackingContextProxy = trackingContextProxy,
|
||||||
|
analyticsEventHandler = analyticsEventHandler,
|
||||||
|
hotWalletRepository = hotWalletRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,16 @@ import arrow.core.raise.either
|
||||||
import arrow.core.right
|
import arrow.core.right
|
||||||
import com.tangem.common.*
|
import com.tangem.common.*
|
||||||
import com.tangem.common.core.TangemSdkError
|
import com.tangem.common.core.TangemSdkError
|
||||||
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
|
import com.tangem.core.analytics.models.Basic
|
||||||
|
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
import com.tangem.domain.common.wallets.UserWalletsListRepository.LockMethod
|
import com.tangem.domain.common.wallets.UserWalletsListRepository.LockMethod
|
||||||
import com.tangem.domain.common.wallets.error.*
|
import com.tangem.domain.common.wallets.error.*
|
||||||
|
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.models.wallet.isLocked
|
import com.tangem.domain.models.wallet.isLocked
|
||||||
|
|
@ -50,6 +54,9 @@ internal class DefaultUserWalletsListRepository(
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
private val hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
private val hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
||||||
private val tangemHotSdk: TangemHotSdk,
|
private val tangemHotSdk: TangemHotSdk,
|
||||||
|
private val trackingContextProxy: TrackingContextProxy,
|
||||||
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
|
private val hotWalletRepository: HotWalletRepository,
|
||||||
) : UserWalletsListRepository {
|
) : UserWalletsListRepository {
|
||||||
|
|
||||||
override val userWallets = MutableStateFlow<List<UserWallet>?>(null)
|
override val userWallets = MutableStateFlow<List<UserWallet>?>(null)
|
||||||
|
|
@ -204,7 +211,7 @@ internal class DefaultUserWalletsListRepository(
|
||||||
|
|
||||||
userWalletEncryptionKeysRepository.delete(userWalletIds)
|
userWalletEncryptionKeysRepository.delete(userWalletIds)
|
||||||
|
|
||||||
removeHotWalletsFromSDK(userWalletIds)
|
removeHotWalletsFromSDKAndRepos(userWalletIds)
|
||||||
|
|
||||||
userWallets.update { currentWallets ->
|
userWallets.update { currentWallets ->
|
||||||
val updatedWallets = currentWallets?.filter { userWalletIds.contains(it.walletId).not() }
|
val updatedWallets = currentWallets?.filter { userWalletIds.contains(it.walletId).not() }
|
||||||
|
|
@ -235,6 +242,7 @@ internal class DefaultUserWalletsListRepository(
|
||||||
when (unlockMethod) {
|
when (unlockMethod) {
|
||||||
UserWalletsListRepository.UnlockMethod.Biometric -> {
|
UserWalletsListRepository.UnlockMethod.Biometric -> {
|
||||||
unlockAllWallets().bind()
|
unlockAllWallets().bind()
|
||||||
|
trackSignInEvent(userWallet, Basic.SignedIn.SignInType.Biometric)
|
||||||
select(userWalletId)
|
select(userWalletId)
|
||||||
}
|
}
|
||||||
UserWalletsListRepository.UnlockMethod.AccessCode -> {
|
UserWalletsListRepository.UnlockMethod.AccessCode -> {
|
||||||
|
|
@ -263,7 +271,10 @@ internal class DefaultUserWalletsListRepository(
|
||||||
removePasswordAttempts(userWallet)
|
removePasswordAttempts(userWallet)
|
||||||
|
|
||||||
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
||||||
.doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } }
|
.doOnSuccess { sensitiveInfo ->
|
||||||
|
updateWallets { it?.updateWith(sensitiveInfo) }
|
||||||
|
trackSignInEvent(userWallet, Basic.SignedIn.SignInType.AccessCode)
|
||||||
|
}
|
||||||
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
|
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
|
||||||
}
|
}
|
||||||
is UserWalletsListRepository.UnlockMethod.Scan -> {
|
is UserWalletsListRepository.UnlockMethod.Scan -> {
|
||||||
|
|
@ -291,7 +302,10 @@ internal class DefaultUserWalletsListRepository(
|
||||||
)
|
)
|
||||||
|
|
||||||
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
||||||
.doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } }
|
.doOnSuccess { sensitiveInfo ->
|
||||||
|
updateWallets { it?.updateWith(sensitiveInfo) }
|
||||||
|
trackSignInEvent(userWallet, Basic.SignedIn.SignInType.Card)
|
||||||
|
}
|
||||||
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
|
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -332,6 +346,9 @@ internal class DefaultUserWalletsListRepository(
|
||||||
sensitiveInformationRepository.getAll(allKeys)
|
sensitiveInformationRepository.getAll(allKeys)
|
||||||
.doOnSuccess { sensitiveInfo ->
|
.doOnSuccess { sensitiveInfo ->
|
||||||
updateWallets { wallets -> wallets?.updateWith(sensitiveInfo) }
|
updateWallets { wallets -> wallets?.updateWith(sensitiveInfo) }
|
||||||
|
selectedUserWallet.value?.let {
|
||||||
|
trackSignInEvent(it, Basic.SignedIn.SignInType.Biometric)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
|
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
|
||||||
}
|
}
|
||||||
|
|
@ -373,7 +390,7 @@ internal class DefaultUserWalletsListRepository(
|
||||||
if (newUserWallet.walletId == oldUserWallet.walletId &&
|
if (newUserWallet.walletId == oldUserWallet.walletId &&
|
||||||
oldUserWallet is UserWallet.Hot && newUserWallet is UserWallet.Cold
|
oldUserWallet is UserWallet.Hot && newUserWallet is UserWallet.Cold
|
||||||
) {
|
) {
|
||||||
removeHotWalletsFromSDK(walletIds = listOf(oldUserWallet.walletId))
|
removeHotWalletsFromSDKAndRepos(walletIds = listOf(oldUserWallet.walletId))
|
||||||
// When upgrading from Hot to Cold, if biometric lock is available, set it
|
// When upgrading from Hot to Cold, if biometric lock is available, set it
|
||||||
if (hasBiometry()) {
|
if (hasBiometry()) {
|
||||||
setLock(newUserWallet.walletId, LockMethod.Biometric, changeUnsecured = true)
|
setLock(newUserWallet.walletId, LockMethod.Biometric, changeUnsecured = true)
|
||||||
|
|
@ -384,13 +401,14 @@ internal class DefaultUserWalletsListRepository(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun removeHotWalletsFromSDK(walletIds: List<UserWalletId>) {
|
private suspend fun removeHotWalletsFromSDKAndRepos(walletIds: List<UserWalletId>) {
|
||||||
val hotWalletsToDelete = userWalletsSync()
|
val hotWalletsToDelete = userWalletsSync()
|
||||||
.filterIsInstance<UserWallet.Hot>()
|
.filterIsInstance<UserWallet.Hot>()
|
||||||
.filter { walletIds.contains(it.walletId) }
|
.filter { walletIds.contains(it.walletId) }
|
||||||
|
|
||||||
hotWalletsToDelete.forEach {
|
hotWalletsToDelete.forEach { wallet ->
|
||||||
tangemHotSdk.delete(it.hotWalletId)
|
hotWalletRepository.setAccessCodeSkipped(wallet.walletId, false) // In case the wallet is added again
|
||||||
|
tangemHotSdk.delete(wallet.hotWalletId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -508,4 +526,14 @@ internal class DefaultUserWalletsListRepository(
|
||||||
|
|
||||||
return lastOrNull()
|
return lastOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun trackSignInEvent(userWallet: UserWallet, type: Basic.SignedIn.SignInType) {
|
||||||
|
trackingContextProxy.addContext(userWallet)
|
||||||
|
analyticsEventHandler.send(
|
||||||
|
event = Basic.SignedIn(
|
||||||
|
signInType = type,
|
||||||
|
walletsCount = userWallets.value?.size ?: 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import com.squareup.moshi.JsonAdapter
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import com.tangem.common.authentication.storage.AuthenticatedStorage
|
import com.tangem.common.authentication.storage.AuthenticatedStorage
|
||||||
|
import com.tangem.common.core.TangemSdkError
|
||||||
import com.tangem.common.services.secure.SecureStorage
|
import com.tangem.common.services.secure.SecureStorage
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.hot.sdk.android.crypto.AESEncryptionProtocol
|
import com.tangem.hot.sdk.android.crypto.AESEncryptionProtocol
|
||||||
|
|
@ -96,7 +97,13 @@ internal class UserWalletEncryptionKeysRepository(
|
||||||
StorageKey.UserWalletEncryptionKey(userWalletId).name
|
StorageKey.UserWalletEncryptionKey(userWalletId).name
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticatedStorage.get(keys).mapNotNull {
|
val result = authenticatedStorage.get(keys)
|
||||||
|
|
||||||
|
if (keys.isNotEmpty() && result.isEmpty()) {
|
||||||
|
throw TangemSdkError.KeystoreInvalidated(Exception("Keys is empty"))
|
||||||
|
}
|
||||||
|
|
||||||
|
result.mapNotNull {
|
||||||
it.value.decodeToKey()
|
it.value.decodeToKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ class DetailsMiddleware {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enrollBiometrics() {
|
private fun enrollBiometrics() {
|
||||||
Analytics.send(Settings.AppSettings.ButtonEnableBiometricAuthentication)
|
Analytics.send(Settings.AppSettings.ButtonEnableBiometricAuthentication())
|
||||||
store.inject(DaggerGraphState::settingsManager).openBiometricSettings()
|
store.inject(DaggerGraphState::settingsManager).openBiometricSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ internal class AppSettingsItemsAnalyticsSender @Inject constructor(
|
||||||
|
|
||||||
private fun getEvent(item: AppSettingsScreenState.Item): AnalyticsEvent? {
|
private fun getEvent(item: AppSettingsScreenState.Item): AnalyticsEvent? {
|
||||||
return when (item.id) {
|
return when (item.id) {
|
||||||
AppSettingsItemsFactory.ID_ENROLL_BIOMETRICS_CARD -> Settings.AppSettings.EnableBiometrics
|
AppSettingsItemsFactory.ID_ENROLL_BIOMETRICS_CARD -> Settings.AppSettings.EnableBiometrics()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
|
@ -24,6 +25,9 @@ import com.tangem.tap.features.details.ui.common.DetailsMainButton
|
||||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
|
|
||||||
|
private const val CARD_PLACEHOLDER_SECONDARY_ROTATION = -15f
|
||||||
|
private const val CARD_PLACEHOLDER_PRIMARY_ROTATION = -1f
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun CardSettingsScreen(state: CardSettingsScreenState, modifier: Modifier = Modifier) {
|
internal fun CardSettingsScreen(state: CardSettingsScreenState, modifier: Modifier = Modifier) {
|
||||||
val isCardReadingNeeded = state.cardDetails == null
|
val isCardReadingNeeded = state.cardDetails == null
|
||||||
|
|
@ -42,74 +46,82 @@ internal fun CardSettingsScreen(state: CardSettingsScreenState, modifier: Modifi
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CardSettingsReadCard(onScanCardClick: () -> Unit) {
|
private fun CardSettingsReadCard(onScanCardClick: () -> Unit) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
Box(
|
CardPlaceholderImages()
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(bottom = TangemTheme.dimens.spacing40)
|
|
||||||
.testTag(DeviceSettingsScreenTestTags.IMAGE_BLOCK),
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(
|
|
||||||
start = TangemTheme.dimens.spacing80,
|
|
||||||
end = TangemTheme.dimens.spacing80,
|
|
||||||
top = TangemTheme.dimens.spacing70,
|
|
||||||
)
|
|
||||||
.rotate(-15f),
|
|
||||||
painter = painterResource(id = R.drawable.card_placeholder_secondary),
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.FillWidth,
|
|
||||||
)
|
|
||||||
Image(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(
|
|
||||||
start = TangemTheme.dimens.spacing60,
|
|
||||||
end = TangemTheme.dimens.spacing60,
|
|
||||||
)
|
|
||||||
.rotate(-1f),
|
|
||||||
painter = painterResource(id = R.drawable.card_placeholder_black),
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.FillWidth,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Column(
|
ScanCardContent(onScanCardClick = onScanCardClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CardPlaceholderImages() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = TangemTheme.dimens.spacing40)
|
||||||
|
.testTag(DeviceSettingsScreenTestTags.IMAGE_BLOCK),
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(
|
.padding(
|
||||||
start = TangemTheme.dimens.spacing16,
|
start = TangemTheme.dimens.spacing80,
|
||||||
end = TangemTheme.dimens.spacing16,
|
end = TangemTheme.dimens.spacing80,
|
||||||
bottom = TangemTheme.dimens.spacing32,
|
top = TangemTheme.dimens.spacing70,
|
||||||
),
|
)
|
||||||
) {
|
.rotate(CARD_PLACEHOLDER_SECONDARY_ROTATION),
|
||||||
Text(
|
painter = painterResource(id = R.drawable.card_placeholder_secondary),
|
||||||
text = stringResourceSafe(id = R.string.scan_card_settings_title),
|
contentDescription = null,
|
||||||
color = TangemTheme.colors.text.primary1,
|
contentScale = ContentScale.FillWidth,
|
||||||
style = TangemTheme.typography.h3,
|
)
|
||||||
)
|
Image(
|
||||||
Spacer(modifier = Modifier.size(TangemTheme.dimens.size20))
|
modifier = Modifier
|
||||||
Text(
|
.fillMaxWidth()
|
||||||
text = stringResourceSafe(id = R.string.scan_card_settings_message),
|
.padding(
|
||||||
color = TangemTheme.colors.text.secondary,
|
start = TangemTheme.dimens.spacing60,
|
||||||
style = TangemTheme.typography.body1,
|
end = TangemTheme.dimens.spacing60,
|
||||||
modifier = Modifier
|
)
|
||||||
.verticalScroll(rememberScrollState())
|
.rotate(CARD_PLACEHOLDER_PRIMARY_ROTATION),
|
||||||
.weight(weight = 1f, fill = false),
|
painter = painterResource(id = R.drawable.card_placeholder_black),
|
||||||
)
|
contentDescription = null,
|
||||||
Spacer(modifier = Modifier.size(TangemTheme.dimens.size32))
|
contentScale = ContentScale.FillWidth,
|
||||||
DetailsMainButton(
|
)
|
||||||
title = stringResourceSafe(id = R.string.scan_card_settings_button),
|
}
|
||||||
onClick = onScanCardClick,
|
}
|
||||||
)
|
|
||||||
}
|
@Composable
|
||||||
|
private fun ScanCardContent(onScanCardClick: () -> Unit) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
start = TangemTheme.dimens.spacing16,
|
||||||
|
end = TangemTheme.dimens.spacing16,
|
||||||
|
bottom = TangemTheme.dimens.spacing32,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResourceSafe(id = R.string.scan_card_settings_title),
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
style = TangemTheme.typography.h3,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.size(TangemTheme.dimens.size20))
|
||||||
|
Text(
|
||||||
|
text = stringResourceSafe(id = R.string.scan_card_settings_message),
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
style = TangemTheme.typography.body1,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.size(TangemTheme.dimens.size32))
|
||||||
|
DetailsMainButton(
|
||||||
|
title = stringResourceSafe(id = R.string.scan_card_settings_button),
|
||||||
|
onClick = onScanCardClick,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package com.tangem.tap.features.details.ui.cardsettings
|
package com.tangem.tap.features.details.ui.cardsettings
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import androidx.compose.runtime.Composable
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
|
||||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
|
||||||
import com.tangem.tap.features.details.redux.SecurityOption
|
import com.tangem.tap.features.details.redux.SecurityOption
|
||||||
import com.tangem.tap.features.details.ui.securitymode.toTitleRes
|
import com.tangem.tap.features.details.ui.securitymode.toTitleRes
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
|
|
@ -32,7 +30,7 @@ internal sealed class CardInfo(
|
||||||
|
|
||||||
class SignedHashes(hashes: String) : CardInfo(
|
class SignedHashes(hashes: String) : CardInfo(
|
||||||
titleRes = TextReference.Res(R.string.details_row_title_signed_hashes),
|
titleRes = TextReference.Res(R.string.details_row_title_signed_hashes),
|
||||||
subtitle = TextReference.Res(R.string.details_row_subtitle_signed_hashes_format, hashes),
|
subtitle = TextReference.Res(R.string.details_row_subtitle_signed_hashes_format, wrappedList(hashes)),
|
||||||
)
|
)
|
||||||
|
|
||||||
class SecurityMode(securityOption: SecurityOption, clickable: Boolean) : CardInfo(
|
class SecurityMode(securityOption: SecurityOption, clickable: Boolean) : CardInfo(
|
||||||
|
|
@ -47,7 +45,7 @@ internal sealed class CardInfo(
|
||||||
isClickable = true,
|
isClickable = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
class AccessCodeRecovery(val isEnabled: Boolean) : CardInfo(
|
class AccessCodeRecovery(isEnabled: Boolean) : CardInfo(
|
||||||
titleRes = TextReference.Res(R.string.card_settings_access_code_recovery_title),
|
titleRes = TextReference.Res(R.string.card_settings_access_code_recovery_title),
|
||||||
subtitle = if (isEnabled) {
|
subtitle = if (isEnabled) {
|
||||||
TextReference.Res(R.string.common_enabled)
|
TextReference.Res(R.string.common_enabled)
|
||||||
|
|
@ -62,22 +60,4 @@ internal sealed class CardInfo(
|
||||||
subtitle = description,
|
subtitle = description,
|
||||||
isClickable = true,
|
isClickable = true,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// TODO("Remove and use the same from coreUI")
|
|
||||||
internal sealed interface TextReference {
|
|
||||||
class Res(@StringRes val id: Int, val formatArgs: List<Any> = emptyList()) : TextReference {
|
|
||||||
constructor(@StringRes id: Int, vararg formatArgs: Any) : this(id, formatArgs.toList())
|
|
||||||
}
|
|
||||||
|
|
||||||
class Str(val value: String) : TextReference
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@ReadOnlyComposable
|
|
||||||
internal fun TextReference.resolveReference(): String {
|
|
||||||
return when (this) {
|
|
||||||
is TextReference.Res -> stringResourceSafe(this.id, *this.formatArgs.toTypedArray())
|
|
||||||
is TextReference.Str -> this.value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.tangem.tap.features.details.ui.common.utils
|
package com.tangem.tap.features.details.ui.common.utils
|
||||||
|
|
||||||
import com.tangem.domain.card.CardTypesResolver
|
import com.tangem.domain.card.CardTypesResolver
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
|
|
||||||
internal fun getResetToFactoryDescription(
|
internal fun getResetToFactoryDescription(
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.core.ui.test.ResetCardScreenTestTags
|
import com.tangem.core.ui.test.ResetCardScreenTestTags
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.tap.features.details.ui.common.DetailsMainButton
|
import com.tangem.tap.features.details.ui.common.DetailsMainButton
|
||||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
|
|
@ -198,7 +198,7 @@ private fun ResetButton(enabled: Boolean, onResetButtonClick: () -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CommonResetDialog(dialog: ResetCardScreenState.Dialog) {
|
private fun CommonResetDialog(dialog: ResetCardDialog) {
|
||||||
BasicDialog(
|
BasicDialog(
|
||||||
title = stringResourceSafe(dialog.titleResId),
|
title = stringResourceSafe(dialog.titleResId),
|
||||||
message = stringResourceSafe(dialog.messageResId),
|
message = stringResourceSafe(dialog.messageResId),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.tangem.tap.features.details.ui.resetcard
|
package com.tangem.tap.features.details.ui.resetcard
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
|
|
||||||
internal data class ResetCardScreenState(
|
internal data class ResetCardScreenState(
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,12 @@ object UnfinishedBackupFoundDialog {
|
||||||
setTitle(R.string.common_warning)
|
setTitle(R.string.common_warning)
|
||||||
setMessage(R.string.welcome_interrupted_backup_alert_message)
|
setMessage(R.string.welcome_interrupted_backup_alert_message)
|
||||||
setPositiveButton(R.string.welcome_interrupted_backup_alert_resume) { _, _ ->
|
setPositiveButton(R.string.welcome_interrupted_backup_alert_resume) { _, _ ->
|
||||||
Analytics.send(OnboardingEvent.Backup.ResumeInterruptedBackup)
|
Analytics.send(OnboardingEvent.Backup.ResumeInterruptedBackup())
|
||||||
store.dispatch(GlobalAction.HideDialog)
|
store.dispatch(GlobalAction.HideDialog)
|
||||||
store.dispatch(BackupAction.ResumeFoundUnfinishedBackup(scanResponse))
|
store.dispatch(BackupAction.ResumeFoundUnfinishedBackup(scanResponse))
|
||||||
}
|
}
|
||||||
setNegativeButton(R.string.welcome_interrupted_backup_alert_discard) { _, _ ->
|
setNegativeButton(R.string.welcome_interrupted_backup_alert_discard) { _, _ ->
|
||||||
Analytics.send(OnboardingEvent.Backup.CancelInterruptedBackup)
|
Analytics.send(OnboardingEvent.Backup.CancelInterruptedBackup())
|
||||||
store.dispatch(GlobalAction.HideDialog)
|
store.dispatch(GlobalAction.HideDialog)
|
||||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.ConfirmDiscardingBackup(scanResponse)))
|
store.dispatch(GlobalAction.ShowDialog(BackupDialog.ConfirmDiscardingBackup(scanResponse)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import com.tangem.core.navigation.finisher.AppFinisher
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||||
import com.tangem.tap.common.analytics.events.SignIn
|
import com.tangem.tap.common.analytics.events.SignIn
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
||||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ internal class WelcomeMiddleware {
|
||||||
.doOnSuccess { selectedUserWallet ->
|
.doOnSuccess { selectedUserWallet ->
|
||||||
sendSignedInAnalyticsEvent(
|
sendSignedInAnalyticsEvent(
|
||||||
userWallet = selectedUserWallet,
|
userWallet = selectedUserWallet,
|
||||||
signInType = Basic.SignedIn.SignInType.Biometric,
|
signInType = Basic.SignedInLegacy.SignInType.Biometric,
|
||||||
)
|
)
|
||||||
|
|
||||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||||
|
|
@ -80,7 +80,7 @@ internal class WelcomeMiddleware {
|
||||||
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Error(error))
|
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Error(error))
|
||||||
}
|
}
|
||||||
.doOnSuccess {
|
.doOnSuccess {
|
||||||
sendSignedInAnalyticsEvent(userWallet, signInType = Basic.SignedIn.SignInType.Card)
|
sendSignedInAnalyticsEvent(userWallet, signInType = Basic.SignedInLegacy.SignInType.Card)
|
||||||
|
|
||||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||||
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Success)
|
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Success)
|
||||||
|
|
@ -89,9 +89,7 @@ internal class WelcomeMiddleware {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendSignedInAnalyticsEvent(userWallet: UserWallet, signInType: Basic.SignedIn.SignInType) {
|
private fun sendSignedInAnalyticsEvent(userWallet: UserWallet, signInType: Basic.SignedInLegacy.SignInType) {
|
||||||
// TODO [REDACTED_TASK_KEY] [Hot Wallet] Analytics
|
|
||||||
|
|
||||||
if (userWallet !is UserWallet.Cold) {
|
if (userWallet !is UserWallet.Cold) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +106,7 @@ internal class WelcomeMiddleware {
|
||||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||||
|
|
||||||
Analytics.send(
|
Analytics.send(
|
||||||
event = Basic.SignedIn(
|
event = Basic.SignedInLegacy(
|
||||||
currency = currency,
|
currency = currency,
|
||||||
batch = scanResponse.card.batchId,
|
batch = scanResponse.card.batchId,
|
||||||
signInType = signInType,
|
signInType = signInType,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.tangem.tap.features.welcome.ui
|
package com.tangem.tap.features.welcome.ui
|
||||||
|
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
||||||
|
|
||||||
internal data class WelcomeScreenState(
|
internal data class WelcomeScreenState(
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
||||||
import com.tangem.tap.features.welcome.component.impl.PreviewWelcomeComponent
|
import com.tangem.tap.features.welcome.component.impl.PreviewWelcomeComponent
|
||||||
import com.tangem.tap.features.welcome.ui.WelcomeScreenState
|
import com.tangem.tap.features.welcome.ui.WelcomeScreenState
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,9 @@ internal class DefaultAuthProvider(
|
||||||
ApiEnvironment.DEV_2,
|
ApiEnvironment.DEV_2,
|
||||||
ApiEnvironment.DEV_3,
|
ApiEnvironment.DEV_3,
|
||||||
-> environmentConfigStorage.getConfigSync().tangemApiKeyDev
|
-> environmentConfigStorage.getConfigSync().tangemApiKeyDev
|
||||||
ApiEnvironment.STAGE -> environmentConfigStorage.getConfigSync().tangemApiKeyStage
|
ApiEnvironment.STAGE_2,
|
||||||
|
ApiEnvironment.STAGE,
|
||||||
|
-> environmentConfigStorage.getConfigSync().tangemApiKeyStage
|
||||||
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().tangemApiKey
|
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().tangemApiKey
|
||||||
} ?: error("No tangem tech api config provided")
|
} ?: error("No tangem tech api config provided")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.tap.network.auth
|
package com.tangem.tap.network.auth
|
||||||
|
|
||||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||||
|
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
||||||
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
||||||
|
|
||||||
internal class DefaultP2PEthPoolAuthProvider(
|
internal class DefaultP2PEthPoolAuthProvider(
|
||||||
|
|
@ -11,6 +12,6 @@ internal class DefaultP2PEthPoolAuthProvider(
|
||||||
val keys = environmentConfigStorage.getConfigSync().p2pApiKey
|
val keys = environmentConfigStorage.getConfigSync().p2pApiKey
|
||||||
?: error("No P2P api keys provided")
|
?: error("No P2P api keys provided")
|
||||||
|
|
||||||
return keys.mainnet
|
return if (P2PStakingConfig.USE_TESTNET) keys.hoodi else keys.mainnet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,8 +11,11 @@ import com.arkivanov.essenty.lifecycle.subscribe
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||||
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||||
|
import com.tangem.core.analytics.models.Basic
|
||||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||||
|
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||||
import com.tangem.core.decompose.context.AppComponentContext
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
import com.tangem.core.decompose.context.child
|
import com.tangem.core.decompose.context.child
|
||||||
import com.tangem.core.decompose.context.childByContext
|
import com.tangem.core.decompose.context.childByContext
|
||||||
|
|
@ -26,6 +29,7 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
import com.tangem.domain.models.wallet.isLocked
|
import com.tangem.domain.models.wallet.isLocked
|
||||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||||
import com.tangem.features.hotwallet.HotAccessCodeRequestComponent
|
import com.tangem.features.hotwallet.HotAccessCodeRequestComponent
|
||||||
|
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||||
import com.tangem.features.hotwallet.accesscoderequest.proxy.HotWalletPasswordRequesterProxy
|
import com.tangem.features.hotwallet.accesscoderequest.proxy.HotWalletPasswordRequesterProxy
|
||||||
import com.tangem.features.walletconnect.components.WcRoutingComponent
|
import com.tangem.features.walletconnect.components.WcRoutingComponent
|
||||||
import com.tangem.hot.sdk.TangemHotSdk
|
import com.tangem.hot.sdk.TangemHotSdk
|
||||||
|
|
@ -63,6 +67,9 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
||||||
private val userWalletsListRepository: UserWalletsListRepository,
|
private val userWalletsListRepository: UserWalletsListRepository,
|
||||||
private val cardRepository: CardRepository,
|
private val cardRepository: CardRepository,
|
||||||
private val onboardingRepository: OnboardingRepository,
|
private val onboardingRepository: OnboardingRepository,
|
||||||
|
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||||
|
private val trackingContextProxy: TrackingContextProxy,
|
||||||
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||||
) : RoutingComponent,
|
) : RoutingComponent,
|
||||||
AppComponentContext by context,
|
AppComponentContext by context,
|
||||||
|
|
@ -151,6 +158,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
trackSignInEvent()
|
||||||
AppRoute.Wallet
|
AppRoute.Wallet
|
||||||
}
|
}
|
||||||
}.also {
|
}.also {
|
||||||
|
|
@ -235,4 +243,18 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
||||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound(onboardingScanResponse)))
|
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound(onboardingScanResponse)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun trackSignInEvent() {
|
||||||
|
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||||
|
val userWallets = userWalletsListRepository.userWalletsSync()
|
||||||
|
val selectedWallet = userWalletsListRepository.selectedUserWalletSync() ?: return
|
||||||
|
trackingContextProxy.addContext(selectedWallet)
|
||||||
|
analyticsEventHandler.send(
|
||||||
|
event = Basic.SignedIn(
|
||||||
|
signInType = Basic.SignedIn.SignInType.NoSecurity,
|
||||||
|
walletsCount = userWallets.size,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,6 @@ object RoutingTransitionAnimationFactory {
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
fun create(appRoute: AppRoute): StackAnimator {
|
fun create(appRoute: AppRoute): StackAnimator {
|
||||||
return when (appRoute) {
|
return when (appRoute) {
|
||||||
is AppRoute.Onboarding,
|
|
||||||
is AppRoute.Welcome,
|
is AppRoute.Welcome,
|
||||||
is AppRoute.Home,
|
is AppRoute.Home,
|
||||||
-> fade(tween(400)).plus(scale(tween(400)))
|
-> fade(tween(400)).plus(scale(tween(400)))
|
||||||
|
|
|
||||||
|
|
@ -529,7 +529,9 @@ internal class ChildFactory @Inject constructor(
|
||||||
is AppRoute.CreateMobileWallet -> {
|
is AppRoute.CreateMobileWallet -> {
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = Unit,
|
params = CreateMobileWalletComponent.Params(
|
||||||
|
source = route.source,
|
||||||
|
),
|
||||||
componentFactory = createMobileWalletComponentFactory,
|
componentFactory = createMobileWalletComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -565,7 +567,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
params = CreateWalletBackupComponent.Params(
|
params = CreateWalletBackupComponent.Params(
|
||||||
userWalletId = route.userWalletId,
|
userWalletId = route.userWalletId,
|
||||||
isUpgradeFlow = route.isUpgradeFlow,
|
isUpgradeFlow = route.isUpgradeFlow,
|
||||||
shouldSetAccessCode = route.setAccessCode,
|
shouldSetAccessCode = route.shouldSetAccessCode,
|
||||||
analyticsSource = route.analyticsSource,
|
analyticsSource = route.analyticsSource,
|
||||||
analyticsAction = route.analyticsAction,
|
analyticsAction = route.analyticsAction,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(deps.plugins.kotlin.android) apply false
|
alias(deps.plugins.kotlin.android) apply false
|
||||||
|
|
@ -32,21 +33,53 @@ interface Injected {
|
||||||
val fs: FileSystemOperations
|
val fs: FileSystemOperations
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test Logging
|
data class TestStats(
|
||||||
|
val total: Long = 0,
|
||||||
|
val passed: Long = 0,
|
||||||
|
val failed: Long = 0,
|
||||||
|
val skipped: Long = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
val testResultsByModule = ConcurrentHashMap<String, TestStats>()
|
||||||
|
|
||||||
|
// Test task to run unit tests for debug/googleDebug variant (Android) and all JVM modules
|
||||||
|
val unitTest by tasks.registering {
|
||||||
|
group = "verification"
|
||||||
|
description = "Run unit tests for debug/googleDebug variant and all JVM modules"
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
if (testResultsByModule.isNotEmpty()) {
|
||||||
|
val totalStats = testResultsByModule.values.fold(TestStats()) { acc, stats ->
|
||||||
|
TestStats(
|
||||||
|
total = acc.total + stats.total,
|
||||||
|
passed = acc.passed + stats.passed,
|
||||||
|
failed = acc.failed + stats.failed,
|
||||||
|
skipped = acc.skipped + stats.skipped,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
println("\n" + "=".repeat(80))
|
||||||
|
println("TEST SUMMARY")
|
||||||
|
println("=".repeat(80))
|
||||||
|
|
||||||
|
testResultsByModule.toSortedMap().forEach { (module, stats) ->
|
||||||
|
println(" $module: ${stats.total} tests (${stats.passed} passed, ${stats.failed} failed, ${stats.skipped} skipped)")
|
||||||
|
}
|
||||||
|
|
||||||
|
println("-".repeat(80))
|
||||||
|
println("TOTAL: ${totalStats.total} tests in ${testResultsByModule.size} modules")
|
||||||
|
println(" Passed: ${totalStats.passed}")
|
||||||
|
println(" Failed: ${totalStats.failed}")
|
||||||
|
println(" Skipped: ${totalStats.skipped}")
|
||||||
|
println("=".repeat(80))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Logging and testCI dependencies
|
||||||
subprojects {
|
subprojects {
|
||||||
tasks.withType<Test>().configureEach {
|
tasks.withType<Test>().configureEach {
|
||||||
val taskName = name.lowercase()
|
println("Test task scheduled: $path")
|
||||||
if (taskName.contains("external") ||
|
|
||||||
taskName.contains("internal") ||
|
|
||||||
taskName.contains("release") ||
|
|
||||||
taskName.contains("mocked") ||
|
|
||||||
taskName.contains("huawei")
|
|
||||||
) {
|
|
||||||
enabled = false
|
|
||||||
println("Skipping test task: $name")
|
|
||||||
} else {
|
|
||||||
println("Test task scheduled: $name")
|
|
||||||
}
|
|
||||||
|
|
||||||
testLogging {
|
testLogging {
|
||||||
exceptionFormat = TestExceptionFormat.FULL
|
exceptionFormat = TestExceptionFormat.FULL
|
||||||
|
|
@ -54,6 +87,13 @@ subprojects {
|
||||||
|
|
||||||
afterSuite(KotlinClosure2<TestDescriptor, TestResult, Unit>({ desc, result ->
|
afterSuite(KotlinClosure2<TestDescriptor, TestResult, Unit>({ desc, result ->
|
||||||
if (desc.parent == null) { // will match the outermost suite
|
if (desc.parent == null) { // will match the outermost suite
|
||||||
|
testResultsByModule[path] = TestStats(
|
||||||
|
total = result.testCount,
|
||||||
|
passed = result.successfulTestCount,
|
||||||
|
failed = result.failedTestCount,
|
||||||
|
skipped = result.skippedTestCount,
|
||||||
|
)
|
||||||
|
|
||||||
val output =
|
val output =
|
||||||
"Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)"
|
"Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)"
|
||||||
val startItem = "| "
|
val startItem = "| "
|
||||||
|
|
@ -68,6 +108,28 @@ subprojects {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register testCI dependencies
|
||||||
|
// App module
|
||||||
|
plugins.withId("com.android.application") {
|
||||||
|
afterEvaluate {
|
||||||
|
unitTest.configure { dependsOn(tasks.named("testGoogleDebugUnitTest")) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Android libraries
|
||||||
|
plugins.withId("com.android.library") {
|
||||||
|
afterEvaluate {
|
||||||
|
unitTest.configure { dependsOn(tasks.named("testDebugUnitTest")) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jvm modules
|
||||||
|
plugins.withId("org.jetbrains.kotlin.jvm") {
|
||||||
|
if (!plugins.hasPlugin("com.android.library") && !plugins.hasPlugin("com.android.application")) {
|
||||||
|
unitTest.configure { dependsOn(tasks.named("test")) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val assembleInternalQA by tasks.registering {
|
val assembleInternalQA by tasks.registering {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<?xml version="1.0" ?>
|
|
||||||
<SmellBaseline>
|
|
||||||
<ManuallySuppressedIssues/>
|
|
||||||
<CurrentIssues>
|
|
||||||
<ID>BooleanPropertyNaming:AppRoute.kt$AppRoute.CreateWalletBackup$val setAccessCode: Boolean = false</ID>
|
|
||||||
<ID>NestedScopeFunctions:PayloadToDeeplinkConverter.kt$PayloadToDeeplinkConverter$let { addQueryParam(NAME_KEY, it) }</ID>
|
|
||||||
<ID>NestedScopeFunctions:PayloadToDeeplinkConverter.kt$PayloadToDeeplinkConverter$let { addQueryParam(TRANSACTION_ID_KEY, it) }</ID>
|
|
||||||
</CurrentIssues>
|
|
||||||
</SmellBaseline>
|
|
||||||
|
|
@ -346,7 +346,9 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
object CreateHardwareWallet : AppRoute(path = "/create_hardware_wallet")
|
object CreateHardwareWallet : AppRoute(path = "/create_hardware_wallet")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object CreateMobileWallet : AppRoute(path = "/create_mobile_wallet")
|
data class CreateMobileWallet(
|
||||||
|
val source: String,
|
||||||
|
) : AppRoute(path = "/create_mobile_wallet")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UpgradeWallet(
|
data class UpgradeWallet(
|
||||||
|
|
@ -368,7 +370,7 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
val analyticsSource: String,
|
val analyticsSource: String,
|
||||||
val analyticsAction: String,
|
val analyticsAction: String,
|
||||||
val isUpgradeFlow: Boolean = false,
|
val isUpgradeFlow: Boolean = false,
|
||||||
val setAccessCode: Boolean = false,
|
val shouldSetAccessCode: Boolean = false,
|
||||||
) : AppRoute(path = "/create_wallet_backup/${userWalletId.stringValue}")
|
) : AppRoute(path = "/create_wallet_backup/${userWalletId.stringValue}")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,12 @@ object PayloadToDeeplinkConverter : Converter<Map<String, String>, String?> {
|
||||||
addQueryParam(DERIVATION_PATH_KEY, derivationPath)
|
addQueryParam(DERIVATION_PATH_KEY, derivationPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionId?.let { addQueryParam(TRANSACTION_ID_KEY, it) }
|
if (transactionId != null) {
|
||||||
name?.let { addQueryParam(NAME_KEY, it) }
|
addQueryParam(TRANSACTION_ID_KEY, transactionId)
|
||||||
|
}
|
||||||
|
if (name != null) {
|
||||||
|
addQueryParam(NAME_KEY, name)
|
||||||
|
}
|
||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
package com.tangem.common.test.data.staking
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
|
||||||
|
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolExitQueueDTO
|
||||||
|
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolStakeDTO
|
||||||
|
import com.tangem.domain.models.staking.StakingID
|
||||||
|
import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating mock P2P ETH Pool account responses for testing
|
||||||
|
*/
|
||||||
|
object MockP2PEthPoolAccountResponseFactory {
|
||||||
|
|
||||||
|
private val defaultStakingId = StakingID(
|
||||||
|
integrationId = "p2p-ethereum-pooled",
|
||||||
|
address = "0x5aa711F440Eb6d4361148bBD89d03464628ace84",
|
||||||
|
)
|
||||||
|
|
||||||
|
const val defaultVaultAddress = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
|
||||||
|
|
||||||
|
fun createWithBalance(
|
||||||
|
stakingId: StakingID = defaultStakingId,
|
||||||
|
vaultAddress: String = defaultVaultAddress,
|
||||||
|
stakedAmount: BigDecimal = BigDecimal("1.5"),
|
||||||
|
earnedAmount: BigDecimal = BigDecimal("0.05"),
|
||||||
|
): P2PEthPoolAccountResponse {
|
||||||
|
return P2PEthPoolAccountResponse(
|
||||||
|
delegatorAddress = stakingId.address,
|
||||||
|
vaultAddress = vaultAddress,
|
||||||
|
stake = P2PEthPoolStakeDTO(
|
||||||
|
assets = stakedAmount,
|
||||||
|
totalEarnedAssets = earnedAmount,
|
||||||
|
),
|
||||||
|
availableToUnstake = stakedAmount,
|
||||||
|
availableToWithdraw = BigDecimal.ZERO,
|
||||||
|
exitQueue = P2PEthPoolExitQueueDTO(
|
||||||
|
total = 0.0,
|
||||||
|
requests = emptyList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createWithEmptyBalance(
|
||||||
|
stakingId: StakingID = defaultStakingId,
|
||||||
|
vaultAddress: String = defaultVaultAddress,
|
||||||
|
): P2PEthPoolAccountResponse {
|
||||||
|
return P2PEthPoolAccountResponse(
|
||||||
|
delegatorAddress = stakingId.address,
|
||||||
|
vaultAddress = vaultAddress,
|
||||||
|
stake = P2PEthPoolStakeDTO(
|
||||||
|
assets = BigDecimal.ZERO,
|
||||||
|
totalEarnedAssets = BigDecimal.ZERO,
|
||||||
|
),
|
||||||
|
availableToUnstake = BigDecimal.ZERO,
|
||||||
|
availableToWithdraw = BigDecimal.ZERO,
|
||||||
|
exitQueue = P2PEthPoolExitQueueDTO(
|
||||||
|
total = 0.0,
|
||||||
|
requests = emptyList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createMockVault(vaultAddress: String = defaultVaultAddress): P2PEthPoolVault {
|
||||||
|
return P2PEthPoolVault(
|
||||||
|
vaultAddress = vaultAddress,
|
||||||
|
displayName = "Test Vault",
|
||||||
|
apy = BigDecimal("3.5"),
|
||||||
|
baseApy = BigDecimal("3.0"),
|
||||||
|
capacity = BigDecimal("10000"),
|
||||||
|
totalAssets = BigDecimal("5000"),
|
||||||
|
feePercent = BigDecimal("10"),
|
||||||
|
isPrivate = false,
|
||||||
|
isGenesis = false,
|
||||||
|
isSmoothingPool = false,
|
||||||
|
isErc20 = false,
|
||||||
|
tokenName = "Test Token",
|
||||||
|
tokenSymbol = "TT",
|
||||||
|
createdAt = 0L,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,9 +18,7 @@
|
||||||
<ID>CanBeNonNullable:NotificationsFactory.kt$NotificationsFactory$minimumSendAmount: BigDecimal?</ID>
|
<ID>CanBeNonNullable:NotificationsFactory.kt$NotificationsFactory$minimumSendAmount: BigDecimal?</ID>
|
||||||
<ID>CanBeNonNullable:NotificationsFactory.kt$NotificationsFactory$rentWarning: CryptoCurrencyWarning.Rent?</ID>
|
<ID>CanBeNonNullable:NotificationsFactory.kt$NotificationsFactory$rentWarning: CryptoCurrencyWarning.Rent?</ID>
|
||||||
<ID>MultilineLambdaItParameter:ExpressStatusItems.kt${ val itemInfo = expressTxs[it].info val (iconRes, tint) = when (itemInfo.iconState) { ExpressTransactionStateIconUM.Warning -> { R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention } ExpressTransactionStateIconUM.Error -> { R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning } ExpressTransactionStateIconUM.None -> null to null } ExpressStatusItem( title = itemInfo.title, fromTokenIconState = itemInfo.fromCurrencyIcon, toTokenIconState = itemInfo.toCurrencyIcon, fromAmount = itemInfo.fromAmount, fromSymbol = itemInfo.fromAmountSymbol, toAmount = itemInfo.toAmount, toSymbol = itemInfo.toAmountSymbol, onClick = itemInfo.onClick, infoIconRes = iconRes, infoIconTint = tint, modifier = modifier.animateItem(), ) }</ID>
|
<ID>MultilineLambdaItParameter:ExpressStatusItems.kt${ val itemInfo = expressTxs[it].info val (iconRes, tint) = when (itemInfo.iconState) { ExpressTransactionStateIconUM.Warning -> { R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention } ExpressTransactionStateIconUM.Error -> { R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning } ExpressTransactionStateIconUM.None -> null to null } ExpressStatusItem( title = itemInfo.title, fromTokenIconState = itemInfo.fromCurrencyIcon, toTokenIconState = itemInfo.toCurrencyIcon, fromAmount = itemInfo.fromAmount, fromSymbol = itemInfo.fromAmountSymbol, toAmount = itemInfo.toAmount, toSymbol = itemInfo.toAmountSymbol, onClick = itemInfo.onClick, infoIconRes = iconRes, infoIconTint = tint, modifier = modifier.animateItem(), ) }</ID>
|
||||||
<ID>MultilineLambdaItParameter:TokenItemStateConverter.kt$TokenItemStateConverter${ createTitleState(it, yieldModuleApyMap, stakingApyMap, { onApyLabelClick?.invoke(it) }) }</ID>
|
|
||||||
<ID>MultilineLambdaItParameter:TokenItemStateConverter.kt$TokenItemStateConverter.Companion${ it.key.equals( other = token.yieldSupplyKey(), ignoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId), ) }</ID>
|
<ID>MultilineLambdaItParameter:TokenItemStateConverter.kt$TokenItemStateConverter.Companion${ it.key.equals( other = token.yieldSupplyKey(), ignoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId), ) }</ID>
|
||||||
<ID>NamedArguments:TokenItemStateConverter.kt$TokenItemStateConverter$createTitleState(it, yieldModuleApyMap, stakingApyMap, { onApyLabelClick?.invoke(it) })</ID>
|
|
||||||
<ID>NoNameShadowing:NavigationButtonsBlock.kt$navigationUM</ID>
|
<ID>NoNameShadowing:NavigationButtonsBlock.kt$navigationUM</ID>
|
||||||
<ID>NoNameShadowing:UserWalletItem.kt$balance</ID>
|
<ID>NoNameShadowing:UserWalletItem.kt$balance</ID>
|
||||||
<ID>NullableBooleanCheck:TokenItemStateConverter.kt$TokenItemStateConverter.Companion$cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive ?: false</ID>
|
<ID>NullableBooleanCheck:TokenItemStateConverter.kt$TokenItemStateConverter.Companion$cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive ?: false</ID>
|
||||||
|
|
|
||||||
|
|
@ -271,6 +271,7 @@ private fun AmountFieldError(
|
||||||
style = TangemTheme.typography.caption2,
|
style = TangemTheme.typography.caption2,
|
||||||
color = color,
|
color = color,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.testTag(SendScreenTestTags.AMOUNT_ERROR_TEXT),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
package com.tangem.common.ui.news
|
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun ArticleBadge(articleTagUM: ArticleTagUM, modifier: Modifier = Modifier) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
modifier = modifier
|
|
||||||
.heightIn(min = 24.dp)
|
|
||||||
.background(
|
|
||||||
color = TangemTheme.colors.icon.informative.copy(alpha = 0.1f),
|
|
||||||
shape = RoundedCornerShape(8.dp),
|
|
||||||
)
|
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
|
||||||
) {
|
|
||||||
when (articleTagUM) {
|
|
||||||
is ArticleTagUM.Category -> Unit
|
|
||||||
is ArticleTagUM.Token -> {
|
|
||||||
CurrencyIcon(
|
|
||||||
state = articleTagUM.iconState,
|
|
||||||
shouldDisplayNetwork = false,
|
|
||||||
iconSize = 16.dp,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = articleTagUM.title.resolveReference(),
|
|
||||||
style = TangemTheme.typography.caption1,
|
|
||||||
color = TangemTheme.colors.text.secondary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true, widthDp = 360)
|
|
||||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
|
||||||
@Composable
|
|
||||||
private fun ArticleBadgePreview() {
|
|
||||||
TangemThemePreview {
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
|
||||||
ArticleBadge(
|
|
||||||
articleTagUM = ArticleTagUM.Token(
|
|
||||||
TextReference.Str("BTC"),
|
|
||||||
iconState = CurrencyIconState.CoinIcon(
|
|
||||||
url = "",
|
|
||||||
fallbackResId = 0,
|
|
||||||
isGrayscale = false,
|
|
||||||
shouldShowCustomBadge = false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
ArticleBadge(
|
|
||||||
articleTagUM = ArticleTagUM.Category(TextReference.Str("Regulation")),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +1,47 @@
|
||||||
package com.tangem.common.ui.news
|
package com.tangem.common.ui.news
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.CardColors
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawWithCache
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.res.vectorResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.components.SpacerH
|
import com.tangem.core.ui.components.SpacerH
|
||||||
import com.tangem.core.ui.components.block.BlockCard
|
import com.tangem.core.ui.components.block.BlockCard
|
||||||
|
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
||||||
|
import com.tangem.core.ui.components.label.Label
|
||||||
|
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.utils.StringsSigns
|
import com.tangem.utils.StringsSigns
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ArticleCard(articleConfigUM: ArticleConfigUM, onArticleClick: () -> Unit, modifier: Modifier = Modifier) {
|
fun ArticleCard(
|
||||||
|
articleConfigUM: ArticleConfigUM,
|
||||||
|
onArticleClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
colors: CardColors = TangemBlockCardColors,
|
||||||
|
) {
|
||||||
BlockCard(
|
BlockCard(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClick = onArticleClick,
|
onClick = onArticleClick,
|
||||||
|
colors = colors,
|
||||||
) {
|
) {
|
||||||
if (articleConfigUM.isTrending) {
|
if (articleConfigUM.isTrending) {
|
||||||
TrendingArticle(articleConfigUM = articleConfigUM)
|
TrendingArticle(articleConfigUM = articleConfigUM)
|
||||||
|
|
@ -121,54 +127,13 @@ private fun DefaultArticle(articleConfigUM: ArticleConfigUM) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ArticleInfo(score: Float, createdAt: String, modifier: Modifier = Modifier) {
|
|
||||||
val dotColor = TangemTheme.colors.text.secondary
|
|
||||||
Row(
|
|
||||||
modifier = modifier,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
imageVector = ImageVector.vectorResource(R.drawable.ic_start_circle_12),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = score.toString(),
|
|
||||||
style = TangemTheme.typography.subtitle2,
|
|
||||||
color = TangemTheme.colors.text.secondary,
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(4.dp)
|
|
||||||
.drawWithCache {
|
|
||||||
val radius = size.minDimension / 2f
|
|
||||||
onDrawBehind {
|
|
||||||
drawCircle(
|
|
||||||
color = dotColor,
|
|
||||||
radius = radius,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = createdAt,
|
|
||||||
style = TangemTheme.typography.subtitle2,
|
|
||||||
color = TangemTheme.colors.text.secondary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun Tags(tags: ImmutableList<ArticleTagUM>, modifier: Modifier = Modifier) {
|
private fun Tags(tags: ImmutableList<LabelUM>, modifier: Modifier = Modifier) {
|
||||||
val expandIndicator = remember {
|
val expandIndicator = remember {
|
||||||
ContextualFlowRowOverflow.expandIndicator {
|
ContextualFlowRowOverflow.expandIndicator {
|
||||||
val remainingItems = tags.size - shownItemCount
|
val remainingItems = tags.size - shownItemCount
|
||||||
ArticleBadge(articleTagUM = ArticleTagUM.Category(TextReference.Str("${StringsSigns.PLUS}$remainingItems")))
|
Label(state = LabelUM(TextReference.Str("${StringsSigns.PLUS}$remainingItems")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContextualFlowRow(
|
ContextualFlowRow(
|
||||||
|
|
@ -179,7 +144,7 @@ private fun Tags(tags: ImmutableList<ArticleTagUM>, modifier: Modifier = Modifie
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = expandIndicator,
|
overflow = expandIndicator,
|
||||||
) { index ->
|
) { index ->
|
||||||
ArticleBadge(articleTagUM = tags[index])
|
Label(state = tags[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,14 +154,14 @@ private fun Tags(tags: ImmutableList<ArticleTagUM>, modifier: Modifier = Modifie
|
||||||
private fun TagsPreview() {
|
private fun TagsPreview() {
|
||||||
TangemThemePreview {
|
TangemThemePreview {
|
||||||
Tags(
|
Tags(
|
||||||
tags = listOf(
|
tags = persistentListOf(
|
||||||
ArticleTagUM.Category(TextReference.Str("Hype")),
|
LabelUM(TextReference.Str("Hype")),
|
||||||
ArticleTagUM.Category(TextReference.Str("BTC")),
|
LabelUM(TextReference.Str("BTC")),
|
||||||
ArticleTagUM.Category(TextReference.Str("Supply")),
|
LabelUM(TextReference.Str("Supply")),
|
||||||
ArticleTagUM.Category(TextReference.Str("Demand")),
|
LabelUM(TextReference.Str("Demand")),
|
||||||
ArticleTagUM.Category(TextReference.Str("Best rate")),
|
LabelUM(TextReference.Str("Best rate")),
|
||||||
ArticleTagUM.Category(TextReference.Str("Breaking news")),
|
LabelUM(TextReference.Str("Breaking news")),
|
||||||
).toPersistentList(),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -206,12 +171,11 @@ private fun TagsPreview() {
|
||||||
@Composable
|
@Composable
|
||||||
private fun ArticleCardsPreview() {
|
private fun ArticleCardsPreview() {
|
||||||
val tags = listOf(
|
val tags = listOf(
|
||||||
ArticleTagUM.Category(TextReference.Str("Hype")),
|
LabelUM(TextReference.Str("Hype")),
|
||||||
ArticleTagUM.Category(TextReference.Str("BTC")),
|
LabelUM(TextReference.Str("BTC")),
|
||||||
ArticleTagUM.Category(TextReference.Str("Supply")),
|
LabelUM(TextReference.Str("Supply")),
|
||||||
ArticleTagUM.Category(TextReference.Str("Demand")),
|
LabelUM(TextReference.Str("Demand")),
|
||||||
ArticleTagUM.Category(TextReference.Str("Best rate")),
|
LabelUM(TextReference.Str("Breaking news")),
|
||||||
ArticleTagUM.Category(TextReference.Str("Breaking news")),
|
|
||||||
).toImmutableSet()
|
).toImmutableSet()
|
||||||
|
|
||||||
val config = ArticleConfigUM(
|
val config = ArticleConfigUM(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.common.ui.news
|
package com.tangem.common.ui.news
|
||||||
|
|
||||||
|
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
|
|
||||||
data class ArticleConfigUM(
|
data class ArticleConfigUM(
|
||||||
|
|
@ -8,6 +9,6 @@ data class ArticleConfigUM(
|
||||||
val score: Float,
|
val score: Float,
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
val isTrending: Boolean,
|
val isTrending: Boolean,
|
||||||
val tags: ImmutableSet<ArticleTagUM>,
|
val tags: ImmutableSet<LabelUM>,
|
||||||
val isViewed: Boolean,
|
val isViewed: Boolean,
|
||||||
)
|
)
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.tangem.common.ui.news
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.core.ui.components.label.Label
|
||||||
|
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ArticleHeader(
|
||||||
|
title: String,
|
||||||
|
createdAt: String,
|
||||||
|
score: Float,
|
||||||
|
tags: ImmutableList<LabelUM>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
ArticleInfo(
|
||||||
|
score = score,
|
||||||
|
createdAt = createdAt,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = TangemTheme.typography.h2,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (tags.isNotEmpty()) {
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
FlowRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
tags.forEach { tag ->
|
||||||
|
Label(
|
||||||
|
state = tag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.tangem.common.ui.news
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.drawWithCache
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun ArticleInfo(score: Float, createdAt: String, modifier: Modifier = Modifier) {
|
||||||
|
val dotColor = TangemTheme.colors.text.secondary
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
imageVector = ImageVector.vectorResource(R.drawable.ic_start_circle_12),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = score.toString(),
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(4.dp)
|
||||||
|
.drawWithCache {
|
||||||
|
val radius = size.minDimension / 2f
|
||||||
|
onDrawBehind {
|
||||||
|
drawCircle(
|
||||||
|
color = dotColor,
|
||||||
|
radius = radius,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = createdAt,
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.tangem.common.ui.news
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.core.ui.components.RectangleShimmer
|
||||||
|
import com.tangem.core.ui.components.SpacerH
|
||||||
|
import com.tangem.core.ui.components.block.BlockCard
|
||||||
|
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TrendingLoadingArticle(modifier: Modifier = Modifier) {
|
||||||
|
BlockCard(
|
||||||
|
modifier = modifier,
|
||||||
|
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 24.dp, horizontal = 16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 96.dp, height = 24.dp), radius = 8.dp)
|
||||||
|
SpacerH(12.dp)
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 285.dp, height = 18.dp), radius = 4.dp)
|
||||||
|
SpacerH(6.dp)
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 190.dp, height = 18.dp), radius = 4.dp)
|
||||||
|
SpacerH(14.dp)
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 110.dp, height = 18.dp), radius = 4.dp)
|
||||||
|
SpacerH(32.dp)
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 64.dp, height = 24.dp), radius = 8.dp)
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 64.dp, height = 24.dp), radius = 8.dp)
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 64.dp, height = 24.dp), radius = 8.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DefaultLoadingArticle() {
|
||||||
|
BlockCard(
|
||||||
|
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(12.dp)) {
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 110.dp, height = 16.dp), radius = 4.dp)
|
||||||
|
SpacerH(12.dp)
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 142.dp, height = 18.dp), radius = 4.dp)
|
||||||
|
SpacerH(6.dp)
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 176.dp, height = 18.dp), radius = 4.dp)
|
||||||
|
SpacerH(6.dp)
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 120.dp, height = 18.dp), radius = 4.dp)
|
||||||
|
SpacerH(16.dp)
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 72.dp, height = 24.dp), radius = 8.dp)
|
||||||
|
RectangleShimmer(modifier = Modifier.size(width = 72.dp, height = 24.dp), radius = 8.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package com.tangem.common.ui.news
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
sealed interface ArticleTagUM {
|
|
||||||
|
|
||||||
val title: TextReference
|
|
||||||
|
|
||||||
data class Category(override val title: TextReference) : ArticleTagUM
|
|
||||||
|
|
||||||
data class Token(
|
|
||||||
override val title: TextReference,
|
|
||||||
val iconState: CurrencyIconState,
|
|
||||||
) : ArticleTagUM
|
|
||||||
}
|
|
||||||
|
|
@ -21,7 +21,7 @@ import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||||
import com.tangem.domain.models.staking.YieldBalance
|
import com.tangem.domain.models.staking.StakingBalance
|
||||||
import com.tangem.domain.staking.model.isStakingSupported
|
import com.tangem.domain.staking.model.isStakingSupported
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
|
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
|
||||||
|
|
@ -171,7 +171,7 @@ class TokenItemStateConverter(
|
||||||
return totalAmount.format { crypto(currency) }
|
return totalAmount.format { crypto(currency) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CryptoCurrencyStatus.getStakedBalance() = (value.yieldBalance as? YieldBalance.Data)
|
private fun CryptoCurrencyStatus.getStakedBalance() = (value.stakingBalance as? StakingBalance.Data)
|
||||||
?.getTotalWithRewardsStakingBalance(blockchainId = currency.network.rawId).orZero()
|
?.getTotalWithRewardsStakingBalance(blockchainId = currency.network.rawId).orZero()
|
||||||
|
|
||||||
private fun createTitleState(
|
private fun createTitleState(
|
||||||
|
|
@ -278,12 +278,13 @@ class TokenItemStateConverter(
|
||||||
val validators = stakingApyMap[stakingKey]
|
val validators = stakingApyMap[stakingKey]
|
||||||
?: return StakingLocalInfo(rate = null, isActive = false, rewardType = null)
|
?: return StakingLocalInfo(rate = null, isActive = false, rewardType = null)
|
||||||
|
|
||||||
val yieldBalance = currencyStatus.value.yieldBalance
|
val stakingBalance = currencyStatus.value.stakingBalance as? StakingBalance.Data
|
||||||
val hasStakedBalance = yieldBalance is YieldBalance.Data
|
val stakeKitBalance = stakingBalance as? StakingBalance.Data.StakeKit
|
||||||
|
|
||||||
val rateInfo: Pair<BigDecimal, Yield.RewardType?>? = if (hasStakedBalance) {
|
val rateInfo: Pair<BigDecimal, Yield.RewardType?>? = if (stakeKitBalance != null) {
|
||||||
|
// StakeKit-specific: try to find rate from validator address
|
||||||
val validatorsByAddress = validators.associateBy { it.address }
|
val validatorsByAddress = validators.associateBy { it.address }
|
||||||
yieldBalance.balance.items
|
stakeKitBalance.balance.items
|
||||||
.mapNotNull { it.validatorAddress }
|
.mapNotNull { it.validatorAddress }
|
||||||
.firstNotNullOfOrNull { address ->
|
.firstNotNullOfOrNull { address ->
|
||||||
val validator = validatorsByAddress[address]
|
val validator = validatorsByAddress[address]
|
||||||
|
|
@ -298,6 +299,8 @@ class TokenItemStateConverter(
|
||||||
}
|
}
|
||||||
.maxByOrNull { it.first }
|
.maxByOrNull { it.first }
|
||||||
} else {
|
} else {
|
||||||
|
// P2P or no balance: use preferred validators
|
||||||
|
// TODO p2p
|
||||||
validators
|
validators
|
||||||
.filter { it.preferred }
|
.filter { it.preferred }
|
||||||
.mapNotNull { validator ->
|
.mapNotNull { validator ->
|
||||||
|
|
@ -310,7 +313,7 @@ class TokenItemStateConverter(
|
||||||
|
|
||||||
return StakingLocalInfo(
|
return StakingLocalInfo(
|
||||||
rate = rateInfo?.first,
|
rate = rateInfo?.first,
|
||||||
isActive = hasStakedBalance,
|
isActive = stakingBalance != null,
|
||||||
rewardType = rateInfo?.second,
|
rewardType = rateInfo?.second,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.tangem.common.ui.userwallet
|
package com.tangem.common.ui.userwallet
|
||||||
|
|
||||||
import com.tangem.common.ui.R
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
|
import com.tangem.core.analytics.models.event.SignIn
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
|
@ -13,6 +15,7 @@ import com.tangem.domain.common.wallets.error.UnlockWalletError.UnableToUnlock.R
|
||||||
inline fun UnlockWalletError.handle(
|
inline fun UnlockWalletError.handle(
|
||||||
onAlreadyUnlocked: () -> Unit = {},
|
onAlreadyUnlocked: () -> Unit = {},
|
||||||
onUserCancelled: () -> Unit = {},
|
onUserCancelled: () -> Unit = {},
|
||||||
|
analyticsEventHandler: AnalyticsEventHandler,
|
||||||
noinline showMessage: (EventMessage) -> Unit,
|
noinline showMessage: (EventMessage) -> Unit,
|
||||||
) {
|
) {
|
||||||
when (this) {
|
when (this) {
|
||||||
|
|
@ -30,15 +33,20 @@ inline fun UnlockWalletError.handle(
|
||||||
// This should never happen in this flow, as we always check for the wallet existence before unlocking
|
// This should never happen in this flow, as we always check for the wallet existence before unlocking
|
||||||
showMessage(SnackbarMessage(TextReference.Res(R.string.generic_error)))
|
showMessage(SnackbarMessage(TextReference.Res(R.string.generic_error)))
|
||||||
}
|
}
|
||||||
is UnlockWalletError.UnableToUnlock -> handleUnableToUnlock(this, showMessage)
|
is UnlockWalletError.UnableToUnlock -> handleUnableToUnlock(this, analyticsEventHandler, showMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleUnableToUnlock(error: UnlockWalletError.UnableToUnlock, showDialog: (DialogMessage) -> Unit) {
|
fun handleUnableToUnlock(
|
||||||
|
error: UnlockWalletError.UnableToUnlock,
|
||||||
|
analyticsEventHandler: AnalyticsEventHandler,
|
||||||
|
showDialog: (DialogMessage) -> Unit,
|
||||||
|
) {
|
||||||
val dialogMessage = when (error) {
|
val dialogMessage = when (error) {
|
||||||
is UnlockWalletError.UnableToUnlock.WithReason -> {
|
is UnlockWalletError.UnableToUnlock.WithReason -> {
|
||||||
when (error.reason) {
|
when (error.reason) {
|
||||||
Reason.AllKeysInvalidated -> {
|
Reason.AllKeysInvalidated -> {
|
||||||
|
analyticsEventHandler.send(SignIn.ErrorBiometricUpdated())
|
||||||
DialogMessage(
|
DialogMessage(
|
||||||
title = resourceReference(R.string.biometric_updated_warning_title),
|
title = resourceReference(R.string.biometric_updated_warning_title),
|
||||||
message = resourceReference(R.string.biometric_updated_warning_description),
|
message = resourceReference(R.string.biometric_updated_warning_description),
|
||||||
|
|
|
||||||
17
core/analytics/models/detekt-baseline-main.xml
Normal file
17
core/analytics/models/detekt-baseline-main.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<SmellBaseline>
|
||||||
|
<ManuallySuppressedIssues/>
|
||||||
|
<CurrentIssues>
|
||||||
|
<ID>MultilineLambdaItParameter:TechAnalyticsEvent.kt$TechAnalyticsEvent.KeyboardIdentifier${ put("Package", it) put("GPUrl", "https://play.google.com/store/apps/details?id=$packageName") }</ID>
|
||||||
|
<ID>UseEmptyCounterpart:AnalyticsEvent.kt$AnalyticsEvent$mapOf()</ID>
|
||||||
|
<ID>UseEmptyCounterpart:Basic.kt$Basic$mapOf()</ID>
|
||||||
|
<ID>UseEmptyCounterpart:ExceptionAnalyticsEvent.kt$ExceptionAnalyticsEvent$mapOf()</ID>
|
||||||
|
<ID>UseEmptyCounterpart:MainScreenAnalyticsEvent.kt$MainScreenAnalyticsEvent$mapOf()</ID>
|
||||||
|
<ID>UseEmptyCounterpart:OnboardingAnalyticsEvent.kt$OnboardingAnalyticsEvent$mapOf()</ID>
|
||||||
|
<ID>UseEmptyCounterpart:OnboardingAnalyticsEvent.kt$OnboardingAnalyticsEvent.CreateWallet$mapOf()</ID>
|
||||||
|
<ID>UseEmptyCounterpart:OnboardingAnalyticsEvent.kt$OnboardingAnalyticsEvent.Error$mapOf()</ID>
|
||||||
|
<ID>UseEmptyCounterpart:OnboardingAnalyticsEvent.kt$OnboardingAnalyticsEvent.Onboarding$mapOf()</ID>
|
||||||
|
<ID>UseEmptyCounterpart:OnboardingAnalyticsEvent.kt$OnboardingAnalyticsEvent.SeedPhrase$mapOf()</ID>
|
||||||
|
<ID>UseEmptyCounterpart:TechAnalyticsEvent.kt$TechAnalyticsEvent$mapOf()</ID>
|
||||||
|
</CurrentIssues>
|
||||||
|
</SmellBaseline>
|
||||||
|
|
@ -11,11 +11,6 @@ sealed class AnalyticsParam {
|
||||||
companion object
|
companion object
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class TokenBalanceState(val value: String) {
|
|
||||||
data object Empty : TokenBalanceState("Empty")
|
|
||||||
data object Full : TokenBalanceState("Full")
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class RateApp(val value: String) {
|
sealed class RateApp(val value: String) {
|
||||||
data object Liked : RateApp("Liked")
|
data object Liked : RateApp("Liked")
|
||||||
data object Disliked : RateApp("Disliked")
|
data object Disliked : RateApp("Disliked")
|
||||||
|
|
@ -83,12 +78,14 @@ sealed class AnalyticsParam {
|
||||||
data object Onboarding : ScreensSources("Onboarding")
|
data object Onboarding : ScreensSources("Onboarding")
|
||||||
data object LongTap : ScreensSources("Long Tap")
|
data object LongTap : ScreensSources("Long Tap")
|
||||||
data object Markets : ScreensSources("Markets")
|
data object Markets : ScreensSources("Markets")
|
||||||
data object HotWallet : ScreensSources("Hot Wallet")
|
|
||||||
data object TangemPay : ScreensSources("Tangem Pay")
|
data object TangemPay : ScreensSources("Tangem Pay")
|
||||||
data object WalletSettings : ScreensSources("Wallet Settings")
|
data object WalletSettings : ScreensSources("Wallet Settings")
|
||||||
data object Upgrade : ScreensSources("Upgrade")
|
data object Upgrade : ScreensSources("Upgrade")
|
||||||
data object HardwareWallet : ScreensSources("Hardware Wallet")
|
data object HardwareWallet : ScreensSources("Hardware Wallet")
|
||||||
data object ImportWallet : ScreensSources("Import Wallet")
|
data object ImportWallet : ScreensSources("Import Wallet")
|
||||||
|
data object CreateWalletIntro : ScreensSources("Create Wallet Intro")
|
||||||
|
data object AddNewWallet : ScreensSources("Add New Wallet")
|
||||||
|
data object CreateWallet : ScreensSources("Create Wallet")
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class TxSentFrom(val value: String) {
|
sealed class TxSentFrom(val value: String) {
|
||||||
|
|
@ -203,8 +200,9 @@ sealed class AnalyticsParam {
|
||||||
Pending(value = "Pending"),
|
Pending(value = "Pending"),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class EnsStatus(val value: String) {
|
enum class EmptyFull(val value: String) {
|
||||||
EMPTY("Empty"), FULL("Full")
|
Empty("Empty"),
|
||||||
|
Full("Full"),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ProductType(val value: String) {
|
enum class ProductType(val value: String) {
|
||||||
|
|
@ -268,5 +266,6 @@ sealed class AnalyticsParam {
|
||||||
const val CHOSEN_TOKEN = "Token Chosen"
|
const val CHOSEN_TOKEN = "Token Chosen"
|
||||||
const val ENS = "ENS"
|
const val ENS = "ENS"
|
||||||
const val ENS_ADDRESS = "ENS Address"
|
const val ENS_ADDRESS = "ENS Address"
|
||||||
|
const val ACCOUNT_DERIVATION_FROM = "Account Derivation (from)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,11 +10,11 @@ sealed class Basic(
|
||||||
) : Basic(
|
) : Basic(
|
||||||
event = "Card Was Scanned",
|
event = "Card Was Scanned",
|
||||||
params = mapOf(
|
params = mapOf(
|
||||||
AnalyticsParam.SOURCE to source.value,
|
AnalyticsParam.Key.SOURCE to source.value,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class SignedIn(
|
class SignedInLegacy(
|
||||||
currency: AnalyticsParam.WalletType,
|
currency: AnalyticsParam.WalletType,
|
||||||
batch: String,
|
batch: String,
|
||||||
signInType: SignInType,
|
signInType: SignInType,
|
||||||
|
|
@ -24,8 +24,8 @@ sealed class Basic(
|
||||||
) : Basic(
|
) : Basic(
|
||||||
event = "Signed in",
|
event = "Signed in",
|
||||||
params = buildMap {
|
params = buildMap {
|
||||||
put(AnalyticsParam.CURRENCY, currency.value)
|
put(AnalyticsParam.Key.CURRENCY, currency.value)
|
||||||
put(AnalyticsParam.BATCH, batch)
|
put(AnalyticsParam.Key.BATCH, batch)
|
||||||
put("Wallet Type", if (isImported) "Seed Phrase" else "Seedless")
|
put("Wallet Type", if (isImported) "Seed Phrase" else "Seedless")
|
||||||
put("Sign in type", signInType.name)
|
put("Sign in type", signInType.name)
|
||||||
put("Wallets Count", walletsCount)
|
put("Wallets Count", walletsCount)
|
||||||
|
|
@ -39,10 +39,37 @@ sealed class Basic(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SignedIn(
|
||||||
|
signInType: SignInType,
|
||||||
|
walletsCount: Int,
|
||||||
|
) : Basic(
|
||||||
|
event = "Signed in",
|
||||||
|
params = buildMap {
|
||||||
|
put("Sign in type", signInType.value)
|
||||||
|
put("Wallets Count", walletsCount.toString())
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
enum class SignInType(val value: String) {
|
||||||
|
Card("Card"),
|
||||||
|
Biometric("Biometric"),
|
||||||
|
NoSecurity("No Security"),
|
||||||
|
AccessCode("Access Code"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ButtonBuy(
|
||||||
|
source: AnalyticsParam.ScreensSources,
|
||||||
|
) : Basic(
|
||||||
|
event = "Button - Buy",
|
||||||
|
params = buildMap {
|
||||||
|
put(AnalyticsParam.Key.SOURCE, source.value)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
class ToppedUp(userWalletId: String, currency: AnalyticsParam.WalletType) :
|
class ToppedUp(userWalletId: String, currency: AnalyticsParam.WalletType) :
|
||||||
Basic(
|
Basic(
|
||||||
event = "Topped up",
|
event = "Topped up",
|
||||||
params = mapOf(AnalyticsParam.CURRENCY to currency.value),
|
params = mapOf(AnalyticsParam.Key.CURRENCY to currency.value),
|
||||||
),
|
),
|
||||||
OneTimeAnalyticsEvent {
|
OneTimeAnalyticsEvent {
|
||||||
|
|
||||||
|
|
@ -53,16 +80,16 @@ sealed class Basic(
|
||||||
Basic(
|
Basic(
|
||||||
event = "Transaction sent",
|
event = "Transaction sent",
|
||||||
params = buildMap {
|
params = buildMap {
|
||||||
this[AnalyticsParam.SOURCE] = sentFrom.value
|
this[AnalyticsParam.Key.SOURCE] = sentFrom.value
|
||||||
if (sentFrom is AnalyticsParam.TxData) {
|
if (sentFrom is AnalyticsParam.TxData) {
|
||||||
this[AnalyticsParam.BLOCKCHAIN] = sentFrom.blockchain
|
this[AnalyticsParam.Key.BLOCKCHAIN] = sentFrom.blockchain
|
||||||
this[AnalyticsParam.TOKEN_PARAM] = sentFrom.token
|
this[AnalyticsParam.Key.TOKEN_PARAM] = sentFrom.token
|
||||||
sentFrom.feeType?.value?.let {
|
sentFrom.feeType?.value?.let {
|
||||||
this[AnalyticsParam.FEE_TYPE] = it
|
this[AnalyticsParam.Key.FEE_TYPE] = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
|
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
|
||||||
this[AnalyticsParam.PERMISSION_TYPE] = sentFrom.permissionType
|
this[AnalyticsParam.Key.PERMISSION_TYPE] = sentFrom.permissionType
|
||||||
}
|
}
|
||||||
this["Memo"] = memoType.name
|
this["Memo"] = memoType.name
|
||||||
},
|
},
|
||||||
|
|
@ -79,7 +106,7 @@ sealed class Basic(
|
||||||
class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic(
|
class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic(
|
||||||
event = "Request Support",
|
event = "Request Support",
|
||||||
params = mapOf(
|
params = mapOf(
|
||||||
AnalyticsParam.SOURCE to source.value,
|
AnalyticsParam.Key.SOURCE to source.value,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -89,7 +116,7 @@ sealed class Basic(
|
||||||
) : Basic(
|
) : Basic(
|
||||||
event = "Biometry Failed",
|
event = "Biometry Failed",
|
||||||
params = mapOf(
|
params = mapOf(
|
||||||
AnalyticsParam.SOURCE to source.value,
|
AnalyticsParam.Key.SOURCE to source.value,
|
||||||
"Reason" to reason.value,
|
"Reason" to reason.value,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -36,26 +36,34 @@ sealed class MainScreenAnalyticsEvent(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
data object ButtonReceive : MainScreenAnalyticsEvent(
|
class ButtonReceive : MainScreenAnalyticsEvent(
|
||||||
event = "Button - Receive",
|
event = "Button - Receive",
|
||||||
)
|
)
|
||||||
|
|
||||||
data object LimitsClicked : MainScreenAnalyticsEvent(
|
class LimitsClicked : MainScreenAnalyticsEvent(
|
||||||
event = "Limits Clicked",
|
event = "Limits Clicked",
|
||||||
)
|
)
|
||||||
|
|
||||||
data object NoticeBalancesInfo : MainScreenAnalyticsEvent(
|
class NoticeBalancesInfo : MainScreenAnalyticsEvent(
|
||||||
event = "Notice - Balances Info",
|
event = "Notice - Balances Info",
|
||||||
)
|
)
|
||||||
|
|
||||||
data object NoticeLimitsInfo : MainScreenAnalyticsEvent(
|
class NoticeLimitsInfo : MainScreenAnalyticsEvent(
|
||||||
event = "Notice - Limits Info",
|
event = "Notice - Limits Info",
|
||||||
)
|
)
|
||||||
|
|
||||||
data object ButtonExplore : MainScreenAnalyticsEvent(
|
class ButtonExplore : MainScreenAnalyticsEvent(
|
||||||
event = "Button - Explore",
|
event = "Button - Explore",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class AccountShowTokens : MainScreenAnalyticsEvent(
|
||||||
|
event = "Button - Account Show Tokens",
|
||||||
|
)
|
||||||
|
|
||||||
|
class AccountHideTokens : MainScreenAnalyticsEvent(
|
||||||
|
event = "Button - Account Hide Tokens",
|
||||||
|
)
|
||||||
|
|
||||||
data class ButtonSwap(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
|
data class ButtonSwap(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
|
||||||
event = "Button - Swap",
|
event = "Button - Swap",
|
||||||
params = mapOf(AnalyticsParam.STATUS to status.value),
|
params = mapOf(AnalyticsParam.STATUS to status.value),
|
||||||
|
|
@ -66,11 +74,11 @@ sealed class MainScreenAnalyticsEvent(
|
||||||
params = mapOf(AnalyticsParam.STATUS to status.value),
|
params = mapOf(AnalyticsParam.STATUS to status.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
data object BuyScreenOpened : MainScreenAnalyticsEvent(event = "Buy Screen Opened")
|
class BuyScreenOpened : MainScreenAnalyticsEvent(event = "Buy Screen Opened")
|
||||||
|
|
||||||
data object SwapScreenOpened : MainScreenAnalyticsEvent(event = "Swap Screen Opened")
|
class SwapScreenOpened : MainScreenAnalyticsEvent(event = "Swap Screen Opened")
|
||||||
|
|
||||||
data object SellScreenOpened : MainScreenAnalyticsEvent(event = "Sell Screen Opened")
|
class SellScreenOpened : MainScreenAnalyticsEvent(event = "Sell Screen Opened")
|
||||||
|
|
||||||
data class BuyTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
|
data class BuyTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
|
||||||
event = "Buy Token Clicked",
|
event = "Buy Token Clicked",
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,92 @@ sealed class OnboardingAnalyticsEvent(
|
||||||
sealed class Onboarding(
|
sealed class Onboarding(
|
||||||
event: String,
|
event: String,
|
||||||
params: Map<String, String> = mapOf(),
|
params: Map<String, String> = mapOf(),
|
||||||
|
) : OnboardingAnalyticsEvent(category = "Onboarding", event = event, params = params) {
|
||||||
|
|
||||||
|
class Started(
|
||||||
|
source: String,
|
||||||
|
) : Onboarding(
|
||||||
|
event = "Onboarding Started",
|
||||||
|
params = mapOf(
|
||||||
|
AnalyticsParam.SOURCE to source,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Finished(
|
||||||
|
source: String,
|
||||||
|
) : Onboarding(
|
||||||
|
event = "Onboarding Finished",
|
||||||
|
params = mapOf(
|
||||||
|
AnalyticsParam.SOURCE to source,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
class ButtonMobileWallet(
|
||||||
|
source: String,
|
||||||
|
) : Onboarding(
|
||||||
|
event = "Button - Mobile Wallet",
|
||||||
|
params = mapOf(
|
||||||
|
AnalyticsParam.SOURCE to source,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class CreateWallet(
|
||||||
|
event: String,
|
||||||
|
params: Map<String, String> = mapOf(),
|
||||||
|
) : OnboardingAnalyticsEvent(category = "Onboarding / Create Wallet", event = event, params = params) {
|
||||||
|
|
||||||
|
class ButtonCreateWallet : CreateWallet("Button - Create Wallet")
|
||||||
|
|
||||||
|
class WalletCreatedSuccessfully(
|
||||||
|
source: String,
|
||||||
|
creationType: WalletCreationType = WalletCreationType.NewSeed,
|
||||||
|
seedPhraseLength: Int? = null,
|
||||||
|
passPhraseState: AnalyticsParam.EmptyFull,
|
||||||
|
) : CreateWallet(
|
||||||
|
event = "Wallet Created Successfully",
|
||||||
|
params = buildMap {
|
||||||
|
put(AnalyticsParam.SOURCE, source)
|
||||||
|
put("Creation Type", creationType.value)
|
||||||
|
put("Passphrase", passPhraseState.value)
|
||||||
|
if (seedPhraseLength != null) {
|
||||||
|
put("Seed Phrase Length", seedPhraseLength.toString())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed class WalletCreationType(val value: String) {
|
||||||
|
data object NewSeed : WalletCreationType(value = "New Seed")
|
||||||
|
data object SeedImport : WalletCreationType(value = "Seed Import")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SeedPhrase(
|
||||||
|
event: String,
|
||||||
|
params: Map<String, String> = mapOf(),
|
||||||
|
) : OnboardingAnalyticsEvent(category = "Onboarding / Seed Phrase", event = event, params = params) {
|
||||||
|
|
||||||
|
class CreateMobileScreenOpened(
|
||||||
|
source: String,
|
||||||
|
) : SeedPhrase(
|
||||||
|
event = "Create Mobile Screen Opened",
|
||||||
|
params = mapOf(
|
||||||
|
AnalyticsParam.SOURCE to source,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
class ButtonImportWallet : SeedPhrase("Button - Import Wallet")
|
||||||
|
class ImportSeedPhraseScreenOpened : SeedPhrase("Import Seed Phrase Screen Opened")
|
||||||
|
class ButtonImport : SeedPhrase("Button - Import")
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Error(
|
||||||
|
event: String,
|
||||||
|
params: Map<String, String> = mapOf(),
|
||||||
) : OnboardingAnalyticsEvent(category = "Error", event = event, params = params) {
|
) : OnboardingAnalyticsEvent(category = "Error", event = event, params = params) {
|
||||||
|
|
||||||
data class OfflineAttestationFailed(
|
data class OfflineAttestationFailed(
|
||||||
val source: AnalyticsParam.ScreensSources,
|
val source: AnalyticsParam.ScreensSources,
|
||||||
) : Onboarding(
|
) : Error(
|
||||||
event = "Offline Attestation Failed",
|
event = "Offline Attestation Failed",
|
||||||
params = mapOf(AnalyticsParam.SOURCE to source.value),
|
params = mapOf(AnalyticsParam.SOURCE to source.value),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.tangem.core.analytics.models.event
|
||||||
|
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
|
|
||||||
|
sealed class SignIn(
|
||||||
|
event: String,
|
||||||
|
params: Map<String, String> = emptyMap(),
|
||||||
|
) : AnalyticsEvent("Sign In", event, params) {
|
||||||
|
|
||||||
|
data class ScreenOpened(
|
||||||
|
val walletsCount: Int,
|
||||||
|
) : SignIn(
|
||||||
|
event = "Sign In Screen Opened",
|
||||||
|
params = mapOf(
|
||||||
|
"Wallets Count" to walletsCount.toString(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
|
||||||
|
|
||||||
|
class ButtonUnlockAllWithBiometric : SignIn(event = "Button - Unlock All With Biometric")
|
||||||
|
|
||||||
|
class ErrorBiometricUpdated : SignIn(event = "Error - Biometric Updated")
|
||||||
|
|
||||||
|
class ButtonWallet(
|
||||||
|
signInType: SignInType,
|
||||||
|
walletsCount: Int,
|
||||||
|
) : SignIn(
|
||||||
|
event = "Button - Wallet",
|
||||||
|
params = buildMap {
|
||||||
|
put("Wallets Count", walletsCount.toString())
|
||||||
|
put("Sign in type", signInType.value)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
enum class SignInType(val value: String) {
|
||||||
|
Card("Card"),
|
||||||
|
Biometric("Biometric"),
|
||||||
|
NoSecurity("No Security"),
|
||||||
|
AccessCode("Access Code"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ButtonAddWallet(
|
||||||
|
val sources: AnalyticsParam.ScreensSources,
|
||||||
|
) : SignIn(
|
||||||
|
event = "Button - Add Wallet",
|
||||||
|
params = mapOf(
|
||||||
|
AnalyticsParam.SOURCE to sources.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "HOT_WALLET_ENABLED",
|
"name": "HOT_WALLET_ENABLED",
|
||||||
"version": "undefined"
|
"version": "5.32.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "TANGEM_PAY_ENABLED",
|
"name": "TANGEM_PAY_ENABLED",
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ enum class ApiEnvironment {
|
||||||
@Json(name = "STAGE")
|
@Json(name = "STAGE")
|
||||||
STAGE,
|
STAGE,
|
||||||
|
|
||||||
|
@Json(name = "STAGE_2")
|
||||||
|
STAGE_2,
|
||||||
|
|
||||||
@Json(name = "MOCK")
|
@Json(name = "MOCK")
|
||||||
MOCK,
|
MOCK,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ internal class Express(
|
||||||
createDev2Environment(),
|
createDev2Environment(),
|
||||||
createDev3Environment(),
|
createDev3Environment(),
|
||||||
createStageEnvironment(),
|
createStageEnvironment(),
|
||||||
|
createStage2Environment(),
|
||||||
createMockedEnvironment(),
|
createMockedEnvironment(),
|
||||||
createProdEnvironment(),
|
createProdEnvironment(),
|
||||||
)
|
)
|
||||||
|
|
@ -73,6 +74,12 @@ internal class Express(
|
||||||
headers = createHeaders(isProd = false),
|
headers = createHeaders(isProd = false),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun createStage2Environment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||||
|
environment = ApiEnvironment.STAGE_2,
|
||||||
|
baseUrl = "[REDACTED_ENV_URL]",
|
||||||
|
headers = createHeaders(isProd = false),
|
||||||
|
)
|
||||||
|
|
||||||
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||||
environment = ApiEnvironment.MOCK,
|
environment = ApiEnvironment.MOCK,
|
||||||
baseUrl = "[REDACTED_ENV_URL]",
|
baseUrl = "[REDACTED_ENV_URL]",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.datasource.api.common.config
|
package com.tangem.datasource.api.common.config
|
||||||
|
|
||||||
import com.tangem.datasource.BuildConfig
|
import com.tangem.datasource.BuildConfig
|
||||||
|
import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
|
||||||
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
||||||
import com.tangem.utils.ProviderSuspend
|
import com.tangem.utils.ProviderSuspend
|
||||||
|
|
||||||
|
|
@ -22,12 +23,7 @@ internal class P2PEthPool(
|
||||||
private fun getInitialEnvironment(): ApiEnvironment {
|
private fun getInitialEnvironment(): ApiEnvironment {
|
||||||
return when (BuildConfig.BUILD_TYPE) {
|
return when (BuildConfig.BUILD_TYPE) {
|
||||||
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
|
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
|
||||||
DEBUG_BUILD_TYPE,
|
else -> if (P2PStakingConfig.USE_TESTNET) ApiEnvironment.DEV else ApiEnvironment.PROD
|
||||||
INTERNAL_BUILD_TYPE,
|
|
||||||
EXTERNAL_BUILD_TYPE,
|
|
||||||
RELEASE_BUILD_TYPE,
|
|
||||||
-> ApiEnvironment.PROD
|
|
||||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ internal class YieldSupply(
|
||||||
ApiEnvironment.DEV_2,
|
ApiEnvironment.DEV_2,
|
||||||
ApiEnvironment.DEV_3,
|
ApiEnvironment.DEV_3,
|
||||||
ApiEnvironment.STAGE,
|
ApiEnvironment.STAGE,
|
||||||
|
ApiEnvironment.STAGE_2,
|
||||||
-> environmentConfigStorage.getConfigSync().yieldModuleApiKeyDev
|
-> environmentConfigStorage.getConfigSync().yieldModuleApiKeyDev
|
||||||
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().yieldModuleApiKey
|
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().yieldModuleApiKey
|
||||||
} ?: error("No tangem tech api config provided")
|
} ?: error("No tangem tech api config provided")
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,7 @@ interface P2PEthPoolApi {
|
||||||
* @param network Ethereum pool network: "mainnet" or "hoodi" (testnet)
|
* @param network Ethereum pool network: "mainnet" or "hoodi" (testnet)
|
||||||
*/
|
*/
|
||||||
@GET("api/v1/staking/pool/{network}/vaults")
|
@GET("api/v1/staking/pool/{network}/vaults")
|
||||||
suspend fun getVaults(
|
suspend fun getVaults(@Path("network") network: String): ApiResponse<P2PEthPoolResponse<P2PEthPoolVaultsResponse>>
|
||||||
@Path("network") network: String = "mainnet",
|
|
||||||
): ApiResponse<P2PEthPoolResponse<P2PEthPoolVaultsResponse>>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare deposit transaction
|
* Prepare deposit transaction
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,6 @@ interface NewsApi {
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
||||||
private const val NEWS_PATH = "api/v1/news"
|
private const val NEWS_PATH = "v1/news"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,11 +5,5 @@ import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class NewsTrendingResponse(
|
data class NewsTrendingResponse(
|
||||||
@Json(name = "meta") val meta: NewsTrendingMetaDto,
|
|
||||||
@Json(name = "items") val items: List<NewsArticleDto>,
|
@Json(name = "items") val items: List<NewsArticleDto>,
|
||||||
)
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
data class NewsTrendingMetaDto(
|
|
||||||
@Json(name = "limit") val limit: Int,
|
|
||||||
)
|
)
|
||||||
|
|
@ -50,6 +50,12 @@ interface TangemTechApi {
|
||||||
@Body userTokens: UserTokensResponse,
|
@Body userTokens: UserTokensResponse,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
|
@PUT("/v1/wallets/{walletId}/tokens")
|
||||||
|
suspend fun saveTokens(
|
||||||
|
@Path(value = "walletId") userId: String,
|
||||||
|
@Body userTokens: UserTokensResponse,
|
||||||
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
/** Returns referral status by [walletId] */
|
/** Returns referral status by [walletId] */
|
||||||
@GET("v1/referral/{walletId}")
|
@GET("v1/referral/{walletId}")
|
||||||
suspend fun getReferralStatus(@Path("walletId") walletId: String): ApiResponse<ReferralResponse>
|
suspend fun getReferralStatus(@Path("walletId") walletId: String): ApiResponse<ReferralResponse>
|
||||||
|
|
@ -129,6 +135,12 @@ interface TangemTechApi {
|
||||||
@Body body: List<WalletIdBody>,
|
@Body body: List<WalletIdBody>,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
|
@PUT("/v1/user-wallets/applications/{application_id}/wallets")
|
||||||
|
suspend fun associateApplicationIdWithWalletsV2(
|
||||||
|
@Path("application_id") applicationId: String,
|
||||||
|
@Body body: AssociateApplicationIdWithWalletsBody,
|
||||||
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
@GET("v1/user-wallets/wallets/{wallet_id}")
|
@GET("v1/user-wallets/wallets/{wallet_id}")
|
||||||
suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse<WalletResponse>
|
suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse<WalletResponse>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class AssociateAppWithWalletsErrorResponse(
|
||||||
|
@Json(name = "missingWalletIds") val missingWalletIds: List<String>,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class AssociateApplicationIdWithWalletsBody(
|
||||||
|
@Json(name = "walletIds") val walletIds: List<String>,
|
||||||
|
)
|
||||||
|
|
@ -19,6 +19,7 @@ data class GetWalletAccountsResponse(
|
||||||
@Json(name = "group") val group: GroupType?,
|
@Json(name = "group") val group: GroupType?,
|
||||||
@Json(name = "sort") val sort: SortType?,
|
@Json(name = "sort") val sort: SortType?,
|
||||||
@Json(name = "totalAccounts") val totalAccounts: Int,
|
@Json(name = "totalAccounts") val totalAccounts: Int,
|
||||||
|
@Json(name = "totalArchivedAccounts") val totalArchivedAccounts: Int,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,7 @@ interface AppCurrencyResponseStore {
|
||||||
|
|
||||||
/** Get [CurrenciesResponse.Currency] synchronously or null */
|
/** Get [CurrenciesResponse.Currency] synchronously or null */
|
||||||
suspend fun getSyncOrNull(): CurrenciesResponse.Currency?
|
suspend fun getSyncOrNull(): CurrenciesResponse.Currency?
|
||||||
|
|
||||||
|
/** Store [CurrenciesResponse.Currency] */
|
||||||
|
suspend fun store(currency: CurrenciesResponse.Currency)
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
import com.tangem.datasource.local.preferences.utils.getObject
|
import com.tangem.datasource.local.preferences.utils.getObject
|
||||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||||
|
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,4 +26,11 @@ internal class DefaultAppCurrencyResponseStore(
|
||||||
PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
|
PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun store(currency: CurrenciesResponse.Currency) {
|
||||||
|
appPreferencesStore.storeObject(
|
||||||
|
PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
|
||||||
|
currency,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
package com.tangem.datasource.di
|
package com.tangem.datasource.di
|
||||||
|
|
||||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||||
|
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||||
|
import com.tangem.datasource.local.promo.DefaultPromoBannerStore
|
||||||
import com.tangem.datasource.local.promo.DefaultPromoStoriesStore
|
import com.tangem.datasource.local.promo.DefaultPromoStoriesStore
|
||||||
|
import com.tangem.datasource.local.promo.PromoBannerStore
|
||||||
import com.tangem.datasource.local.promo.PromoStoriesStore
|
import com.tangem.datasource.local.promo.PromoStoriesStore
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -18,4 +21,10 @@ object PromoStoreModule {
|
||||||
fun providePromoStoriesStore(): PromoStoriesStore {
|
fun providePromoStoriesStore(): PromoStoriesStore {
|
||||||
return DefaultPromoStoriesStore(dataStore = RuntimeDataStore())
|
return DefaultPromoStoriesStore(dataStore = RuntimeDataStore())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providePromoBannerStore(): PromoBannerStore {
|
||||||
|
return DefaultPromoBannerStore(dataStore = RuntimeSharedStore())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore
|
||||||
import androidx.datastore.core.DataStoreFactory
|
import androidx.datastore.core.DataStoreFactory
|
||||||
import androidx.datastore.dataStoreFile
|
import androidx.datastore.dataStoreFile
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.tangem.datasource.api.ethpool.models.response.P2PEthPoolAccountResponse
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||||
|
|
@ -77,6 +78,24 @@ internal object StakingStoreModule {
|
||||||
return DefaultStakingActionsStore(dataStore = RuntimeDataStore())
|
return DefaultStakingActionsStore(dataStore = RuntimeDataStore())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideP2PBalancesPersistenceStore(
|
||||||
|
@NetworkMoshi moshi: Moshi,
|
||||||
|
@ApplicationContext context: Context,
|
||||||
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
): DataStore<Map<String, Set<P2PEthPoolAccountResponse>>> {
|
||||||
|
return DataStoreFactory.create(
|
||||||
|
serializer = MoshiDataStoreSerializer(
|
||||||
|
moshi = moshi,
|
||||||
|
types = mapWithStringKeyTypes(valueTypes = setTypes<P2PEthPoolAccountResponse>()),
|
||||||
|
defaultValue = emptyMap(),
|
||||||
|
),
|
||||||
|
produceFile = { context.dataStoreFile(fileName = "p2p_balances") },
|
||||||
|
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideP2PEthPoolVaultsStore(
|
fun provideP2PEthPoolVaultsStore(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.tangem.datasource.local.promo
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.promotion.models.PromoBannerResponse
|
||||||
|
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||||
|
|
||||||
|
internal class DefaultPromoBannerStore(
|
||||||
|
private val dataStore: RuntimeSharedStore<Map<String, PromoBannerResponse>>,
|
||||||
|
) : PromoBannerStore {
|
||||||
|
|
||||||
|
override suspend fun getSyncOrNull(promoId: String): PromoBannerResponse? {
|
||||||
|
return dataStore.getSyncOrNull()?.get(promoId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun store(promoId: String, promoBanner: PromoBannerResponse) {
|
||||||
|
dataStore.update(emptyMap()) { current ->
|
||||||
|
current + (promoId to promoBanner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.tangem.datasource.local.promo
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.promotion.models.PromoBannerResponse
|
||||||
|
|
||||||
|
interface PromoBannerStore {
|
||||||
|
|
||||||
|
suspend fun getSyncOrNull(promoId: String): PromoBannerResponse?
|
||||||
|
|
||||||
|
suspend fun store(promoId: String, promoBanner: PromoBannerResponse)
|
||||||
|
}
|
||||||
|
|
@ -4,28 +4,32 @@ import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||||
import com.tangem.domain.models.StatusSource
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.models.staking.BalanceItem
|
import com.tangem.domain.models.staking.BalanceItem
|
||||||
|
import com.tangem.domain.models.staking.StakingBalance
|
||||||
import com.tangem.domain.models.staking.StakingID
|
import com.tangem.domain.models.staking.StakingID
|
||||||
import com.tangem.domain.models.staking.YieldBalance
|
|
||||||
import com.tangem.domain.models.staking.YieldBalanceItem
|
import com.tangem.domain.models.staking.YieldBalanceItem
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
class YieldBalanceConverter(
|
/**
|
||||||
|
* Converts StakeKit DTO to [StakingBalance].
|
||||||
|
* Returns [StakingBalance.Data.StakeKit] for non-empty balances, [StakingBalance.Empty] otherwise.
|
||||||
|
*/
|
||||||
|
class StakingBalanceConverter(
|
||||||
private val source: StatusSource,
|
private val source: StatusSource,
|
||||||
) : Converter<YieldBalanceWrapperDTO, YieldBalance?> {
|
) : Converter<YieldBalanceWrapperDTO, StakingBalance?> {
|
||||||
|
|
||||||
constructor(isCached: Boolean) : this(source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL)
|
constructor(isCached: Boolean) : this(source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL)
|
||||||
|
|
||||||
override fun convert(value: YieldBalanceWrapperDTO): YieldBalance? {
|
override fun convert(value: YieldBalanceWrapperDTO): StakingBalance? {
|
||||||
val stakingId = StakingID(
|
val stakingId = StakingID(
|
||||||
integrationId = value.integrationId ?: return null,
|
integrationId = value.integrationId ?: return null,
|
||||||
address = value.addresses.address,
|
address = value.addresses.address,
|
||||||
)
|
)
|
||||||
|
|
||||||
return if (value.balances.isEmpty()) {
|
return if (value.balances.isEmpty()) {
|
||||||
YieldBalance.Empty(stakingId = stakingId, source = source)
|
StakingBalance.Empty(stakingId = stakingId, source = source)
|
||||||
} else {
|
} else {
|
||||||
YieldBalance.Data(
|
StakingBalance.Data.StakeKit(
|
||||||
stakingId = stakingId,
|
stakingId = stakingId,
|
||||||
balance = YieldBalanceItem(
|
balance = YieldBalanceItem(
|
||||||
items = value.balances
|
items = value.balances
|
||||||
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