Updated on 2026-08-14
This commit is contained in:
commit
afff7892bb
721 changed files with 24513 additions and 7275 deletions
|
|
@ -18,6 +18,9 @@ android {
|
||||||
jniLibs {
|
jniLibs {
|
||||||
useLegacyPackaging = true
|
useLegacyPackaging = true
|
||||||
}
|
}
|
||||||
|
resources.excludes.add("META-INF/DEPENDENCIES")
|
||||||
|
resources.excludes.add("META-INF/LICENSE.md")
|
||||||
|
resources.excludes.add("META-INF/NOTICE.md")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.common.extensions
|
||||||
|
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
|
||||||
|
fun KNode.clickWithAssertion() {
|
||||||
|
assertIsDisplayed()
|
||||||
|
performClick()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.tangem.scenarios
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||||
|
import com.kaspersky.kaspresso.testcases.api.scenario.Scenario
|
||||||
|
import com.kaspersky.kaspresso.testcases.core.testcontext.TestContext
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.domain.models.scan.ProductType
|
||||||
|
import com.tangem.screens.DisclaimerTestScreen
|
||||||
|
import com.tangem.screens.MainTestScreen
|
||||||
|
import com.tangem.screens.StoriesTestScreen
|
||||||
|
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
|
||||||
|
class OpenMainScreenScenario(
|
||||||
|
private val testRule: ComposeTestRule,
|
||||||
|
private val productType: ProductType? = null,
|
||||||
|
) : Scenario() {
|
||||||
|
override val steps: TestContext<Unit>.() -> Unit = {
|
||||||
|
if (productType != null) {
|
||||||
|
MockProvider.setMocks(productType)
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<DisclaimerTestScreen>(testRule) {
|
||||||
|
step("Click on \"Accept\" button") {
|
||||||
|
acceptButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<StoriesTestScreen>(testRule) {
|
||||||
|
step("Click on \"Scan\" button") {
|
||||||
|
scanButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<MainTestScreen>(testRule) {
|
||||||
|
step("Make sure wallet screen is visible") {
|
||||||
|
assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.core.ui.test.TestTags
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
|
||||||
|
class DetailsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<DetailsTestScreen>(
|
||||||
|
semanticsProvider = semanticsProvider,
|
||||||
|
viewBuilderAction = { hasTestTag(TestTags.DETAILS_SCREEN) }
|
||||||
|
) {
|
||||||
|
|
||||||
|
val walletConnectButton: KNode = child {
|
||||||
|
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||||
|
hasText(getResourceString(R.string.wallet_connect_title))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val walletBlock: KNode = child {
|
||||||
|
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||||
|
}
|
||||||
|
|
||||||
|
val walletNameButton: KNode = walletBlock.child {
|
||||||
|
hasClickAction()
|
||||||
|
hasPosition(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val scanCardButton: KNode = walletBlock.child {
|
||||||
|
hasText(getResourceString(R.string.scan_card_settings_button))
|
||||||
|
}
|
||||||
|
|
||||||
|
val buyTangemButton: KNode = child {
|
||||||
|
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||||
|
hasText(getResourceString(R.string.details_buy_wallet))
|
||||||
|
}
|
||||||
|
|
||||||
|
val appSettingsButton: KNode = child {
|
||||||
|
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||||
|
hasText(getResourceString(R.string.app_settings_title))
|
||||||
|
}
|
||||||
|
val contactSupportButton: KNode = child {
|
||||||
|
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||||
|
hasText(getResourceString(R.string.details_row_title_contact_to_support))
|
||||||
|
}
|
||||||
|
val toSButton: KNode = child {
|
||||||
|
hasTestTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||||
|
hasText(getResourceString(R.string.disclaimer_title))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package com.tangem.screens
|
|
||||||
|
|
||||||
import com.kaspersky.kaspresso.screens.KScreen
|
|
||||||
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
|
|
||||||
import com.tangem.wallet.R
|
|
||||||
import io.github.kakaocup.kakao.text.KButton
|
|
||||||
|
|
||||||
object DisclaimerScreen : KScreen<DisclaimerScreen>(){
|
|
||||||
|
|
||||||
override val layoutId = R.layout.fragment_disclaimer
|
|
||||||
|
|
||||||
override val viewClass = DisclaimerFragment::class.java
|
|
||||||
|
|
||||||
val acceptButton: KButton = KButton {
|
|
||||||
withId(R.id.btn_accept)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.core.ui.test.TestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
|
||||||
|
class DisclaimerTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<DisclaimerTestScreen>(
|
||||||
|
semanticsProvider = semanticsProvider,
|
||||||
|
viewBuilderAction = { hasTestTag(TestTags.DISCLAIMER_SCREEN_CONTAINER) }
|
||||||
|
) {
|
||||||
|
|
||||||
|
val acceptButton: KNode = child {
|
||||||
|
hasTestTag(TestTags.DISCLAIMER_SCREEN_ACCEPT_BUTTON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,8 +4,8 @@ import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
import com.tangem.core.ui.test.TestTags
|
import com.tangem.core.ui.test.TestTags
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
|
||||||
class WalletScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
class MainTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
ComposeScreen<WalletScreen>(
|
ComposeScreen<MainTestScreen>(
|
||||||
semanticsProvider = semanticsProvider,
|
semanticsProvider = semanticsProvider,
|
||||||
viewBuilderAction = { hasTestTag(TestTags.WALLET_SCREEN) }
|
viewBuilderAction = { hasTestTag(TestTags.MAIN_SCREEN) }
|
||||||
)
|
)
|
||||||
|
|
@ -8,8 +8,8 @@ import io.github.kakaocup.compose.node.element.KNode
|
||||||
import io.github.kakaocup.kakao.common.views.KView
|
import io.github.kakaocup.kakao.common.views.KView
|
||||||
import io.github.kakaocup.kakao.text.KButton
|
import io.github.kakaocup.kakao.text.KButton
|
||||||
|
|
||||||
class StoriesScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
class StoriesTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
ComposeScreen<StoriesScreen>(
|
ComposeScreen<StoriesTestScreen>(
|
||||||
semanticsProvider = semanticsProvider,
|
semanticsProvider = semanticsProvider,
|
||||||
viewBuilderAction = { hasTestTag(TestTags.STORIES_SCREEN) }
|
viewBuilderAction = { hasTestTag(TestTags.STORIES_SCREEN) }
|
||||||
) {
|
) {
|
||||||
16
app/src/androidTest/kotlin/com/tangem/screens/TestTopBar.kt
Normal file
16
app/src/androidTest/kotlin/com/tangem/screens/TestTopBar.kt
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.core.ui.test.TestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
|
||||||
|
class TestTopBar(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<TestTopBar>(
|
||||||
|
semanticsProvider = semanticsProvider,
|
||||||
|
viewBuilderAction = { hasTestTag(TestTags.MAIN_SCREEN_TOP_BAR) }
|
||||||
|
) {
|
||||||
|
val moreButton: KNode = child {
|
||||||
|
hasTestTag(TestTags.MAIN_SCREEN_MORE_BUTTON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.core.ui.test.TestTags
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
|
||||||
|
class WalletSettingsTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<WalletSettingsTestScreen>(
|
||||||
|
semanticsProvider = semanticsProvider,
|
||||||
|
viewBuilderAction = { hasTestTag(TestTags.WALLET_SETTINGS_SCREEN) }
|
||||||
|
) {
|
||||||
|
private val walletSettingsItem: KNode = child {
|
||||||
|
hasTestTag(TestTags.WALLET_SETTINGS_SCREEN_ITEM)
|
||||||
|
}
|
||||||
|
|
||||||
|
val linkMoreCardsButton: KNode = walletSettingsItem.child {
|
||||||
|
hasText(getResourceString(R.string.details_row_title_create_backup))
|
||||||
|
}
|
||||||
|
val cardSettingsButton: KNode = walletSettingsItem.child {
|
||||||
|
hasText(getResourceString(R.string.card_settings_title))
|
||||||
|
}
|
||||||
|
val referralProgramButton: KNode = walletSettingsItem.child {
|
||||||
|
hasText(getResourceString(R.string.details_referral_title))
|
||||||
|
}
|
||||||
|
val forgetWalletButton: KNode = walletSettingsItem.child {
|
||||||
|
hasText(getResourceString(R.string.settings_forget_wallet))
|
||||||
|
}
|
||||||
|
}
|
||||||
160
app/src/androidTest/kotlin/com/tangem/tests/DetailsScreenTest.kt
Normal file
160
app/src/androidTest/kotlin/com/tangem/tests/DetailsScreenTest.kt
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
package com.tangem.tests
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.domain.models.scan.ProductType
|
||||||
|
import com.tangem.scenarios.OpenMainScreenScenario
|
||||||
|
import com.tangem.screens.DetailsTestScreen
|
||||||
|
import com.tangem.screens.TestTopBar
|
||||||
|
import com.tangem.screens.WalletSettingsTestScreen
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class DetailsScreenTest : BaseTestCase() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun walletWithoutBackupDetails() =
|
||||||
|
setupHooks().run {
|
||||||
|
scenario(OpenMainScreenScenario(composeTestRule))
|
||||||
|
ComposeScreen.onComposeScreen<TestTopBar>(composeTestRule) {
|
||||||
|
step("Open wallet details") {
|
||||||
|
moreButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<DetailsTestScreen>(composeTestRule) {
|
||||||
|
step("Assert wallet connect button is visible") {
|
||||||
|
walletConnectButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert scan card button is visible") {
|
||||||
|
scanCardButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert buy Tangem card button is visible") {
|
||||||
|
buyTangemButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert app settings button is visible") {
|
||||||
|
appSettingsButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert contact support button is visible") {
|
||||||
|
contactSupportButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert terms or service button is visible") {
|
||||||
|
toSButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Open wallet settings screen") {
|
||||||
|
walletNameButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<WalletSettingsTestScreen>(composeTestRule) {
|
||||||
|
step("Assert Link more cards button is visible") {
|
||||||
|
linkMoreCardsButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert Card Settings button is visible") {
|
||||||
|
cardSettingsButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert Referral program button is visible") {
|
||||||
|
referralProgramButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert Forget wallet button is visible") {
|
||||||
|
forgetWalletButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun wallet2Details() =
|
||||||
|
setupHooks().run {
|
||||||
|
scenario(OpenMainScreenScenario(composeTestRule, ProductType.Wallet2))
|
||||||
|
ComposeScreen.onComposeScreen<TestTopBar>(composeTestRule) {
|
||||||
|
step("Open wallet details") {
|
||||||
|
moreButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<DetailsTestScreen>(composeTestRule) {
|
||||||
|
step("Assert wallet connect button is visible") {
|
||||||
|
walletConnectButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert scan card button is visible") {
|
||||||
|
scanCardButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert buy Tangem card button is visible") {
|
||||||
|
buyTangemButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert app settings button is visible") {
|
||||||
|
appSettingsButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert contact support button is visible") {
|
||||||
|
contactSupportButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert terms or service button is visible") {
|
||||||
|
toSButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Open wallet settings screen") {
|
||||||
|
walletNameButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<WalletSettingsTestScreen>(composeTestRule) {
|
||||||
|
step("Assert Link more cards button does not exist") {
|
||||||
|
linkMoreCardsButton.assertIsNotDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert Card Settings button is visible") {
|
||||||
|
cardSettingsButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert Referral program button is visible") {
|
||||||
|
referralProgramButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert Forget wallet button is visible") {
|
||||||
|
forgetWalletButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun noteDetails() =
|
||||||
|
setupHooks().run {
|
||||||
|
scenario(OpenMainScreenScenario(composeTestRule, ProductType.Note))
|
||||||
|
ComposeScreen.onComposeScreen<TestTopBar>(composeTestRule) {
|
||||||
|
step("Open wallet details") {
|
||||||
|
moreButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<DetailsTestScreen>(composeTestRule) {
|
||||||
|
step("Assert wallet connect button does not exist") {
|
||||||
|
walletConnectButton.assertIsNotDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert scan card button is visible") {
|
||||||
|
scanCardButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert buy Tangem card button is visible") {
|
||||||
|
buyTangemButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert app settings button is visible") {
|
||||||
|
appSettingsButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert contact support button is visible") {
|
||||||
|
contactSupportButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert terms or service button is visible") {
|
||||||
|
toSButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Open wallet settings screen") {
|
||||||
|
walletNameButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<WalletSettingsTestScreen>(composeTestRule) {
|
||||||
|
step("Assert Link more cards button does not exist") {
|
||||||
|
linkMoreCardsButton.assertIsNotDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert Card Settings button is visible") {
|
||||||
|
cardSettingsButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert Referral program button does not exist") {
|
||||||
|
referralProgramButton.assertIsNotDisplayed()
|
||||||
|
}
|
||||||
|
step("Assert Forget wallet button is visible") {
|
||||||
|
forgetWalletButton.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
package com.tangem.tests
|
package com.tangem.tests
|
||||||
|
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.screens.DisclaimerScreen
|
import com.tangem.scenarios.OpenMainScreenScenario
|
||||||
import com.tangem.screens.StoriesScreen
|
|
||||||
import com.tangem.screens.WalletScreen
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@HiltAndroidTest
|
@HiltAndroidTest
|
||||||
|
|
@ -14,26 +11,6 @@ class MainScreenTest : BaseTestCase() {
|
||||||
@Test
|
@Test
|
||||||
fun goToMain() =
|
fun goToMain() =
|
||||||
setupHooks().run {
|
setupHooks().run {
|
||||||
ComposeScreen.onComposeScreen<StoriesScreen>(composeTestRule) {
|
scenario(OpenMainScreenScenario(composeTestRule))
|
||||||
step("Click on \"Scan\" button") {
|
|
||||||
scanButton {
|
|
||||||
assertIsDisplayed()
|
|
||||||
performClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DisclaimerScreen {
|
|
||||||
step("Click on \"Accept\" button") {
|
|
||||||
acceptButton {
|
|
||||||
isVisible()
|
|
||||||
click()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ComposeScreen.onComposeScreen<WalletScreen>(composeTestRule) {
|
|
||||||
step("Make sure wallet screen is visible") {
|
|
||||||
assertIsDisplayed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package com.tangem.tests
|
package com.tangem.tests
|
||||||
|
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.screens.DisclaimerScreen
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
import com.tangem.screens.StoriesScreen
|
import com.tangem.screens.DisclaimerTestScreen
|
||||||
import com.tangem.screens.WalletScreen
|
import com.tangem.screens.MainTestScreen
|
||||||
|
import com.tangem.screens.StoriesTestScreen
|
||||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
|
@ -15,31 +16,22 @@ class ScanErrorTest : BaseTestCase() {
|
||||||
@Test
|
@Test
|
||||||
fun goToMain() =
|
fun goToMain() =
|
||||||
setupHooks().run {
|
setupHooks().run {
|
||||||
ComposeScreen.onComposeScreen<StoriesScreen>(composeTestRule) {
|
ComposeScreen.onComposeScreen<DisclaimerTestScreen>(composeTestRule) {
|
||||||
|
step("Click on \"Accept\" button") {
|
||||||
|
acceptButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<StoriesTestScreen>(composeTestRule) {
|
||||||
step("Click on \"Scan\" button emulating scan error") {
|
step("Click on \"Scan\" button emulating scan error") {
|
||||||
MockProvider.setEmulateError()
|
MockProvider.setEmulateError()
|
||||||
scanButton {
|
scanButton.clickWithAssertion()
|
||||||
assertIsDisplayed()
|
|
||||||
performClick()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
step("Click on \"Scan\" button again without emulating error") {
|
step("Click on \"Scan\" button again without emulating error") {
|
||||||
MockProvider.resetEmulateError()
|
MockProvider.resetEmulateError()
|
||||||
scanButton {
|
scanButton.clickWithAssertion()
|
||||||
assertIsDisplayed()
|
|
||||||
performClick()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DisclaimerScreen {
|
ComposeScreen.onComposeScreen<MainTestScreen>(composeTestRule) {
|
||||||
step("Click on \"Accept\" button") {
|
|
||||||
acceptButton {
|
|
||||||
isVisible()
|
|
||||||
click()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ComposeScreen.onComposeScreen<WalletScreen>(composeTestRule) {
|
|
||||||
step("Make sure wallet screen is visible") {
|
step("Make sure wallet screen is visible") {
|
||||||
assertIsDisplayed()
|
assertIsDisplayed()
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,9 @@ package com.tangem.tests
|
||||||
|
|
||||||
import android.content.Intent.ACTION_VIEW
|
import android.content.Intent.ACTION_VIEW
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.screens.StoriesScreen
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.screens.DisclaimerTestScreen
|
||||||
|
import com.tangem.screens.StoriesTestScreen
|
||||||
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
|
@ -13,22 +15,16 @@ import org.junit.Test
|
||||||
class StoriesTest : BaseTestCase() {
|
class StoriesTest : BaseTestCase() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun clickOnButtons() =
|
fun clickOnOrderButton() =
|
||||||
setupHooks().run {
|
setupHooks().run {
|
||||||
ComposeScreen.onComposeScreen<StoriesScreen>(composeTestRule) {
|
ComposeScreen.onComposeScreen<DisclaimerTestScreen>(composeTestRule) {
|
||||||
step("Click on \"Scan\" button") {
|
step("Click on \"Accept\" button") {
|
||||||
scanButton {
|
acceptButton.clickWithAssertion()
|
||||||
assertIsDisplayed()
|
|
||||||
performClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
step("Assert: \"Scan card\" popup opened") {
|
|
||||||
enableNFCAlert.isDisplayed()
|
|
||||||
cancelButton.click()
|
|
||||||
device.uiDevice.pressBack()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<StoriesTestScreen>(composeTestRule) {
|
||||||
step("Click on \"Order\" button") {
|
step("Click on \"Order\" button") {
|
||||||
orderButton.performClick()
|
orderButton.clickWithAssertion()
|
||||||
}
|
}
|
||||||
step("Assert: browser opened") {
|
step("Assert: browser opened") {
|
||||||
val expectedIntent = KIntent {
|
val expectedIntent = KIntent {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/SplashTheme"
|
android:theme="@style/SplashTheme"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustNothing">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 095b8f4ea0fa02e7ccea93cf0f437346345297ef
|
Subproject commit 57484a0139299d1229872c626b2c9cb34442038c
|
||||||
|
|
@ -708,6 +708,16 @@
|
||||||
"networkId": "cyber/test"
|
"networkId": "cyber/test"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sei-network",
|
||||||
|
"name": "Sei Network",
|
||||||
|
"symbol": "SEI",
|
||||||
|
"networks": [
|
||||||
|
{
|
||||||
|
"networkId": "sei-network/test"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import com.tangem.core.navigation.url.UrlOpener
|
||||||
import com.tangem.datasource.asset.loader.AssetLoader
|
import com.tangem.datasource.asset.loader.AssetLoader
|
||||||
import com.tangem.datasource.config.ConfigManager
|
import com.tangem.datasource.config.ConfigManager
|
||||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
import com.tangem.datasource.local.token.UserTokensStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||||
|
|
@ -76,7 +76,7 @@ interface ApplicationEntryPoint {
|
||||||
|
|
||||||
fun getBalanceHidingRepository(): BalanceHidingRepository
|
fun getBalanceHidingRepository(): BalanceHidingRepository
|
||||||
|
|
||||||
fun getUserTokensStore(): UserTokensStore
|
fun getAppPreferencesStore(): AppPreferencesStore
|
||||||
|
|
||||||
fun getGetAppThemeModeUseCase(): GetAppThemeModeUseCase
|
fun getGetAppThemeModeUseCase(): GetAppThemeModeUseCase
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -583,8 +583,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
||||||
private fun sendStakingUnsubmittedHashes() {
|
private fun sendStakingUnsubmittedHashes() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
sendUnsubmittedHashesUseCase.invoke()
|
sendUnsubmittedHashesUseCase.invoke()
|
||||||
.onRight { Timber.d("Submitting hashes succeeded") }
|
|
||||||
.onLeft { Timber.e(it.toString()) }
|
.onLeft { Timber.e(it.toString()) }
|
||||||
|
.onRight { Timber.d("Submitting hashes succeeded") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ import com.tangem.datasource.config.ConfigManager
|
||||||
import com.tangem.datasource.config.FeaturesLocalLoader
|
import com.tangem.datasource.config.FeaturesLocalLoader
|
||||||
import com.tangem.datasource.config.models.Config
|
import com.tangem.datasource.config.models.Config
|
||||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
import com.tangem.datasource.local.token.UserTokensStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||||
|
|
@ -126,8 +126,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
private val balanceHidingRepository: BalanceHidingRepository
|
private val balanceHidingRepository: BalanceHidingRepository
|
||||||
get() = entryPoint.getBalanceHidingRepository()
|
get() = entryPoint.getBalanceHidingRepository()
|
||||||
|
|
||||||
private val userTokensStore: UserTokensStore
|
private val appPreferencesStore: AppPreferencesStore
|
||||||
get() = entryPoint.getUserTokensStore()
|
get() = entryPoint.getAppPreferencesStore()
|
||||||
|
|
||||||
val getAppThemeModeUseCase: GetAppThemeModeUseCase
|
val getAppThemeModeUseCase: GetAppThemeModeUseCase
|
||||||
get() = entryPoint.getGetAppThemeModeUseCase()
|
get() = entryPoint.getGetAppThemeModeUseCase()
|
||||||
|
|
@ -228,7 +228,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
derivationsFinder = DerivationsFinder(
|
derivationsFinder = DerivationsFinder(
|
||||||
newTokensStore = userTokensStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
dispatchers = AppCoroutineDispatcherProvider(),
|
dispatchers = AppCoroutineDispatcherProvider(),
|
||||||
)
|
)
|
||||||
appStateHolder.mainStore = store
|
appStateHolder.mainStore = store
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,11 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
||||||
source = state.dialog.source,
|
source = state.dialog.source,
|
||||||
onTryAgain = state.dialog.onTryAgain,
|
onTryAgain = state.dialog.onTryAgain,
|
||||||
)
|
)
|
||||||
|
is StateDialog.NfcFeatureIsUnavailable -> SimpleAlertDialog.create(
|
||||||
|
titleRes = R.string.common_error,
|
||||||
|
messageRes = R.string.nfc_error_unavailable,
|
||||||
|
context = context,
|
||||||
|
)
|
||||||
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
|
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
|
||||||
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
|
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
|
||||||
is AppDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog(
|
is AppDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.tangem.tap.common.analytics.handlers.firebase
|
||||||
|
|
||||||
|
import com.google.firebase.analytics.ktx.analytics
|
||||||
|
import com.google.firebase.ktx.Firebase
|
||||||
|
import com.tangem.core.analytics.AppInstanceIdProvider
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import timber.log.Timber
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
|
internal class FirebaseAppInstanceIdProvider : AppInstanceIdProvider {
|
||||||
|
|
||||||
|
override suspend fun getAppInstanceId(): String? = suspendCancellableCoroutine { continuation ->
|
||||||
|
Firebase.analytics.appInstanceId
|
||||||
|
.addOnSuccessListener { continuation.resume(it) }
|
||||||
|
.addOnFailureListener {
|
||||||
|
Timber.w("Fail to get appInstanceId")
|
||||||
|
continuation.resume(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAppInstanceIdSync(): String? {
|
||||||
|
return try {
|
||||||
|
Firebase.analytics.appInstanceId.result
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
Timber.e(e, "getAppInstanceIdSync")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ import com.tangem.data.card.sdk.CardSdkOwner
|
||||||
import com.tangem.data.card.sdk.CardSdkProvider
|
import com.tangem.data.card.sdk.CardSdkProvider
|
||||||
import com.tangem.sdk.DefaultSessionViewDelegate
|
import com.tangem.sdk.DefaultSessionViewDelegate
|
||||||
import com.tangem.sdk.extensions.*
|
import com.tangem.sdk.extensions.*
|
||||||
|
import com.tangem.sdk.nfc.AndroidNfcAvailabilityProvider
|
||||||
import com.tangem.sdk.nfc.NfcManager
|
import com.tangem.sdk.nfc.NfcManager
|
||||||
import com.tangem.sdk.storage.create
|
import com.tangem.sdk.storage.create
|
||||||
import com.tangem.tap.foregroundActivityObserver
|
import com.tangem.tap.foregroundActivityObserver
|
||||||
|
|
@ -98,9 +99,11 @@ internal class DefaultCardSdkProvider @Inject constructor(
|
||||||
val viewDelegate = DefaultSessionViewDelegate(nfcManager, activity)
|
val viewDelegate = DefaultSessionViewDelegate(nfcManager, activity)
|
||||||
viewDelegate.sdkConfig = config
|
viewDelegate.sdkConfig = config
|
||||||
|
|
||||||
|
val androidNfcAvailabilityProvider = AndroidNfcAvailabilityProvider(activity)
|
||||||
val sdk = TangemSdk(
|
val sdk = TangemSdk(
|
||||||
reader = nfcManager.reader,
|
reader = nfcManager.reader,
|
||||||
viewDelegate = viewDelegate,
|
viewDelegate = viewDelegate,
|
||||||
|
nfcAvailabilityProvider = androidNfcAvailabilityProvider,
|
||||||
secureStorage = secureStorage,
|
secureStorage = secureStorage,
|
||||||
authenticationManager = authenticationManager,
|
authenticationManager = authenticationManager,
|
||||||
keystoreManager = keystoreManager,
|
keystoreManager = keystoreManager,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
package com.tangem.tap.di.analytics
|
package com.tangem.tap.di.analytics
|
||||||
|
|
||||||
|
import com.tangem.core.analytics.AppInstanceIdProvider
|
||||||
import com.tangem.core.analytics.utils.AnalyticsContextProxy
|
import com.tangem.core.analytics.utils.AnalyticsContextProxy
|
||||||
import com.tangem.domain.analytics.ChangeCardAnalyticsContextUseCase
|
import com.tangem.domain.analytics.ChangeCardAnalyticsContextUseCase
|
||||||
import com.tangem.tap.common.analytics.DefaultAnalyticsContextProxy
|
import com.tangem.tap.common.analytics.DefaultAnalyticsContextProxy
|
||||||
import com.tangem.tap.common.analytics.DefaultChangeCardAnalyticsContextUseCase
|
import com.tangem.tap.common.analytics.DefaultChangeCardAnalyticsContextUseCase
|
||||||
|
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAppInstanceIdProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -23,4 +25,8 @@ internal object AnalyticsModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideAnalyticsContextProxy(): AnalyticsContextProxy = DefaultAnalyticsContextProxy()
|
fun provideAnalyticsContextProxy(): AnalyticsContextProxy = DefaultAnalyticsContextProxy()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideAppInstanceIdProvider(): AppInstanceIdProvider = FirebaseAppInstanceIdProvider()
|
||||||
}
|
}
|
||||||
|
|
@ -92,4 +92,12 @@ internal object CardDomainModule {
|
||||||
fun provideNetworkHasDerivationUseCase(): NetworkHasDerivationUseCase {
|
fun provideNetworkHasDerivationUseCase(): NetworkHasDerivationUseCase {
|
||||||
return NetworkHasDerivationUseCase()
|
return NetworkHasDerivationUseCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideIsRequiredDerivePublicKeysUseCase(
|
||||||
|
derivationsRepository: DerivationsRepository,
|
||||||
|
): HasMissedDerivationsUseCase {
|
||||||
|
return HasMissedDerivationsUseCase(derivationsRepository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
import com.tangem.domain.managetokens.GetManagedTokensUseCase
|
import com.tangem.domain.card.repository.DerivationsRepository
|
||||||
|
import com.tangem.domain.managetokens.*
|
||||||
|
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||||
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||||
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -17,4 +22,84 @@ internal object ManageTokensDomainModule {
|
||||||
fun provideGetManageTokensUseCase(manageTokensRepository: ManageTokensRepository): GetManagedTokensUseCase {
|
fun provideGetManageTokensUseCase(manageTokensRepository: ManageTokensRepository): GetManagedTokensUseCase {
|
||||||
return GetManagedTokensUseCase(manageTokensRepository)
|
return GetManagedTokensUseCase(manageTokensRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideValidateTokenFormatUseCase(customTokensRepository: CustomTokensRepository): ValidateTokenFormUseCase {
|
||||||
|
return ValidateTokenFormUseCase(customTokensRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideCreateCurrencyUseCase(customTokensRepository: CustomTokensRepository): CreateCurrencyUseCase {
|
||||||
|
return CreateCurrencyUseCase(customTokensRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideFindTokenUseCase(customTokensRepository: CustomTokensRepository): FindTokenUseCase {
|
||||||
|
return FindTokenUseCase(customTokensRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideCheckIsCurrencyNotAddedUseCase(
|
||||||
|
customTokensRepository: CustomTokensRepository,
|
||||||
|
): CheckIsCurrencyNotAddedUseCase {
|
||||||
|
return CheckIsCurrencyNotAddedUseCase(customTokensRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideRemoveCustomManagedCryptoCurrencyUseCase(
|
||||||
|
customTokensRepository: CustomTokensRepository,
|
||||||
|
): RemoveCustomManagedCryptoCurrencyUseCase {
|
||||||
|
return RemoveCustomManagedCryptoCurrencyUseCase(customTokensRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSaveManagedTokensUseCase(
|
||||||
|
customTokensRepository: CustomTokensRepository,
|
||||||
|
walletManagersFacade: WalletManagersFacade,
|
||||||
|
currenciesRepository: CurrenciesRepository,
|
||||||
|
networksRepository: NetworksRepository,
|
||||||
|
derivationsRepository: DerivationsRepository,
|
||||||
|
): SaveManagedTokensUseCase {
|
||||||
|
return SaveManagedTokensUseCase(
|
||||||
|
customTokensRepository = customTokensRepository,
|
||||||
|
walletManagersFacade = walletManagersFacade,
|
||||||
|
currenciesRepository = currenciesRepository,
|
||||||
|
networksRepository = networksRepository,
|
||||||
|
derivationsRepository = derivationsRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetSupportedNetworksUseCase(
|
||||||
|
customTokensRepository: CustomTokensRepository,
|
||||||
|
): GetSupportedNetworksUseCase {
|
||||||
|
return GetSupportedNetworksUseCase(customTokensRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideValidateDerivationPathUseCase(
|
||||||
|
customTokensRepository: CustomTokensRepository,
|
||||||
|
): ValidateDerivationPathUseCase {
|
||||||
|
return ValidateDerivationPathUseCase(customTokensRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideCheckHasLinkedTokensUseCase(repository: ManageTokensRepository): CheckHasLinkedTokensUseCase {
|
||||||
|
return CheckHasLinkedTokensUseCase(repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideCheckCurrencyUnsupportedUseCase(repository: ManageTokensRepository): CheckCurrencyUnsupportedUseCase {
|
||||||
|
return CheckCurrencyUnsupportedUseCase(repository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase
|
import com.tangem.domain.card.repository.DerivationsRepository
|
||||||
import com.tangem.domain.markets.GetTokenMarketInfoUseCase
|
import com.tangem.domain.markets.*
|
||||||
import com.tangem.domain.markets.GetTokenPriceChartUseCase
|
|
||||||
import com.tangem.domain.markets.GetTokenQuotesUseCase
|
|
||||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||||
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||||
|
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -37,7 +38,29 @@ object MarketsDomainModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGetTokenQuotesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenQuotesUseCase {
|
fun provideTokenFullQuotesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenFullQuotesUseCase {
|
||||||
return GetTokenQuotesUseCase(marketsTokenRepository = marketsTokenRepository)
|
return GetTokenFullQuotesUseCase(marketsTokenRepository = marketsTokenRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetTokenQuotesUseCase(quotesRepository: QuotesRepository): GetCurrencyQuotesUseCase {
|
||||||
|
return GetCurrencyQuotesUseCase(quotesRepository = quotesRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSaveMarketTokensUseCase(
|
||||||
|
derivationsRepository: DerivationsRepository,
|
||||||
|
marketsTokenRepository: MarketsTokenRepository,
|
||||||
|
currenciesRepository: CurrenciesRepository,
|
||||||
|
networksRepository: NetworksRepository,
|
||||||
|
): SaveMarketTokensUseCase {
|
||||||
|
return SaveMarketTokensUseCase(
|
||||||
|
derivationsRepository = derivationsRepository,
|
||||||
|
marketsTokenRepository = marketsTokenRepository,
|
||||||
|
currenciesRepository = currenciesRepository,
|
||||||
|
networksRepository = networksRepository,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,6 +57,14 @@ internal object SettingsDomainModule {
|
||||||
return ShouldShowSaveWalletScreenUseCase(settingsRepository = settingsRepository)
|
return ShouldShowSaveWalletScreenUseCase(settingsRepository = settingsRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideShouldShowMarketsTooltipUseCase(
|
||||||
|
settingsRepository: SettingsRepository,
|
||||||
|
): ShouldShowMarketsTooltipUseCase {
|
||||||
|
return ShouldShowMarketsTooltipUseCase(settingsRepository = settingsRepository)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesCanUseBiometryUseCase(tangemSdkManager: TangemSdkManager): CanUseBiometryUseCase {
|
fun providesCanUseBiometryUseCase(tangemSdkManager: TangemSdkManager): CanUseBiometryUseCase {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.di.domain
|
||||||
import com.tangem.domain.staking.*
|
import com.tangem.domain.staking.*
|
||||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||||
import com.tangem.domain.staking.repositories.StakingRepository
|
import com.tangem.domain.staking.repositories.StakingRepository
|
||||||
|
import com.tangem.domain.staking.repositories.StakingTransactionHashRepository
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -73,18 +74,6 @@ internal object StakingDomainModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideGetStakingYieldBalanceUseCase(
|
|
||||||
stakingRepository: StakingRepository,
|
|
||||||
stakingErrorResolver: StakingErrorResolver,
|
|
||||||
): GetStakingYieldBalanceUseCase {
|
|
||||||
return GetStakingYieldBalanceUseCase(
|
|
||||||
stakingRepository = stakingRepository,
|
|
||||||
stakingErrorResolver = stakingErrorResolver,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideInitializeStakingProcessUseCase(
|
fun provideInitializeStakingProcessUseCase(
|
||||||
|
|
@ -112,11 +101,11 @@ internal object StakingDomainModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSubmitHashUseCase(
|
fun provideSubmitHashUseCase(
|
||||||
stakingRepository: StakingRepository,
|
stakingTransactionHashRepository: StakingTransactionHashRepository,
|
||||||
stakingErrorResolver: StakingErrorResolver,
|
stakingErrorResolver: StakingErrorResolver,
|
||||||
): SubmitHashUseCase {
|
): SubmitHashUseCase {
|
||||||
return SubmitHashUseCase(
|
return SubmitHashUseCase(
|
||||||
stakingRepository = stakingRepository,
|
stakingTransactionHashRepository = stakingTransactionHashRepository,
|
||||||
stakingErrorResolver = stakingErrorResolver,
|
stakingErrorResolver = stakingErrorResolver,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -124,11 +113,11 @@ internal object StakingDomainModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSaveUnsubmittedHashUseCase(
|
fun provideSaveUnsubmittedHashUseCase(
|
||||||
stakingRepository: StakingRepository,
|
stakingTransactionHashRepository: StakingTransactionHashRepository,
|
||||||
stakingErrorResolver: StakingErrorResolver,
|
stakingErrorResolver: StakingErrorResolver,
|
||||||
): SaveUnsubmittedHashUseCase {
|
): SaveUnsubmittedHashUseCase {
|
||||||
return SaveUnsubmittedHashUseCase(
|
return SaveUnsubmittedHashUseCase(
|
||||||
stakingRepository = stakingRepository,
|
stakingTransactionHashRepository = stakingTransactionHashRepository,
|
||||||
stakingErrorResolver = stakingErrorResolver,
|
stakingErrorResolver = stakingErrorResolver,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -136,23 +125,11 @@ internal object StakingDomainModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSendUnsubmittedHashesUseCase(
|
fun provideSendUnsubmittedHashesUseCase(
|
||||||
stakingRepository: StakingRepository,
|
stakingTransactionHashRepository: StakingTransactionHashRepository,
|
||||||
stakingErrorResolver: StakingErrorResolver,
|
stakingErrorResolver: StakingErrorResolver,
|
||||||
): SendUnsubmittedHashesUseCase {
|
): SendUnsubmittedHashesUseCase {
|
||||||
return SendUnsubmittedHashesUseCase(
|
return SendUnsubmittedHashesUseCase(
|
||||||
stakingRepository = stakingRepository,
|
stakingTransactionHashRepository = stakingTransactionHashRepository,
|
||||||
stakingErrorResolver = stakingErrorResolver,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideIsStakeMoreAvailableUseCase(
|
|
||||||
stakingRepository: StakingRepository,
|
|
||||||
stakingErrorResolver: StakingErrorResolver,
|
|
||||||
): IsStakeMoreAvailableUseCase {
|
|
||||||
return IsStakeMoreAvailableUseCase(
|
|
||||||
stakingRepository = stakingRepository,
|
|
||||||
stakingErrorResolver = stakingErrorResolver,
|
stakingErrorResolver = stakingErrorResolver,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -180,4 +157,16 @@ internal object StakingDomainModule {
|
||||||
stakingErrorResolver = stakingErrorResolver,
|
stakingErrorResolver = stakingErrorResolver,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideIsAnyTokenStakedUseCase(
|
||||||
|
stakingRepository: StakingRepository,
|
||||||
|
stakingErrorResolver: StakingErrorResolver,
|
||||||
|
): IsAnyTokenStakedUseCase {
|
||||||
|
return IsAnyTokenStakedUseCase(
|
||||||
|
stakingRepository = stakingRepository,
|
||||||
|
stakingErrorResolver = stakingErrorResolver,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.tokens.*
|
||||||
import com.tangem.domain.tokens.repository.*
|
import com.tangem.domain.tokens.repository.*
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||||
|
import com.tangem.features.markets.MarketsFeatureToggles
|
||||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -38,8 +39,9 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
networksRepository: NetworksRepository,
|
networksRepository: NetworksRepository,
|
||||||
|
stakingRepository: StakingRepository,
|
||||||
): FetchTokenListUseCase {
|
): FetchTokenListUseCase {
|
||||||
return FetchTokenListUseCase(currenciesRepository, networksRepository, quotesRepository)
|
return FetchTokenListUseCase(currenciesRepository, networksRepository, quotesRepository, stakingRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
@ -73,8 +75,8 @@ internal object TokensDomainModule {
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
networksRepository: NetworksRepository,
|
networksRepository: NetworksRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
): GetCardTokensListUseCase {
|
): GetNodlTokenListUseCase {
|
||||||
return GetCardTokensListUseCase(currenciesRepository, quotesRepository, networksRepository, stakingRepository)
|
return GetNodlTokenListUseCase(currenciesRepository, quotesRepository, networksRepository, stakingRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
@ -104,6 +106,22 @@ internal object TokensDomainModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetAllWalletsCryptoCurrencyStatusesUseCase(
|
||||||
|
currenciesRepository: CurrenciesRepository,
|
||||||
|
quotesRepository: QuotesRepository,
|
||||||
|
networksRepository: NetworksRepository,
|
||||||
|
stakingRepository: StakingRepository,
|
||||||
|
): GetAllWalletsCryptoCurrencyStatusesUseCase {
|
||||||
|
return GetAllWalletsCryptoCurrencyStatusesUseCase(
|
||||||
|
currenciesRepository = currenciesRepository,
|
||||||
|
quotesRepository = quotesRepository,
|
||||||
|
networksRepository = networksRepository,
|
||||||
|
stakingRepository = stakingRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGetCurrencyWarningsUseCase(
|
fun provideGetCurrencyWarningsUseCase(
|
||||||
|
|
@ -158,8 +176,9 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
networksRepository: NetworksRepository,
|
networksRepository: NetworksRepository,
|
||||||
|
stakingRepository: StakingRepository,
|
||||||
): FetchCurrencyStatusUseCase {
|
): FetchCurrencyStatusUseCase {
|
||||||
return FetchCurrencyStatusUseCase(currenciesRepository, networksRepository, quotesRepository)
|
return FetchCurrencyStatusUseCase(currenciesRepository, networksRepository, quotesRepository, stakingRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
@ -213,6 +232,7 @@ internal object TokensDomainModule {
|
||||||
networksRepository: NetworksRepository,
|
networksRepository: NetworksRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
stakingFeatureToggles: StakingFeatureToggles,
|
stakingFeatureToggles: StakingFeatureToggles,
|
||||||
|
marketsFeatureToggles: MarketsFeatureToggles,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): GetCryptoCurrencyActionsUseCase {
|
): GetCryptoCurrencyActionsUseCase {
|
||||||
return GetCryptoCurrencyActionsUseCase(
|
return GetCryptoCurrencyActionsUseCase(
|
||||||
|
|
@ -224,6 +244,7 @@ internal object TokensDomainModule {
|
||||||
networksRepository = networksRepository,
|
networksRepository = networksRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
stakingFeatureToggles = stakingFeatureToggles,
|
stakingFeatureToggles = stakingFeatureToggles,
|
||||||
|
marketsFeatureToggles = marketsFeatureToggles,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -416,7 +437,7 @@ internal object TokensDomainModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideCheckHasLinkedTokensUseCase(currenciesRepository: CurrenciesRepository): CheckHasLinkedTokensUseCase {
|
fun provideGetCurrencyCheckUseCase(currencyChecksRepository: CurrencyChecksRepository): GetCurrencyCheckUseCase {
|
||||||
return CheckHasLinkedTokensUseCase(currenciesRepository)
|
return GetCurrencyCheckUseCase(currencyChecksRepository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,6 +42,21 @@ internal object TransactionDomainModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSendMultipleTransactionUseCase(
|
||||||
|
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||||
|
transactionRepository: TransactionRepository,
|
||||||
|
walletManagersFacade: WalletManagersFacade,
|
||||||
|
): SendMultipleTransactionUseCase {
|
||||||
|
return SendMultipleTransactionUseCase(
|
||||||
|
demoConfig = DemoConfig(),
|
||||||
|
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||||
|
transactionRepository = transactionRepository,
|
||||||
|
walletManagersFacade = walletManagersFacade,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideAssociateAssetUseCase(
|
fun provideAssociateAssetUseCase(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.tangem.tap.domain.card
|
package com.tangem.tap.domain.card
|
||||||
|
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
import com.tangem.common.card.EllipticCurve
|
import com.tangem.common.card.EllipticCurve
|
||||||
import com.tangem.common.core.TangemSdkError
|
import com.tangem.common.core.TangemSdkError
|
||||||
|
|
@ -8,10 +10,13 @@ import com.tangem.common.doOnSuccess
|
||||||
import com.tangem.common.extensions.ByteArrayKey
|
import com.tangem.common.extensions.ByteArrayKey
|
||||||
import com.tangem.common.extensions.toMapKey
|
import com.tangem.common.extensions.toMapKey
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
|
import com.tangem.data.common.currency.getNetwork
|
||||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||||
import com.tangem.domain.card.repository.DerivationsRepository
|
import com.tangem.domain.card.repository.DerivationsRepository
|
||||||
|
import com.tangem.domain.common.util.derivationStyleProvider
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
|
import com.tangem.domain.tokens.model.Network
|
||||||
import com.tangem.domain.wallets.models.UserWallet
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||||
|
|
@ -30,6 +35,25 @@ internal class DefaultDerivationsRepository(
|
||||||
) : DerivationsRepository {
|
) : DerivationsRepository {
|
||||||
|
|
||||||
override suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
override suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||||
|
derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List<Network.ID>) {
|
||||||
|
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
||||||
|
|
||||||
|
derivePublicKeysByNetworks(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
networks = networkIds.mapNotNull {
|
||||||
|
getNetwork(
|
||||||
|
blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null,
|
||||||
|
extraDerivationPath = null,
|
||||||
|
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List<Network>) {
|
||||||
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
||||||
|
|
||||||
if (!userWallet.scanResponse.card.settings.isHDWalletAllowed) {
|
if (!userWallet.scanResponse.card.settings.isHDWalletAllowed) {
|
||||||
|
|
@ -38,7 +62,7 @@ internal class DefaultDerivationsRepository(
|
||||||
}
|
}
|
||||||
|
|
||||||
val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse)
|
val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse)
|
||||||
.find(currencies)
|
.findByNetworks(networks)
|
||||||
.ifEmpty {
|
.ifEmpty {
|
||||||
Timber.d("Nothing to derive")
|
Timber.d("Nothing to derive")
|
||||||
return
|
return
|
||||||
|
|
@ -47,6 +71,26 @@ internal class DefaultDerivationsRepository(
|
||||||
derivePublicKeys(userWalletId = userWalletId, derivations = derivations)
|
derivePublicKeys(userWalletId = userWalletId, derivations = derivations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun hasMissedDerivations(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
networksWithDerivationPath: Map<Network.ID, String?>,
|
||||||
|
): Boolean {
|
||||||
|
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
||||||
|
|
||||||
|
val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse)
|
||||||
|
.findByNetworks(
|
||||||
|
networksWithDerivationPath.mapNotNull { (networkId, extraDerivationPath) ->
|
||||||
|
getNetwork(
|
||||||
|
blockchain = Blockchain.fromNetworkId(networkId.value) ?: return@mapNotNull null,
|
||||||
|
extraDerivationPath = extraDerivationPath,
|
||||||
|
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return derivations.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun derivePublicKeys(userWalletId: UserWalletId, derivations: Derivations): DerivedKeys {
|
override suspend fun derivePublicKeys(userWalletId: UserWalletId, derivations: Derivations): DerivedKeys {
|
||||||
tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)
|
tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)
|
||||||
.doOnSuccess { response ->
|
.doOnSuccess { response ->
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.common.util.derivationStyleProvider
|
||||||
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
|
import com.tangem.domain.tokens.model.Network
|
||||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||||
|
|
||||||
private typealias DerivationData = Pair<ByteArrayKey, List<DerivationPath>>
|
private typealias DerivationData = Pair<ByteArrayKey, List<DerivationPath>>
|
||||||
|
|
@ -26,8 +27,12 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
||||||
|
|
||||||
/** Find missed derivations for given currencies [currencies] */
|
/** Find missed derivations for given currencies [currencies] */
|
||||||
fun find(currencies: List<CryptoCurrency>): Derivations {
|
fun find(currencies: List<CryptoCurrency>): Derivations {
|
||||||
|
return currencies.map { it.network }.let(::findByNetworks)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findByNetworks(networks: List<Network>): Derivations {
|
||||||
return buildMap<ByteArrayKey, MutableList<DerivationPath>> {
|
return buildMap<ByteArrayKey, MutableList<DerivationPath>> {
|
||||||
currencies
|
networks
|
||||||
.mapToNewDerivations()
|
.mapToNewDerivations()
|
||||||
.forEach { data ->
|
.forEach { data ->
|
||||||
val current = this[data.first]
|
val current = this[data.first]
|
||||||
|
|
@ -41,25 +46,25 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<CryptoCurrency>.mapToNewDerivations(): List<DerivationData> {
|
private fun List<Network>.mapToNewDerivations(): List<DerivationData> {
|
||||||
val config = CardConfig.createConfig(scanResponse.card)
|
val config = CardConfig.createConfig(scanResponse.card)
|
||||||
return mapNotNull { currency ->
|
return mapNotNull { network ->
|
||||||
val blockchain = Blockchain.fromId(id = currency.network.id.value)
|
val blockchain = Blockchain.fromId(id = network.id.value)
|
||||||
val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null
|
val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null
|
||||||
|
|
||||||
findNewDerivations(curve = curve, scanResponse = scanResponse, currency = currency)
|
findNewDerivations(curve = curve, scanResponse = scanResponse, network = network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findNewDerivations(
|
private fun findNewDerivations(
|
||||||
curve: EllipticCurve,
|
curve: EllipticCurve,
|
||||||
scanResponse: ScanResponse,
|
scanResponse: ScanResponse,
|
||||||
currency: CryptoCurrency,
|
network: Network,
|
||||||
): DerivationData? {
|
): DerivationData? {
|
||||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
||||||
val publicKey = wallet.publicKey.toMapKey()
|
val publicKey = wallet.publicKey.toMapKey()
|
||||||
|
|
||||||
val derivationCandidates = currency
|
val derivationCandidates = network
|
||||||
.getDerivationCandidates(curve)
|
.getDerivationCandidates(curve)
|
||||||
.ifEmpty { return null }
|
.ifEmpty { return null }
|
||||||
.filterAlreadyDerivedKeys(publicKey)
|
.filterAlreadyDerivedKeys(publicKey)
|
||||||
|
|
@ -68,13 +73,13 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
||||||
return publicKey to derivationCandidates
|
return publicKey to derivationCandidates
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CryptoCurrency.getDerivationCandidates(curve: EllipticCurve): List<DerivationPath> {
|
private fun Network.getDerivationCandidates(curve: EllipticCurve): List<DerivationPath> {
|
||||||
val blockchain = Blockchain.fromId(id = network.id.value)
|
val blockchain = Blockchain.fromId(id = this.id.value)
|
||||||
|
|
||||||
return buildList {
|
return buildList {
|
||||||
add(blockchain.getDerivationPath(curve = curve))
|
add(blockchain.getDerivationPath(curve = curve))
|
||||||
add(blockchain.getCustomDerivationPath(curve = curve, currency = this@getDerivationCandidates))
|
add(blockchain.getCustomDerivationPath(curve = curve, network = this@getDerivationCandidates))
|
||||||
add(blockchain.getCardanoDerivationPathIfNeeded(currency = this@getDerivationCandidates))
|
add(blockchain.getCardanoDerivationPathIfNeeded(network = this@getDerivationCandidates))
|
||||||
}
|
}
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
.distinct()
|
.distinct()
|
||||||
|
|
@ -88,17 +93,17 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Blockchain.getCustomDerivationPath(curve: EllipticCurve, currency: CryptoCurrency): DerivationPath? {
|
private fun Blockchain.getCustomDerivationPath(curve: EllipticCurve, network: Network): DerivationPath? {
|
||||||
return if (getSupportedCurves().contains(curve)) {
|
return if (getSupportedCurves().contains(curve)) {
|
||||||
currency.network.derivationPath.value?.let(::DerivationPath)
|
network.derivationPath.value?.let(::DerivationPath)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Blockchain.getCardanoDerivationPathIfNeeded(currency: CryptoCurrency): DerivationPath? {
|
private fun Blockchain.getCardanoDerivationPathIfNeeded(network: Network): DerivationPath? {
|
||||||
return if (currency is CryptoCurrency.Coin && this == Blockchain.Cardano) {
|
return if (this == Blockchain.Cardano) {
|
||||||
currency.network.derivationPath.value?.let {
|
network.derivationPath.value?.let {
|
||||||
CardanoUtils.extendedDerivationPath(derivationPath = DerivationPath(it))
|
CardanoUtils.extendedDerivationPath(derivationPath = DerivationPath(it))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
package com.tangem.tap.domain.configurable.warningMessage
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import com.squareup.moshi.Json
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
|
|
||||||
/**
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
data class WarningMessage(
|
|
||||||
val title: String,
|
|
||||||
val message: String,
|
|
||||||
val type: Type,
|
|
||||||
val priority: Priority,
|
|
||||||
val location: List<Location>,
|
|
||||||
private val blockchains: List<String>?,
|
|
||||||
@StringRes val titleResId: Int? = null,
|
|
||||||
@StringRes val messageResId: Int? = null,
|
|
||||||
val origin: Origin = Origin.Remote,
|
|
||||||
@StringRes val buttonTextId: Int? = null,
|
|
||||||
val titleFormatArg: String? = null,
|
|
||||||
val messageFormatArg: String? = null,
|
|
||||||
) {
|
|
||||||
val blockchainList: List<Blockchain>? by lazy {
|
|
||||||
blockchains?.map { Blockchain.fromId(it.uppercase()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
var isHidden = false
|
|
||||||
|
|
||||||
enum class Priority {
|
|
||||||
@Json(name = "critical")
|
|
||||||
Critical,
|
|
||||||
|
|
||||||
@Json(name = "warning")
|
|
||||||
Warning,
|
|
||||||
|
|
||||||
@Json(name = "info")
|
|
||||||
Info,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class Type {
|
|
||||||
@Json(name = "permanent")
|
|
||||||
Permanent, // нельзя скрыть
|
|
||||||
|
|
||||||
@Json(name = "temporary")
|
|
||||||
Temporary, // можно скрыть (кнопка ОК)
|
|
||||||
|
|
||||||
AppRating,
|
|
||||||
|
|
||||||
TestCard,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class Location {
|
|
||||||
|
|
||||||
@Json(name = "send")
|
|
||||||
SendScreen,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class Origin {
|
|
||||||
Remote,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult
|
||||||
import com.tangem.common.core.TangemError
|
import com.tangem.common.core.TangemError
|
||||||
import com.tangem.common.core.TangemSdkError
|
import com.tangem.common.core.TangemSdkError
|
||||||
import com.tangem.domain.models.scan.ProductType
|
import com.tangem.domain.models.scan.ProductType
|
||||||
|
import com.tangem.tap.domain.sdk.mocks.content.NoteMockContent
|
||||||
import com.tangem.tap.domain.sdk.mocks.content.WalletMockContent
|
import com.tangem.tap.domain.sdk.mocks.content.WalletMockContent
|
||||||
import com.tangem.tap.domain.sdk.mocks.content.Wallet2MockContent
|
import com.tangem.tap.domain.sdk.mocks.content.Wallet2MockContent
|
||||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
||||||
|
|
@ -67,6 +68,7 @@ object MockProvider {
|
||||||
return when (productType) {
|
return when (productType) {
|
||||||
ProductType.Wallet -> WalletMockContent
|
ProductType.Wallet -> WalletMockContent
|
||||||
ProductType.Wallet2 -> Wallet2MockContent
|
ProductType.Wallet2 -> Wallet2MockContent
|
||||||
|
ProductType.Note -> NoteMockContent
|
||||||
else -> TODO()
|
else -> TODO()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
package com.tangem.tap.domain.sdk.mocks.content
|
||||||
|
|
||||||
|
import com.tangem.common.SuccessResponse
|
||||||
|
import com.tangem.common.card.*
|
||||||
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
|
import com.tangem.domain.models.scan.ProductType
|
||||||
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
import com.tangem.operations.attestation.Attestation
|
||||||
|
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||||
|
import com.tangem.operations.wallet.CreateWalletResponse
|
||||||
|
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||||
|
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
object NoteMockContent : MockContent {
|
||||||
|
|
||||||
|
override val cardDto = CardDTO(
|
||||||
|
cardId = "AB04000000010905",
|
||||||
|
batchId = "AB04",
|
||||||
|
cardPublicKey = byteArrayOf(2, 102, 3, -106, -14, -87, -118, 120, 10, 93, 17, 55, 26, -44, 5, 115, 88, 35, 49, -88, -69, 116, 0, -72, -27, 57, 50, -55, 80, -16, 39, -70, 119),
|
||||||
|
firmwareVersion = CardDTO.FirmwareVersion(
|
||||||
|
major = 4,
|
||||||
|
minor = 39,
|
||||||
|
patch = 0,
|
||||||
|
type = FirmwareVersion.FirmwareType.Release,
|
||||||
|
),
|
||||||
|
manufacturer = CardDTO.Manufacturer(
|
||||||
|
name = "TANGEM",
|
||||||
|
manufactureDate = Date(1649635200000),
|
||||||
|
signature = byteArrayOf(-2, -45, 100, -79, 7, -120, 49, 74, 126, -3, -75, 54, -36, -1, 19, -83, 47, -82, 52, -43, -119, 75, 58, 97, 50, 37, 103, -6, -2, 28, 120, 103, -38, -95, 7, -126, -3, -23, -22, -30, -24, -126, 3, 70, 7, -20, -6, -49, 55, -93, 26, 57, -71, 12, -20, 41, -105, -75, 82, 116, 12, -75, -22, 55),
|
||||||
|
),
|
||||||
|
issuer = CardDTO.Issuer(
|
||||||
|
name = "TANGEM AG",
|
||||||
|
publicKey = byteArrayOf(3, 86, -25, -61, 55, 99, 41, -33, -82, 115, -120, -33, 22, -107, 103, 3, -122, 16, 60, -110, 72, 106, -121, 100, 79, -87, -27, 18, -55, -49, 78, -110, -2),
|
||||||
|
),
|
||||||
|
settings = CardDTO.Settings(
|
||||||
|
securityDelay = 15000,
|
||||||
|
maxWalletsCount = 1,
|
||||||
|
isSettingAccessCodeAllowed = false,
|
||||||
|
isSettingPasscodeAllowed = false,
|
||||||
|
isResettingUserCodesAllowed = true,
|
||||||
|
isLinkedTerminalEnabled = true,
|
||||||
|
isBackupAllowed = false,
|
||||||
|
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
|
||||||
|
isFilesAllowed = false,
|
||||||
|
isHDWalletAllowed = false,
|
||||||
|
isKeysImportAllowed = false,
|
||||||
|
),
|
||||||
|
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = false),
|
||||||
|
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
|
||||||
|
isAccessCodeSet = false,
|
||||||
|
isPasscodeSet = false,
|
||||||
|
supportedCurves = listOf(
|
||||||
|
EllipticCurve.Secp256k1,
|
||||||
|
EllipticCurve.Ed25519,
|
||||||
|
EllipticCurve.Secp256r1,
|
||||||
|
),
|
||||||
|
wallets = listOf(
|
||||||
|
CardDTO.Wallet(
|
||||||
|
publicKey = byteArrayOf(2, -27, -117, 23, 68, -3, 21, -109, 18, -67, -107, -42, -44, -16, -127, -53, 46, -109, -46, -51, 89, 119, 79, 111, 78, 62, -125, 72, 109, 8, 45, 59, 117),
|
||||||
|
chainCode = byteArrayOf(),
|
||||||
|
curve = EllipticCurve.Secp256k1,
|
||||||
|
settings = CardWallet.Settings(isPermanent = false),
|
||||||
|
totalSignedHashes = 3,
|
||||||
|
remainingSignatures = null,
|
||||||
|
index = 0,
|
||||||
|
hasBackup = false,
|
||||||
|
derivedKeys = emptyMap(),
|
||||||
|
extendedPublicKey = null,
|
||||||
|
isImported = false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
attestation = Attestation(
|
||||||
|
cardKeyAttestation = Attestation.Status.Verified,
|
||||||
|
walletKeysAttestation = Attestation.Status.Skipped,
|
||||||
|
firmwareAttestation = Attestation.Status.Skipped,
|
||||||
|
cardUniquenessAttestation = Attestation.Status.Skipped,
|
||||||
|
),
|
||||||
|
backupStatus = CardDTO.BackupStatus.NoBackup,
|
||||||
|
)
|
||||||
|
|
||||||
|
override val scanResponse = ScanResponse(
|
||||||
|
card = cardDto,
|
||||||
|
productType = ProductType.Note,
|
||||||
|
walletData = WalletData(blockchain = "DOGE", token = null),
|
||||||
|
secondTwinPublicKey = null,
|
||||||
|
derivedKeys = emptyMap(),
|
||||||
|
primaryCard = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
override val derivationTaskResponse = DerivationTaskResponse(
|
||||||
|
entries = emptyMap(),
|
||||||
|
)
|
||||||
|
|
||||||
|
override val extendedPublicKey
|
||||||
|
get() = error("Available only for wallet+?")
|
||||||
|
|
||||||
|
override val successResponse = SuccessResponse(cardId = "AB04000000010905")
|
||||||
|
|
||||||
|
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
|
||||||
|
card = cardDto,
|
||||||
|
derivedKeys = emptyMap(),
|
||||||
|
primaryCard = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
override val importWalletResponse: CreateProductWalletTaskResponse
|
||||||
|
get() = error("Available only for Wallet 2")
|
||||||
|
|
||||||
|
override val createFirstTwinResponse: CreateWalletResponse
|
||||||
|
get() = error("Available only for Twin")
|
||||||
|
|
||||||
|
override val createSecondTwinResponse: CreateWalletResponse
|
||||||
|
get() = error("Available only for Twin")
|
||||||
|
|
||||||
|
override val finalizeTwinResponse: ScanResponse
|
||||||
|
get() = error("Available only for Twin")
|
||||||
|
}
|
||||||
|
|
@ -1,33 +1,292 @@
|
||||||
package com.tangem.tap.domain.sdk.mocks.content
|
package com.tangem.tap.domain.sdk.mocks.content
|
||||||
|
|
||||||
import com.tangem.common.SuccessResponse
|
import com.tangem.common.SuccessResponse
|
||||||
|
import com.tangem.common.card.*
|
||||||
|
import com.tangem.common.extensions.ByteArrayKey
|
||||||
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
|
import com.tangem.domain.models.scan.ProductType
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
import com.tangem.operations.attestation.Attestation
|
||||||
|
import com.tangem.operations.backup.PrimaryCard
|
||||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||||
|
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||||
import com.tangem.operations.wallet.CreateWalletResponse
|
import com.tangem.operations.wallet.CreateWalletResponse
|
||||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
object Wallet2MockContent : MockContent {
|
object Wallet2MockContent : MockContent {
|
||||||
|
|
||||||
override val scanResponse: ScanResponse
|
private val primaryCard = PrimaryCard(
|
||||||
get() = TODO("Not yet implemented")
|
cardId = "AF10000000981426",
|
||||||
|
batchId = "AF10",
|
||||||
|
cardPublicKey = byteArrayOf(3, 7, 80, -118, 6, 77, -15, -22, 107, 105, -64, 103, 77, -79, -102, 106, 46, 84, 21, -34, 47, -74, -56, 124, 17, -49, -29, 76, 84, 59, 50, -15, -52),
|
||||||
|
linkingKey = byteArrayOf( //
|
||||||
|
2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121,
|
||||||
|
-57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91,
|
||||||
|
),
|
||||||
|
existingWalletsCount = 5, isHDWalletAllowed = true,
|
||||||
|
issuer = Card.Issuer(
|
||||||
|
name = "TANGEM 2.0",
|
||||||
|
publicKey = byteArrayOf(2, -120, 89, -52, -60, 36, -73, -17, 103, 107, -110, -36, 3, 110, -122, 72, 43, -38, 8, 30, -50, 25, -23, -17, 38, 94, 5, -112, -20, 9, 54, -24, -32),
|
||||||
|
),
|
||||||
|
walletCurves = listOf(
|
||||||
|
EllipticCurve.Secp256k1,
|
||||||
|
EllipticCurve.Ed25519,
|
||||||
|
EllipticCurve.Bls12381G2Aug,
|
||||||
|
EllipticCurve.Secp256r1,
|
||||||
|
EllipticCurve.Ed25519Slip0010,
|
||||||
|
EllipticCurve.Bls12381G2,
|
||||||
|
EllipticCurve.Bls12381G2Pop,
|
||||||
|
EllipticCurve.Bip0340,
|
||||||
|
),
|
||||||
|
firmwareVersion = FirmwareVersion(
|
||||||
|
major = 6,
|
||||||
|
minor = 33,
|
||||||
|
patch = 0,
|
||||||
|
type = FirmwareVersion.FirmwareType.Release,
|
||||||
|
),
|
||||||
|
isKeysImportAllowed = false,
|
||||||
|
certificate = null,
|
||||||
|
)
|
||||||
|
|
||||||
override val cardDto: CardDTO
|
override val cardDto = CardDTO(
|
||||||
get() = TODO("Not yet implemented")
|
cardId = "AF10000000981426",
|
||||||
|
batchId = "AF10",
|
||||||
|
cardPublicKey = byteArrayOf(3, 7, 80, -118, 6, 77, -15, -22, 107, 105, -64, 103, 77, -79, -102, 106, 46, 84, 21, -34, 47, -74, -56, 124, 17, -49, -29, 76, 84, 59, 50, -15, -52),
|
||||||
|
firmwareVersion = CardDTO.FirmwareVersion(
|
||||||
|
major = 6,
|
||||||
|
minor = 33,
|
||||||
|
patch = 0,
|
||||||
|
type = FirmwareVersion.FirmwareType.Release,
|
||||||
|
),
|
||||||
|
manufacturer = CardDTO.Manufacturer(
|
||||||
|
name = "TANGEM",
|
||||||
|
manufactureDate = Date(1698094800000),
|
||||||
|
signature = byteArrayOf(71, -20, 9, 31, 25, 111, 61, 119, 109, -123, -63, 51, 58, -71, -44, 53, 57, 20, -16, 97, -87, -82, 1, -35, -48, 63, 77, -78, -89, -112, -27, 25, -10, 90, 7, -53, -84, -125, 112, 68, -14, -85, -100, -64, -115, 31, 42, 119, 87, -79, 127, 42, -87, -102, 13, -9, -10, -51, -29, -63, -1, -52, -117, 57),
|
||||||
|
),
|
||||||
|
issuer = CardDTO.Issuer(
|
||||||
|
name = "TANGEM 2.0",
|
||||||
|
publicKey = byteArrayOf(2, -120, 89, -52, -60, 36, -73, -17, 103, 107, -110, -36, 3, 110, -122, 72, 43, -38, 8, 30, -50, 25, -23, -17, 38, 94, 5, -112, -20, 9, 54, -24, -32),
|
||||||
|
),
|
||||||
|
settings = CardDTO.Settings(
|
||||||
|
securityDelay = 15000,
|
||||||
|
maxWalletsCount = 20,
|
||||||
|
isSettingAccessCodeAllowed = true,
|
||||||
|
isSettingPasscodeAllowed = true,
|
||||||
|
isResettingUserCodesAllowed = false,
|
||||||
|
isLinkedTerminalEnabled = true,
|
||||||
|
isBackupAllowed = true,
|
||||||
|
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
|
||||||
|
isFilesAllowed = true,
|
||||||
|
isHDWalletAllowed = true,
|
||||||
|
isKeysImportAllowed = false,
|
||||||
|
),
|
||||||
|
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
|
||||||
|
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
|
||||||
|
isAccessCodeSet = true,
|
||||||
|
isPasscodeSet = false,
|
||||||
|
supportedCurves = listOf(
|
||||||
|
EllipticCurve.Secp256k1,
|
||||||
|
EllipticCurve.Ed25519,
|
||||||
|
EllipticCurve.Bls12381G2Aug,
|
||||||
|
EllipticCurve.Secp256r1,
|
||||||
|
EllipticCurve.Ed25519Slip0010,
|
||||||
|
EllipticCurve.Bls12381G2,
|
||||||
|
EllipticCurve.Bls12381G2Pop,
|
||||||
|
EllipticCurve.Bip0340,
|
||||||
|
),
|
||||||
|
wallets = listOf(
|
||||||
|
CardDTO.Wallet(
|
||||||
|
publicKey = byteArrayOf(2, -114, -64, 120, -121, 11, -28, 89, -91, 114, 10, 84, -87, -36, 36, 19, -69, 95, 66, 14, 32, -35, -99, -67, 118, 51, 26, 71, -78, -36, 59, -126, -58),
|
||||||
|
chainCode = byteArrayOf(-47, -8, 74, 69, -1, 52, 10, -56, -85, 118, 56, 77, 125, -12, 85, -23, 42, -58, 99, 47, -87, -34, -83, 72, -122, -29, 88, -85, 46, -118, -26, 116),
|
||||||
|
curve = EllipticCurve.Secp256k1,
|
||||||
|
settings = CardWallet.Settings(isPermanent = false),
|
||||||
|
totalSignedHashes = 73,
|
||||||
|
remainingSignatures = null,
|
||||||
|
index = 0,
|
||||||
|
hasBackup = true,
|
||||||
|
derivedKeys = mapOf(
|
||||||
|
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
|
||||||
|
publicKey = byteArrayOf(2, 55, -114, -94, -73, -61, -50, 51, -115, 55, -79, 63, -96, -44, -64, -24, -36, -122, -123, -38, 81, -15, -127, -97, -42, 72, -85, -62, -98, 46, 119, 16, -55),
|
||||||
|
chainCode = byteArrayOf(31, 17, 71, -28, -29, 17, 72, -29, -98, 112, 31, -8, 72, -75, 4, -11, 60, -100, 9, 35, 58, -42, -38, 96, -71, -68, 24, -119, -43, -18, -122, 72),
|
||||||
|
),
|
||||||
|
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey(
|
||||||
|
publicKey = byteArrayOf(2, -26, 103, 96, 112, 127, -125, 0, 7, 53, 30, -12, -82, 45, 14, 107, -9, 126, 75, 104, -67, -49, 35, -12, -82, -90, 101, -101, 125, -76, 88, -54, 99),
|
||||||
|
chainCode = byteArrayOf(-42, 36, 97, 65, -64, -113, 76, -91, -9, 11, 89, 123, -9, -3, 21, 103, -113, -60, 48, -31, -34, 108, 111, -38, -110, -80, 109, 17, -29, 2, 45, -71),
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
|
||||||
|
publicKey = byteArrayOf(2, -96, -3, -83, 101, 63, -25, -125, -4, -65, -42, -56, 24, -52, 118, -11, -104, -105, 40, -59, 20, -109, -97, 29, -95, -6, -80, 2, 67, 103, -80, -22, -94),
|
||||||
|
chainCode = byteArrayOf(-125, 27, -91, 38, -66, 109, -92, 16, -37, 93, 107, -29, -128, -1, 115, -64, 108, -63, 17, 27, 58, 78, -2, 39, 88, -39, 44, 89, 32, -38, -16, -38),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
extendedPublicKey = ExtendedPublicKey(
|
||||||
|
publicKey = byteArrayOf(2, -114, -64, 120, -121, 11, -28, 89, -91, 114, 10, 84, -87, -36, 36, 19, -69, 95, 66, 14, 32, -35, -99, -67, 118, 51, 26, 71, -78, -36, 59, -126, -58),
|
||||||
|
chainCode = byteArrayOf(-47, -8, 74, 69, -1, 52, 10, -56, -85, 118, 56, 77, 125, -12, 85, -23, 42, -58, 99, 47, -87, -34, -83, 72, -122, -29, 88, -85, 46, -118, -26, 116),
|
||||||
|
),
|
||||||
|
isImported = false,
|
||||||
|
),
|
||||||
|
CardDTO.Wallet(
|
||||||
|
publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90),
|
||||||
|
chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11),
|
||||||
|
curve = EllipticCurve.Ed25519,
|
||||||
|
settings = CardWallet.Settings(isPermanent = false),
|
||||||
|
totalSignedHashes = 0,
|
||||||
|
remainingSignatures = null,
|
||||||
|
index = 1,
|
||||||
|
hasBackup = true,
|
||||||
|
derivedKeys = emptyMap(),
|
||||||
|
extendedPublicKey = ExtendedPublicKey(
|
||||||
|
publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90),
|
||||||
|
chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11),
|
||||||
|
),
|
||||||
|
isImported = false,
|
||||||
|
),
|
||||||
|
CardDTO.Wallet(
|
||||||
|
publicKey = byteArrayOf(-94, 111, -89, -77, -62, -113, 16, -118, -46, -16, -20, 28, 53, -82, -109, 28, 99, -98, -54, 59, -3, 99, 16, -70, -73, 43, -6, 33, -53, -66, 76, -72, 6, -49, 83, -121, -122, 111, -111, -116, -119, 98, -94, -98, -121, -37, 20, -95),
|
||||||
|
chainCode = null,
|
||||||
|
curve = EllipticCurve.Bls12381G2Aug,
|
||||||
|
settings = CardWallet.Settings(isPermanent = false),
|
||||||
|
totalSignedHashes = 1,
|
||||||
|
remainingSignatures = null,
|
||||||
|
index = 2,
|
||||||
|
hasBackup = true,
|
||||||
|
derivedKeys = emptyMap(),
|
||||||
|
extendedPublicKey = null,
|
||||||
|
isImported = false,
|
||||||
|
),
|
||||||
|
CardDTO.Wallet(
|
||||||
|
publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123),
|
||||||
|
chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112),
|
||||||
|
curve = EllipticCurve.Bip0340,
|
||||||
|
settings = CardWallet.Settings(isPermanent = false),
|
||||||
|
totalSignedHashes = 0,
|
||||||
|
remainingSignatures = null,
|
||||||
|
index = 3,
|
||||||
|
hasBackup = true,
|
||||||
|
derivedKeys = emptyMap(),
|
||||||
|
extendedPublicKey = ExtendedPublicKey(
|
||||||
|
publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123),
|
||||||
|
chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112),
|
||||||
|
),
|
||||||
|
isImported = false,
|
||||||
|
),
|
||||||
|
CardDTO.Wallet(
|
||||||
|
publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47),
|
||||||
|
chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89),
|
||||||
|
curve = EllipticCurve.Ed25519Slip0010,
|
||||||
|
settings = CardWallet.Settings(isPermanent = false),
|
||||||
|
totalSignedHashes = 15,
|
||||||
|
remainingSignatures = null,
|
||||||
|
index = 4,
|
||||||
|
hasBackup = true,
|
||||||
|
derivedKeys = emptyMap(),
|
||||||
|
extendedPublicKey = ExtendedPublicKey(
|
||||||
|
publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47),
|
||||||
|
chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89),
|
||||||
|
),
|
||||||
|
isImported = false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
attestation = Attestation(
|
||||||
|
cardKeyAttestation = Attestation.Status.Verified,
|
||||||
|
walletKeysAttestation = Attestation.Status.Skipped,
|
||||||
|
firmwareAttestation = Attestation.Status.Skipped,
|
||||||
|
cardUniquenessAttestation = Attestation.Status.Skipped,
|
||||||
|
),
|
||||||
|
backupStatus = CardDTO.BackupStatus.Active(1),
|
||||||
|
)
|
||||||
|
|
||||||
override val derivationTaskResponse: DerivationTaskResponse
|
override val scanResponse = ScanResponse(
|
||||||
get() = TODO("Not yet implemented")
|
card = cardDto,
|
||||||
|
productType = ProductType.Wallet2,
|
||||||
|
walletData = null,
|
||||||
|
secondTwinPublicKey = null,
|
||||||
|
derivedKeys = emptyMap(),
|
||||||
|
primaryCard = null,
|
||||||
|
)
|
||||||
|
|
||||||
override val extendedPublicKey: ExtendedPublicKey
|
override val derivationTaskResponse = DerivationTaskResponse(
|
||||||
get() = TODO("Not yet implemented")
|
entries = mapOf(
|
||||||
|
ByteArrayKey(
|
||||||
|
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||||
|
)
|
||||||
|
to
|
||||||
|
ExtendedPublicKeysMap(
|
||||||
|
mapOf(
|
||||||
|
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
|
||||||
|
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
|
||||||
|
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
|
||||||
|
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||||
|
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch
|
||||||
|
publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121),
|
||||||
|
chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge
|
||||||
|
publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124),
|
||||||
|
chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
override val successResponse: SuccessResponse
|
override val extendedPublicKey = ExtendedPublicKey(
|
||||||
get() = TODO("Not yet implemented")
|
publicKey = byteArrayOf(3, 2, 95, 53, 40, -87, -60, 11, -8, -47, 41, 37, 100, 15, -69, 1, -122, 127, -20, -81, -32, -20, -24, 5, -28, 113, 106, -90, -59, -30, -27, -110, -110),
|
||||||
|
chainCode = byteArrayOf(-95, -87, -95, -25, 27, 96, -57, -92, -69, -106, -45, 10, 85, 4, -92, -68, 49, -24, -28, -50, -49, -77, -20, 118, -50, -27, 104, -93, 115, -50, -46, -34),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
)
|
||||||
|
|
||||||
override val createProductWalletTaskResponse: CreateProductWalletTaskResponse
|
override val successResponse = SuccessResponse(cardId = "AF10000000981426")
|
||||||
get() = TODO("Not yet implemented")
|
|
||||||
|
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
|
||||||
|
card = cardDto,
|
||||||
|
derivedKeys = mapOf(
|
||||||
|
ByteArrayKey(
|
||||||
|
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||||
|
)
|
||||||
|
to
|
||||||
|
ExtendedPublicKeysMap(
|
||||||
|
mapOf(
|
||||||
|
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
|
||||||
|
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
|
||||||
|
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
|
||||||
|
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||||
|
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
primaryCard = primaryCard,
|
||||||
|
)
|
||||||
|
|
||||||
override val importWalletResponse: CreateProductWalletTaskResponse
|
override val importWalletResponse: CreateProductWalletTaskResponse
|
||||||
get() = TODO("Not yet implemented")
|
get() = TODO("Not yet implemented")
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,10 @@ object WalletMockContent : MockContent {
|
||||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
),
|
),
|
||||||
|
DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey(
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
extendedPublicKey = ExtendedPublicKey(
|
extendedPublicKey = ExtendedPublicKey(
|
||||||
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||||
|
|
@ -189,6 +193,13 @@ object WalletMockContent : MockContent {
|
||||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
childNumber = 0,
|
childNumber = 0,
|
||||||
),
|
),
|
||||||
|
DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey(
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.datasource.local.token.UserTokensStore
|
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
|
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||||
import com.tangem.domain.common.DerivationStyleProvider
|
import com.tangem.domain.common.DerivationStyleProvider
|
||||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
|
|
@ -22,7 +25,7 @@ internal data class BlockchainToDerive(
|
||||||
|
|
||||||
// FIXME: May be move to DI, currently unnecessary
|
// FIXME: May be move to DI, currently unnecessary
|
||||||
internal class DerivationsFinder(
|
internal class DerivationsFinder(
|
||||||
private val newTokensStore: UserTokensStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
@ -64,7 +67,9 @@ internal class DerivationsFinder(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getBlockchains(userWalletId: UserWalletId): MutableSet<BlockchainToDerive> {
|
private suspend fun getBlockchains(userWalletId: UserWalletId): MutableSet<BlockchainToDerive> {
|
||||||
val responseTokens = newTokensStore.getSyncOrNull(userWalletId)
|
val responseTokens = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||||
|
key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue),
|
||||||
|
)
|
||||||
?.tokens
|
?.tokens
|
||||||
?: return hashSetOf()
|
?: return hashSetOf()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ class WalletConnectSdkHelper {
|
||||||
} else {
|
} else {
|
||||||
feeAmount
|
feeAmount
|
||||||
}
|
}
|
||||||
Fee.Ethereum(patchedAmount, gasLimit.toBigInteger(), gasPrice.toBigInteger())
|
Fee.Ethereum.Legacy(patchedAmount, gasLimit.toBigInteger(), gasPrice.toBigInteger())
|
||||||
} else {
|
} else {
|
||||||
Fee.Common(feeAmount)
|
Fee.Common(feeAmount)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ import kotlinx.coroutines.launch
|
||||||
internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestContent, modifier: Modifier = Modifier) {
|
internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestContent, modifier: Modifier = Modifier) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
|
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
|
||||||
bottomSheetState = BottomSheetState(initialValue = BottomSheetValue.Collapsed),
|
bottomSheetState = BottomSheetState(initialValue = BottomSheetValue.Collapsed, LocalDensity.current),
|
||||||
)
|
)
|
||||||
|
|
||||||
BackHandler(
|
BackHandler(
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||||
import com.tangem.core.ui.components.BasicDialog
|
import com.tangem.core.ui.components.BasicDialog
|
||||||
import com.tangem.core.ui.components.DialogButton
|
import com.tangem.core.ui.components.DialogButtonUM
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory
|
import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory
|
||||||
|
|
@ -20,12 +20,12 @@ internal fun SettingsAlertDialog(dialog: Dialog.Alert) {
|
||||||
title = dialog.title.resolveReference(),
|
title = dialog.title.resolveReference(),
|
||||||
message = dialog.description.resolveReference(),
|
message = dialog.description.resolveReference(),
|
||||||
isDismissable = false,
|
isDismissable = false,
|
||||||
confirmButton = DialogButton(
|
confirmButton = DialogButtonUM(
|
||||||
title = dialog.confirmText.resolveReference(),
|
title = dialog.confirmText.resolveReference(),
|
||||||
warning = true,
|
warning = true,
|
||||||
onClick = dialog.onConfirm,
|
onClick = dialog.onConfirm,
|
||||||
),
|
),
|
||||||
dismissButton = DialogButton(
|
dismissButton = DialogButtonUM(
|
||||||
title = stringResource(id = R.string.common_cancel),
|
title = stringResource(id = R.string.common_cancel),
|
||||||
onClick = dialog.onDismiss,
|
onClick = dialog.onDismiss,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||||
import com.tangem.core.ui.components.DialogButton
|
import com.tangem.core.ui.components.DialogButtonUM
|
||||||
import com.tangem.core.ui.components.SelectorDialog
|
import com.tangem.core.ui.components.SelectorDialog
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
|
@ -21,7 +21,7 @@ internal fun SettingsSelectorDialog(dialog: Dialog.Selector) {
|
||||||
title = dialog.title.resolveReference(),
|
title = dialog.title.resolveReference(),
|
||||||
selectedItemIndex = dialog.selectedItemIndex,
|
selectedItemIndex = dialog.selectedItemIndex,
|
||||||
items = dialog.items.map { it.resolveReference() }.toImmutableList(),
|
items = dialog.items.map { it.resolveReference() }.toImmutableList(),
|
||||||
confirmButton = DialogButton(
|
confirmButton = DialogButtonUM(
|
||||||
title = stringResource(R.string.common_cancel),
|
title = stringResource(R.string.common_cancel),
|
||||||
onClick = dialog.onDismiss,
|
onClick = dialog.onDismiss,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.domain.card.ScanCardProcessor
|
import com.tangem.domain.card.ScanCardProcessor
|
||||||
import com.tangem.domain.common.CardTypesResolver
|
import com.tangem.domain.common.CardTypesResolver
|
||||||
import com.tangem.domain.common.util.cardTypesResolver
|
import com.tangem.domain.common.util.cardTypesResolver
|
||||||
|
import com.tangem.domain.common.util.getBackupCardsCount
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||||
|
|
@ -159,6 +160,8 @@ internal class CardSettingsViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scanResponse.cardTypesResolver.isTangemTwins()) {
|
if (scanResponse.cardTypesResolver.isTangemTwins()) {
|
||||||
|
// needs to prepare twin state if it's twin cards (depends on onboarding refactoring)
|
||||||
|
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||||
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet(scanResponse)))
|
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet(scanResponse)))
|
||||||
|
|
||||||
cardSettingsInteractor.clear()
|
cardSettingsInteractor.clear()
|
||||||
|
|
@ -173,13 +176,7 @@ internal class CardSettingsViewModel @Inject constructor(
|
||||||
userWalletId = userWalletId,
|
userWalletId = userWalletId,
|
||||||
cardId = card.cardId,
|
cardId = card.cardId,
|
||||||
isActiveBackupStatus = card.backupStatus?.isActive == true,
|
isActiveBackupStatus = card.backupStatus?.isActive == true,
|
||||||
backupCardsCount = when (val status = card.backupStatus) {
|
backupCardsCount = scanResponse.getBackupCardsCount() ?: 0,
|
||||||
is CardDTO.BackupStatus.Active -> status.cardCount
|
|
||||||
is CardDTO.BackupStatus.CardLinked,
|
|
||||||
CardDTO.BackupStatus.NoBackup,
|
|
||||||
null,
|
|
||||||
-> 0
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -190,11 +190,11 @@ private fun CommonResetDialog(dialog: ResetCardScreenState.Dialog) {
|
||||||
BasicDialog(
|
BasicDialog(
|
||||||
title = stringResource(dialog.titleResId),
|
title = stringResource(dialog.titleResId),
|
||||||
message = stringResource(dialog.messageResId),
|
message = stringResource(dialog.messageResId),
|
||||||
dismissButton = DialogButton(
|
dismissButton = DialogButtonUM(
|
||||||
title = stringResource(id = R.string.common_cancel),
|
title = stringResource(id = R.string.common_cancel),
|
||||||
onClick = dialog.onDismiss,
|
onClick = dialog.onDismiss,
|
||||||
),
|
),
|
||||||
confirmButton = DialogButton(
|
confirmButton = DialogButtonUM(
|
||||||
title = stringResource(id = R.string.card_settings_action_sheet_reset),
|
title = stringResource(id = R.string.card_settings_action_sheet_reset),
|
||||||
warning = true,
|
warning = true,
|
||||||
onClick = dialog.onConfirmClick,
|
onClick = dialog.onConfirmClick,
|
||||||
|
|
@ -208,7 +208,7 @@ private fun CompletedResetDialog(dialog: ResetCardDialog) {
|
||||||
BasicDialog(
|
BasicDialog(
|
||||||
title = stringResource(id = dialog.titleResId),
|
title = stringResource(id = dialog.titleResId),
|
||||||
message = stringResource(id = dialog.messageResId),
|
message = stringResource(id = dialog.messageResId),
|
||||||
confirmButton = DialogButton(
|
confirmButton = DialogButtonUM(
|
||||||
title = stringResource(id = R.string.common_ok),
|
title = stringResource(id = R.string.common_ok),
|
||||||
onClick = dialog.onConfirmClick,
|
onClick = dialog.onConfirmClick,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,11 @@ import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.tangem.common.routing.AppRoute
|
|
||||||
import com.tangem.core.analytics.Analytics
|
|
||||||
import com.tangem.core.ui.UiDependencies
|
import com.tangem.core.ui.UiDependencies
|
||||||
import com.tangem.core.ui.components.SystemBarsIconsDisposable
|
import com.tangem.core.ui.components.SystemBarsIconsDisposable
|
||||||
import com.tangem.core.ui.screen.ComposeFragment
|
import com.tangem.core.ui.screen.ComposeFragment
|
||||||
import com.tangem.domain.tokens.TokensAction
|
|
||||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
|
||||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
|
||||||
import com.tangem.tap.common.redux.AppState
|
import com.tangem.tap.common.redux.AppState
|
||||||
import com.tangem.tap.features.home.compose.StoriesScreen
|
import com.tangem.tap.features.home.compose.StoriesScreen
|
||||||
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
|
|
||||||
import com.tangem.tap.features.home.redux.HomeAction
|
import com.tangem.tap.features.home.redux.HomeAction
|
||||||
import com.tangem.tap.features.home.redux.HomeState
|
import com.tangem.tap.features.home.redux.HomeState
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
|
|
@ -32,9 +25,6 @@ internal class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
|
||||||
@Inject
|
@Inject
|
||||||
override lateinit var uiDependencies: UiDependencies
|
override lateinit var uiDependencies: UiDependencies
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var homeFeatureToggles: HomeFeatureToggles
|
|
||||||
|
|
||||||
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
|
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
|
||||||
|
|
||||||
private val viewModel by viewModels<HomeViewModel>()
|
private val viewModel by viewModels<HomeViewModel>()
|
||||||
|
|
@ -77,31 +67,9 @@ internal class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
|
||||||
private fun ScreenContent() {
|
private fun ScreenContent() {
|
||||||
StoriesScreen(
|
StoriesScreen(
|
||||||
homeState = homeState,
|
homeState = homeState,
|
||||||
onScanButtonClick = {
|
onScanButtonClick = viewModel::onScanClick,
|
||||||
if (homeFeatureToggles.isCallbacksRefactoringEnabled) {
|
onShopButtonClick = viewModel::onShopClick,
|
||||||
viewModel.onScanClick()
|
onSearchTokensClick = viewModel::onSearchClick,
|
||||||
} else {
|
|
||||||
Analytics.send(IntroductionProcess.ButtonScanCard())
|
|
||||||
store.dispatch(action = HomeAction.ReadCard(scope = requireActivity().lifecycleScope))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onShopButtonClick = {
|
|
||||||
if (homeFeatureToggles.isCallbacksRefactoringEnabled) {
|
|
||||||
viewModel.onShopClick()
|
|
||||||
} else {
|
|
||||||
Analytics.send(IntroductionProcess.ButtonBuyCards())
|
|
||||||
store.dispatch(HomeAction.GoToShop)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSearchTokensClick = {
|
|
||||||
if (homeFeatureToggles.isCallbacksRefactoringEnabled) {
|
|
||||||
viewModel.onSearchClick()
|
|
||||||
} else {
|
|
||||||
Analytics.send(IntroductionProcess.ButtonTokensList())
|
|
||||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens(readOnlyContent = true)) }
|
|
||||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,8 @@ package com.tangem.tap.features.home
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.google.firebase.analytics.ktx.analytics
|
||||||
|
import com.google.firebase.ktx.Firebase
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
|
|
@ -45,6 +47,8 @@ internal class HomeViewModel @Inject constructor(
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val tangemErrorHandler = TangemTangemErrorsHandler(store)
|
||||||
|
|
||||||
fun onScanClick() {
|
fun onScanClick() {
|
||||||
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard())
|
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard())
|
||||||
scanCard()
|
scanCard()
|
||||||
|
|
@ -54,14 +58,16 @@ internal class HomeViewModel @Inject constructor(
|
||||||
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards())
|
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards())
|
||||||
analyticsEventHandler.send(Shop.ScreenOpened())
|
analyticsEventHandler.send(Shop.ScreenOpened())
|
||||||
|
|
||||||
urlOpener.openUrl(NEW_BUY_WALLET_URL)
|
Firebase.analytics.appInstanceId
|
||||||
|
.addOnSuccessListener { urlOpener.openUrl(url = "$NEW_BUY_WALLET_URL&app_instance_id=$it") }
|
||||||
|
.addOnFailureListener { urlOpener.openUrl(url = NEW_BUY_WALLET_URL) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSearchClick() {
|
fun onSearchClick() {
|
||||||
analyticsEventHandler.send(IntroductionProcess.ButtonTokensList())
|
analyticsEventHandler.send(IntroductionProcess.ButtonTokensList())
|
||||||
|
|
||||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens(readOnlyContent = true)) }
|
store.dispatchNavigationAction { push(AppRoute.ManageTokens()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scanCard() {
|
private fun scanCard() {
|
||||||
|
|
@ -79,7 +85,7 @@ internal class HomeViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
Timber.e(it, "Unable to scan card")
|
tangemErrorHandler.onErrorReceived(error = it)
|
||||||
delay(HIDE_PROGRESS_DELAY)
|
delay(HIDE_PROGRESS_DELAY)
|
||||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.tangem.tap.features.home
|
||||||
|
|
||||||
|
import com.tangem.blockchain.common.BlockchainError
|
||||||
|
import com.tangem.common.core.TangemError
|
||||||
|
import com.tangem.common.core.TangemSdkError
|
||||||
|
import com.tangem.domain.redux.StateDialog
|
||||||
|
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||||
|
import com.tangem.tap.common.redux.AppState
|
||||||
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
|
import com.tangem.tap.features.home.errors.TangemSdkErrorHandler
|
||||||
|
import org.rekotlin.Store
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class TangemTangemErrorsHandler(val store: Store<AppState>) : TangemSdkErrorHandler {
|
||||||
|
|
||||||
|
override fun onErrorReceived(error: TangemError) {
|
||||||
|
when (error) {
|
||||||
|
is TangemSdkError -> {
|
||||||
|
handleCardSdkError(error)
|
||||||
|
}
|
||||||
|
is BlockchainError -> {
|
||||||
|
handleBlockchainSdkError(error)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Timber.e("Error happened", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCardSdkError(error: TangemSdkError) {
|
||||||
|
when (error) {
|
||||||
|
is TangemSdkError.NfcFeatureIsUnavailable -> {
|
||||||
|
store.dispatchOnMain(GlobalAction.ShowDialog(StateDialog.NfcFeatureIsUnavailable))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Timber.e(error, "Unable to scan card")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleBlockchainSdkError(error: TangemError) {
|
||||||
|
Timber.e("Sdk error happened", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.tap.features.home.errors
|
||||||
|
|
||||||
|
import com.tangem.common.core.TangemError
|
||||||
|
|
||||||
|
interface TangemSdkErrorHandler {
|
||||||
|
|
||||||
|
fun onErrorReceived(error: TangemError)
|
||||||
|
}
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package com.tangem.tap.features.home.featuretoggles
|
|
||||||
|
|
||||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class HomeFeatureToggles @Inject constructor(
|
|
||||||
private val featureTogglesManager: FeatureTogglesManager,
|
|
||||||
) {
|
|
||||||
|
|
||||||
val isCallbacksRefactoringEnabled: Boolean
|
|
||||||
get() = featureTogglesManager.isFeatureEnabled(name = "HOME_SCREEN_CALLBACKS_REFACTORING_ENABLED")
|
|
||||||
}
|
|
||||||
|
|
@ -15,7 +15,6 @@ sealed class HomeAction : Action {
|
||||||
data class ReadCard(val scope: CoroutineScope) : HomeAction()
|
data class ReadCard(val scope: CoroutineScope) : HomeAction()
|
||||||
|
|
||||||
data class ScanInProgress(val scanInProgress: Boolean) : HomeAction()
|
data class ScanInProgress(val scanInProgress: Boolean) : HomeAction()
|
||||||
data object GoToShop : HomeAction()
|
|
||||||
|
|
||||||
data class UpdateCountryCode(val userCountryCode: String) : HomeAction()
|
data class UpdateCountryCode(val userCountryCode: String) : HomeAction()
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package com.tangem.tap.features.home.redux
|
package com.tangem.tap.features.home.redux
|
||||||
|
|
||||||
import com.google.firebase.analytics.ktx.analytics
|
|
||||||
import com.google.firebase.ktx.Firebase
|
|
||||||
import com.tangem.common.doOnFailure
|
import com.tangem.common.doOnFailure
|
||||||
import com.tangem.common.doOnResult
|
import com.tangem.common.doOnResult
|
||||||
import com.tangem.common.doOnSuccess
|
import com.tangem.common.doOnSuccess
|
||||||
|
|
@ -15,11 +13,12 @@ import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||||
import com.tangem.tap.common.analytics.events.Shop
|
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||||
import com.tangem.tap.common.extensions.*
|
import com.tangem.tap.common.extensions.eraseContext
|
||||||
|
import com.tangem.tap.common.extensions.inject
|
||||||
|
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||||
import com.tangem.tap.common.redux.AppState
|
import com.tangem.tap.common.redux.AppState
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
|
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.tap.scope
|
import com.tangem.tap.scope
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
|
|
@ -61,21 +60,6 @@ private fun handleHomeAction(action: Action) {
|
||||||
readCard()
|
readCard()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is HomeAction.GoToShop -> {
|
|
||||||
Analytics.send(Shop.ScreenOpened())
|
|
||||||
Firebase.analytics.appInstanceId
|
|
||||||
.addOnSuccessListener {
|
|
||||||
store.dispatchOpenUrl("$NEW_BUY_WALLET_URL&app_instance_id=$it")
|
|
||||||
}
|
|
||||||
.addOnFailureListener {
|
|
||||||
store.dispatchOpenUrl(NEW_BUY_WALLET_URL)
|
|
||||||
}
|
|
||||||
// disabled for now in task [REDACTED_JIRA]
|
|
||||||
// when (action.userCountryCode) {
|
|
||||||
// RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL)
|
|
||||||
// else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ internal class MainViewModel @Inject constructor(
|
||||||
|
|
||||||
private fun fetchStakingTokens() {
|
private fun fetchStakingTokens() {
|
||||||
viewModelScope.launch(dispatchers.main) {
|
viewModelScope.launch(dispatchers.main) {
|
||||||
fetchStakingTokensUseCase()
|
fetchStakingTokensUseCase(true)
|
||||||
.onLeft { Timber.e(it.toString(), "Unable to fetch the staking tokens list") }
|
.onLeft { Timber.e(it.toString(), "Unable to fetch the staking tokens list") }
|
||||||
.onRight { Timber.d("Staking token list was fetched successfully") }
|
.onRight { Timber.d("Staking token list was fetched successfully") }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -317,16 +317,16 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>(
|
||||||
|
|
||||||
private fun setupTopUpWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
private fun setupTopUpWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||||
when (previousStep) {
|
when (previousStep) {
|
||||||
TwinCardsStep.None -> {
|
TwinCardsStep.None, TwinCardsStep.Welcome -> {
|
||||||
when (state.cardNumber) {
|
when (state.cardNumber) {
|
||||||
TwinCardNumber.First -> {
|
TwinCardNumber.First -> {
|
||||||
twinsWidget.leapfrogWidget.unfold(false) {
|
twinsWidget.leapfrogWidget.unfold(true) {
|
||||||
twinsWidget.toActivate(false)
|
twinsWidget.toActivate(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TwinCardNumber.Second -> {
|
TwinCardNumber.Second -> {
|
||||||
switchToCard(state.cardNumber, false) {
|
switchToCard(state.cardNumber, true) {
|
||||||
twinsWidget.toActivate(false)
|
twinsWidget.toActivate(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@ sealed class OnboardingWalletAction : Action {
|
||||||
data object ResumeBackup : OnboardingWalletAction()
|
data object ResumeBackup : OnboardingWalletAction()
|
||||||
|
|
||||||
data class LoadArtwork(val cardArtworkUriForUnfinishedBackup: Uri? = null) : OnboardingWalletAction()
|
data class LoadArtwork(val cardArtworkUriForUnfinishedBackup: Uri? = null) : OnboardingWalletAction()
|
||||||
class SetArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction()
|
class SetPrimaryCardArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction()
|
||||||
|
class SetSecondCardArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction()
|
||||||
|
class SetThirdCardArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction()
|
||||||
|
|
||||||
data object OnBackPressed : OnboardingWalletAction()
|
data object OnBackPressed : OnboardingWalletAction()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
|
import com.tangem.common.card.Card
|
||||||
import com.tangem.common.core.TangemSdkError
|
import com.tangem.common.core.TangemSdkError
|
||||||
import com.tangem.common.extensions.guard
|
import com.tangem.common.extensions.guard
|
||||||
import com.tangem.common.extensions.ifNotNull
|
import com.tangem.common.extensions.ifNotNull
|
||||||
|
|
@ -11,6 +12,7 @@ import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.common.routing.AppRouter
|
import com.tangem.common.routing.AppRouter
|
||||||
import com.tangem.common.services.Result
|
import com.tangem.common.services.Result
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
|
import com.tangem.domain.common.extensions.withIOContext
|
||||||
import com.tangem.domain.common.extensions.withMainContext
|
import com.tangem.domain.common.extensions.withMainContext
|
||||||
import com.tangem.domain.common.util.cardTypesResolver
|
import com.tangem.domain.common.util.cardTypesResolver
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
|
|
@ -106,7 +108,7 @@ private fun handleWalletAction(action: Action) {
|
||||||
val cardPublicKey = backupService.primaryPublicKey
|
val cardPublicKey = backupService.primaryPublicKey
|
||||||
if (primaryCardId != null && cardPublicKey != null) {
|
if (primaryCardId != null && cardPublicKey != null) {
|
||||||
// uses when no scanResponse and backup state restored
|
// uses when no scanResponse and backup state restored
|
||||||
loadArtworkForUnfinishedBackup(
|
loadArtworkForCard(
|
||||||
cardId = primaryCardId,
|
cardId = primaryCardId,
|
||||||
cardPublicKey = cardPublicKey,
|
cardPublicKey = cardPublicKey,
|
||||||
defaultArtwork = action.cardArtworkUriForUnfinishedBackup,
|
defaultArtwork = action.cardArtworkUriForUnfinishedBackup,
|
||||||
|
|
@ -119,7 +121,7 @@ private fun handleWalletAction(action: Action) {
|
||||||
.takeIf { it != Artwork.DEFAULT_IMG_URL }
|
.takeIf { it != Artwork.DEFAULT_IMG_URL }
|
||||||
?.let { Uri.parse(it) }
|
?.let { Uri.parse(it) }
|
||||||
}
|
}
|
||||||
store.dispatchOnMain(OnboardingWalletAction.SetArtworkUrl(cardArtwork))
|
store.dispatchOnMain(OnboardingWalletAction.SetPrimaryCardArtworkUrl(cardArtwork))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is OnboardingWalletAction.CreateWallet -> {
|
is OnboardingWalletAction.CreateWallet -> {
|
||||||
|
|
@ -232,11 +234,7 @@ private suspend fun readCard(onSuccess: (ScanResponse) -> Unit) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadArtworkForUnfinishedBackup(
|
private suspend fun loadArtworkForCard(cardId: String, cardPublicKey: ByteArray, defaultArtwork: Uri?): Uri {
|
||||||
cardId: String,
|
|
||||||
cardPublicKey: ByteArray,
|
|
||||||
defaultArtwork: Uri?,
|
|
||||||
): Uri {
|
|
||||||
return when (val cardInfo = OnlineCardVerifier().getCardInfo(cardId, cardPublicKey)) {
|
return when (val cardInfo = OnlineCardVerifier().getCardInfo(cardId, cardPublicKey)) {
|
||||||
is Result.Success -> {
|
is Result.Success -> {
|
||||||
val artworkId = cardInfo.data.artwork?.id
|
val artworkId = cardInfo.data.artwork?.id
|
||||||
|
|
@ -443,6 +441,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
||||||
store.dispatchOnMain(BackupAction.AddBackupCard.ChangeButtonLoading(false))
|
store.dispatchOnMain(BackupAction.AddBackupCard.ChangeButtonLoading(false))
|
||||||
when (result) {
|
when (result) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
|
updateArtworks(backupService.addedBackupCardsCount, result.data)
|
||||||
store.dispatchOnMain(BackupAction.AddBackupCard.Success)
|
store.dispatchOnMain(BackupAction.AddBackupCard.Success)
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
|
|
@ -630,6 +629,22 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateArtworks(addedBackupCardsCount: Int, card: Card) {
|
||||||
|
mainScope.launch {
|
||||||
|
withIOContext {
|
||||||
|
val imageUri = loadArtworkForCard(card.cardId, card.cardPublicKey, Uri.EMPTY)
|
||||||
|
when (addedBackupCardsCount) {
|
||||||
|
1 -> {
|
||||||
|
store.dispatchOnMain(OnboardingWalletAction.SetSecondCardArtworkUrl(imageUri))
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
store.dispatchOnMain(OnboardingWalletAction.SetThirdCardArtworkUrl(imageUri))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal fun gatherCardIds(backupState: BackupState, card: CardDTO?): List<String> {
|
internal fun gatherCardIds(backupState: BackupState, card: CardDTO?): List<String> {
|
||||||
return (listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds)
|
return (listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds)
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,20 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet
|
||||||
step = OnboardingWalletStep.CreateWallet,
|
step = OnboardingWalletStep.CreateWallet,
|
||||||
)
|
)
|
||||||
is OnboardingWalletAction.ResumeBackup -> state.copy(step = OnboardingWalletStep.Backup)
|
is OnboardingWalletAction.ResumeBackup -> state.copy(step = OnboardingWalletStep.Backup)
|
||||||
is OnboardingWalletAction.SetArtworkUrl -> {
|
is OnboardingWalletAction.SetPrimaryCardArtworkUrl -> {
|
||||||
state.copy(cardArtworkUri = action.artworkUri)
|
state.copy(
|
||||||
|
walletImages = state.walletImages.copy(primaryCardImage = action.artworkUri),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is OnboardingWalletAction.SetSecondCardArtworkUrl -> {
|
||||||
|
state.copy(
|
||||||
|
walletImages = state.walletImages.copy(secondCardImage = action.artworkUri),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is OnboardingWalletAction.SetThirdCardArtworkUrl -> {
|
||||||
|
state.copy(
|
||||||
|
walletImages = state.walletImages.copy(thirdCardImage = action.artworkUri),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is OnboardingWalletAction.Done -> state.copy(step = OnboardingWalletStep.Done)
|
is OnboardingWalletAction.Done -> state.copy(step = OnboardingWalletStep.Done)
|
||||||
else -> state
|
else -> state
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ data class OnboardingWalletState(
|
||||||
val step: OnboardingWalletStep = OnboardingWalletStep.None,
|
val step: OnboardingWalletStep = OnboardingWalletStep.None,
|
||||||
val wallet2State: OnboardingWallet2State? = null,
|
val wallet2State: OnboardingWallet2State? = null,
|
||||||
val backupState: BackupState = BackupState(),
|
val backupState: BackupState = BackupState(),
|
||||||
val cardArtworkUri: Uri? = null,
|
val walletImages: WalletImages = WalletImages(),
|
||||||
val showConfetti: Boolean = false,
|
val showConfetti: Boolean = false,
|
||||||
val isRingOnboarding: Boolean = false,
|
val isRingOnboarding: Boolean = false,
|
||||||
) : StateType {
|
) : StateType {
|
||||||
|
|
@ -49,6 +49,12 @@ data class OnboardingWalletState(
|
||||||
private fun getWallet2Progress(): Int = wallet2State?.maxProgress ?: 0
|
private fun getWallet2Progress(): Int = wallet2State?.maxProgress ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class WalletImages(
|
||||||
|
val primaryCardImage: Uri? = null,
|
||||||
|
val secondCardImage: Uri? = null,
|
||||||
|
val thirdCardImage: Uri? = null,
|
||||||
|
)
|
||||||
|
|
||||||
data class OnboardingWallet2State(
|
data class OnboardingWallet2State(
|
||||||
val maxProgress: Int,
|
val maxProgress: Int,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
import com.tangem.core.analytics.models.Basic
|
import com.tangem.core.analytics.models.Basic
|
||||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||||
|
import com.tangem.datasource.utils.isNullOrEmpty
|
||||||
import com.tangem.domain.common.util.cardTypesResolver
|
import com.tangem.domain.common.util.cardTypesResolver
|
||||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||||
|
|
@ -186,30 +187,27 @@ class OnboardingWalletFragment :
|
||||||
when {
|
when {
|
||||||
state.wallet2State != null -> {
|
state.wallet2State != null -> {
|
||||||
seedPhraseStateHandler.newState(this, state, seedPhraseViewModel)
|
seedPhraseStateHandler.newState(this, state, seedPhraseViewModel)
|
||||||
state.cardArtworkUri?.let {
|
updateWalletImagesState(state.walletImages)
|
||||||
seedPhraseViewModel.setCardArtworkUri(it.toString())
|
|
||||||
if (state.isRingOnboarding) {
|
|
||||||
binding.imvFrontCard.load(R.drawable.img_ring_placeholder)
|
|
||||||
} else {
|
|
||||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard)
|
|
||||||
}
|
|
||||||
loadImageIntoImageView(it, binding.imvFirstBackupCard)
|
|
||||||
loadImageIntoImageView(it, binding.imvSecondBackupCard)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
if (state.isRingOnboarding) {
|
updateWalletImagesState(state.walletImages)
|
||||||
binding.imvFrontCard.load(R.drawable.img_ring_placeholder)
|
|
||||||
} else {
|
|
||||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard)
|
|
||||||
}
|
|
||||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard)
|
|
||||||
loadImageIntoImageView(state.cardArtworkUri, binding.imvSecondBackupCard)
|
|
||||||
handleOnboardingStep(state)
|
handleOnboardingStep(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateWalletImagesState(walletImages: WalletImages) {
|
||||||
|
if (!walletImages.primaryCardImage.isNullOrEmpty()) {
|
||||||
|
loadImageIntoImageView(walletImages.primaryCardImage, binding.imvFrontCard)
|
||||||
|
}
|
||||||
|
if (!walletImages.secondCardImage.isNullOrEmpty()) {
|
||||||
|
loadImageIntoImageView(walletImages.secondCardImage, binding.imvFirstBackupCard)
|
||||||
|
}
|
||||||
|
if (!walletImages.thirdCardImage.isNullOrEmpty()) {
|
||||||
|
loadImageIntoImageView(walletImages.thirdCardImage, binding.imvSecondBackupCard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadImageIntoImageView(uri: Uri?, view: ImageView) {
|
private fun loadImageIntoImageView(uri: Uri?, view: ImageView) {
|
||||||
view.load(uri) {
|
view.load(uri) {
|
||||||
placeholder(R.drawable.card_placeholder_black)
|
placeholder(R.drawable.card_placeholder_black)
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.components.BasicDialog
|
import com.tangem.core.ui.components.BasicDialog
|
||||||
import com.tangem.core.ui.components.DialogButton
|
import com.tangem.core.ui.components.DialogButtonUM
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
|
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -19,11 +19,11 @@ fun EnrollBiometricsDialogContent(dialog: EnrollBiometricsDialog) {
|
||||||
BasicDialog(
|
BasicDialog(
|
||||||
title = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_title),
|
title = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_title),
|
||||||
message = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_description),
|
message = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_description),
|
||||||
confirmButton = DialogButton(
|
confirmButton = DialogButtonUM(
|
||||||
title = stringResource(R.string.common_enable),
|
title = stringResource(R.string.common_enable),
|
||||||
onClick = dialog.onEnroll,
|
onClick = dialog.onEnroll,
|
||||||
),
|
),
|
||||||
dismissButton = DialogButton(
|
dismissButton = DialogButtonUM(
|
||||||
onClick = dialog.onCancel,
|
onClick = dialog.onCancel,
|
||||||
),
|
),
|
||||||
onDismissDialog = dialog.onCancel,
|
onDismissDialog = dialog.onCancel,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.common.routing.AppRouter
|
import com.tangem.common.routing.AppRouter
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
|
||||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
import com.tangem.domain.tokens.model.NetworkAddress
|
import com.tangem.domain.tokens.model.NetworkAddress
|
||||||
|
|
@ -48,12 +47,6 @@ object TradeCryptoMiddleware {
|
||||||
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
|
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
|
||||||
is TradeCryptoAction.Buy -> proceedBuyAction(state, action)
|
is TradeCryptoAction.Buy -> proceedBuyAction(state, action)
|
||||||
is TradeCryptoAction.Sell -> proceedSellAction(action)
|
is TradeCryptoAction.Sell -> proceedSellAction(action)
|
||||||
is TradeCryptoAction.Swap -> openSwap(currency = action.cryptoCurrency)
|
|
||||||
is TradeCryptoAction.Stake -> openStaking(
|
|
||||||
userWalletId = action.userWalletId,
|
|
||||||
cryptoCurrencyId = action.cryptoCurrencyId,
|
|
||||||
yield = action.yield,
|
|
||||||
)
|
|
||||||
is TradeCryptoAction.SendToken -> handleNewSendToken(action = action)
|
is TradeCryptoAction.SendToken -> handleNewSendToken(action = action)
|
||||||
is TradeCryptoAction.SendCoin -> handleNewSendCoin(action = action)
|
is TradeCryptoAction.SendCoin -> handleNewSendCoin(action = action)
|
||||||
}
|
}
|
||||||
|
|
@ -141,22 +134,6 @@ object TradeCryptoMiddleware {
|
||||||
)?.let { store.dispatchOpenUrl(it) }
|
)?.let { store.dispatchOpenUrl(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openSwap(currency: CryptoCurrency) {
|
|
||||||
store.dispatchNavigationAction { push(AppRoute.Swap(currency = currency)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openStaking(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, yield: Yield) {
|
|
||||||
store.dispatchNavigationAction {
|
|
||||||
push(
|
|
||||||
AppRoute.Staking(
|
|
||||||
userWalletId = userWalletId,
|
|
||||||
cryptoCurrencyId = cryptoCurrencyId,
|
|
||||||
yield = yield,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleNewSendToken(action: TradeCryptoAction.SendToken) {
|
private fun handleNewSendToken(action: TradeCryptoAction.SendToken) {
|
||||||
handleNewSend(
|
handleNewSend(
|
||||||
userWalletId = action.userWallet.walletId,
|
userWalletId = action.userWallet.walletId,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.tangem.core.ui.components.BasicDialog
|
import com.tangem.core.ui.components.BasicDialog
|
||||||
import com.tangem.core.ui.components.DialogButton
|
import com.tangem.core.ui.components.DialogButtonUM
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
|
|
@ -27,7 +27,7 @@ internal fun WarningDialog(warning: WarningModel?) {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
onDismissDialog = warning.onDismiss,
|
onDismissDialog = warning.onDismiss,
|
||||||
confirmButton = DialogButton(
|
confirmButton = DialogButtonUM(
|
||||||
title = stringResource(id = R.string.common_ok),
|
title = stringResource(id = R.string.common_ok),
|
||||||
onClick = warning.onDismiss,
|
onClick = warning.onDismiss,
|
||||||
),
|
),
|
||||||
|
|
@ -38,7 +38,7 @@ internal fun WarningDialog(warning: WarningModel?) {
|
||||||
title = stringResource(id = R.string.common_attention),
|
title = stringResource(id = R.string.common_attention),
|
||||||
message = stringResource(id = R.string.key_invalidated_warning_description),
|
message = stringResource(id = R.string.key_invalidated_warning_description),
|
||||||
onDismissDialog = warning.onDismiss,
|
onDismissDialog = warning.onDismiss,
|
||||||
confirmButton = DialogButton(
|
confirmButton = DialogButtonUM(
|
||||||
title = stringResource(id = R.string.common_ok),
|
title = stringResource(id = R.string.common_ok),
|
||||||
onClick = warning.onDismiss,
|
onClick = warning.onDismiss,
|
||||||
),
|
),
|
||||||
|
|
@ -50,7 +50,7 @@ internal fun WarningDialog(warning: WarningModel?) {
|
||||||
message = stringResource(id = R.string.biometric_unavailable_warning),
|
message = stringResource(id = R.string.biometric_unavailable_warning),
|
||||||
onDismissDialog = warning.onDismiss,
|
onDismissDialog = warning.onDismiss,
|
||||||
isDismissable = false,
|
isDismissable = false,
|
||||||
confirmButton = DialogButton(
|
confirmButton = DialogButtonUM(
|
||||||
title = stringResource(id = R.string.common_ok),
|
title = stringResource(id = R.string.common_ok),
|
||||||
onClick = warning.onDismiss,
|
onClick = warning.onDismiss,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import com.tangem.features.details.component.DetailsComponent
|
||||||
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
||||||
import com.tangem.features.managetokens.ManageTokensToggles
|
import com.tangem.features.managetokens.ManageTokensToggles
|
||||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||||
|
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||||
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
||||||
import com.tangem.features.send.api.navigation.SendRouter
|
import com.tangem.features.send.api.navigation.SendRouter
|
||||||
|
|
@ -50,6 +51,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
private val walletSettingsComponentFactory: WalletSettingsComponent.Factory,
|
private val walletSettingsComponentFactory: WalletSettingsComponent.Factory,
|
||||||
private val disclaimerComponentFactory: DisclaimerComponent.Factory,
|
private val disclaimerComponentFactory: DisclaimerComponent.Factory,
|
||||||
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
|
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
|
||||||
|
private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory,
|
||||||
private val sendRouter: SendRouter,
|
private val sendRouter: SendRouter,
|
||||||
private val tokenDetailsRouter: TokenDetailsRouter,
|
private val tokenDetailsRouter: TokenDetailsRouter,
|
||||||
private val walletRouter: WalletRouter,
|
private val walletRouter: WalletRouter,
|
||||||
|
|
@ -126,13 +128,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
if (manageTokensToggles.isFeatureEnabled) {
|
if (manageTokensToggles.isFeatureEnabled) {
|
||||||
route.asComponentChild(
|
route.asComponentChild(
|
||||||
contextProvider = contextProvider(route, contextFactory),
|
contextProvider = contextProvider(route, contextFactory),
|
||||||
params = ManageTokensComponent.Params(
|
params = ManageTokensComponent.Params(route.userWalletId),
|
||||||
mode = if (route.readOnlyContent) {
|
|
||||||
ManageTokensComponent.Mode.READ_ONLY
|
|
||||||
} else {
|
|
||||||
ManageTokensComponent.Mode.MANAGE
|
|
||||||
},
|
|
||||||
),
|
|
||||||
componentFactory = manageTokensComponentFactory,
|
componentFactory = manageTokensComponentFactory,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -191,6 +187,23 @@ internal class ChildFactory @Inject constructor(
|
||||||
componentFactory = walletSettingsComponentFactory,
|
componentFactory = walletSettingsComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is AppRoute.MarketsTokenDetails -> {
|
||||||
|
route.asComponentChild(
|
||||||
|
contextProvider = contextProvider(route, contextFactory),
|
||||||
|
params = MarketsTokenDetailsComponent.Params(
|
||||||
|
token = route.token,
|
||||||
|
appCurrency = route.appCurrency,
|
||||||
|
showPortfolio = route.showPortfolio,
|
||||||
|
analyticsParams = route.analyticsParams?.let {
|
||||||
|
MarketsTokenDetailsComponent.AnalyticsParams(
|
||||||
|
blockchain = it.blockchain,
|
||||||
|
source = it.source,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
componentFactory = marketsTokenDetailsComponentFactory,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/llTotalContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="64dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/llTotal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvTotal"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="start"
|
|
||||||
android:text="@string/send_total_label"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvTotalValue"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="usd" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvWillBeSentValue"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:gravity="end"
|
|
||||||
android:textColor="@color/text_tertiary"
|
|
||||||
tools:text="123.29837729 ADA 39487593.109342039402938049 will be sent" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/flTotalTokenCrypto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvTotalTokenCrypto"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="start"
|
|
||||||
android:text="@string/send_total_label"
|
|
||||||
android:textColor="@color/text_tertiary"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvTotalTokenCryptoValue"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
android:textColor="@color/text_tertiary"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="usd" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
package com.tangem.tap.domain.card
|
package com.tangem.tap.domain.card
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.blockchain.common.FeePaidCurrency
|
||||||
import com.tangem.blockchain.common.Token
|
import com.tangem.blockchain.common.Token
|
||||||
|
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||||
|
import com.tangem.data.common.currency.getNetworkDerivationPath
|
||||||
|
import com.tangem.data.common.currency.getNetworkStandardType
|
||||||
import com.tangem.domain.common.util.derivationStyleProvider
|
import com.tangem.domain.common.util.derivationStyleProvider
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
|
|
@ -40,11 +44,23 @@ internal class CryptoCurrenciesMocks(private val scanResponse: ScanResponse) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createCoin(blockchain: Blockchain): CryptoCurrency {
|
private fun createCoin(blockchain: Blockchain): CryptoCurrency {
|
||||||
return factory.createCoin(
|
val network = Network(
|
||||||
blockchain = blockchain,
|
id = Network.ID(blockchain.id),
|
||||||
extraDerivationPath = null,
|
backendId = blockchain.toNetworkId(),
|
||||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
name = blockchain.getNetworkName(),
|
||||||
)!!
|
isTestnet = blockchain.isTestnet(),
|
||||||
|
derivationPath = getNetworkDerivationPath(
|
||||||
|
blockchain,
|
||||||
|
extraDerivationPath = null,
|
||||||
|
scanResponse.derivationStyleProvider,
|
||||||
|
),
|
||||||
|
currencySymbol = blockchain.currency,
|
||||||
|
standardType = getNetworkStandardType(blockchain),
|
||||||
|
hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource,
|
||||||
|
canHandleTokens = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
return factory.createCoin(network = network)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Impossible to create custom token by CryptoCurrencyFactory because it works with URI under the hood
|
// Impossible to create custom token by CryptoCurrencyFactory because it works with URI under the hood
|
||||||
|
|
@ -68,6 +84,7 @@ internal class CryptoCurrenciesMocks(private val scanResponse: ScanResponse) {
|
||||||
isTestnet = false,
|
isTestnet = false,
|
||||||
standardType = Network.StandardType.ERC20,
|
standardType = Network.StandardType.ERC20,
|
||||||
hasFiatFeeRate = true,
|
hasFiatFeeRate = true,
|
||||||
|
canHandleTokens = true,
|
||||||
),
|
),
|
||||||
name = "NEVER-MIND",
|
name = "NEVER-MIND",
|
||||||
symbol = "NEVER-MIND",
|
symbol = "NEVER-MIND",
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ dependencies {
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
implementation(projects.domain.staking.models)
|
implementation(projects.domain.staking.models)
|
||||||
|
implementation(projects.domain.markets.models)
|
||||||
|
implementation(projects.domain.appCurrency.models)
|
||||||
|
|
||||||
/* Libs - Other */
|
/* Libs - Other */
|
||||||
api(deps.kotlin.serialization)
|
api(deps.kotlin.serialization)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import com.tangem.common.routing.bundle.RouteBundleParams
|
||||||
import com.tangem.common.routing.bundle.bundle
|
import com.tangem.common.routing.bundle.bundle
|
||||||
import com.tangem.common.routing.entity.SerializableIntent
|
import com.tangem.common.routing.entity.SerializableIntent
|
||||||
import com.tangem.core.decompose.navigation.Route
|
import com.tangem.core.decompose.navigation.Route
|
||||||
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.markets.TokenMarketParams
|
||||||
import com.tangem.domain.qrscanning.models.SourceType
|
import com.tangem.domain.qrscanning.models.SourceType
|
||||||
import com.tangem.domain.staking.model.stakekit.Yield
|
import com.tangem.domain.staking.model.stakekit.Yield
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
|
|
@ -177,8 +179,8 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ManageTokens(
|
data class ManageTokens(
|
||||||
val readOnlyContent: Boolean,
|
val userWalletId: UserWalletId? = null,
|
||||||
) : AppRoute(path = "/manage_tokens/$readOnlyContent"), RouteBundleParams {
|
) : AppRoute(path = "/manage_tokens/$userWalletId"), RouteBundleParams {
|
||||||
override fun getBundle(): Bundle = bundle(serializer())
|
override fun getBundle(): Bundle = bundle(serializer())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,12 +219,14 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Swap(
|
data class Swap(
|
||||||
val currency: CryptoCurrency,
|
val currency: CryptoCurrency,
|
||||||
) : AppRoute(path = "/swap/${currency.id.value}"), RouteBundleParams {
|
val userWalletId: UserWalletId,
|
||||||
|
) : AppRoute(path = "/swap/${currency.id.value}/${userWalletId.stringValue}"), RouteBundleParams {
|
||||||
|
|
||||||
override fun getBundle(): Bundle = bundle(serializer())
|
override fun getBundle(): Bundle = bundle(serializer())
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CURRENCY_BUNDLE_KEY = "currency"
|
const val CURRENCY_BUNDLE_KEY = "currency"
|
||||||
|
const val USER_WALLET_ID_KEY = "userWalletId"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,4 +266,19 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
data class WalletSettings(
|
data class WalletSettings(
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
) : AppRoute(path = "/wallet_settings/${userWalletId.stringValue}")
|
) : AppRoute(path = "/wallet_settings/${userWalletId.stringValue}")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MarketsTokenDetails(
|
||||||
|
val token: TokenMarketParams,
|
||||||
|
val appCurrency: AppCurrency,
|
||||||
|
val showPortfolio: Boolean,
|
||||||
|
val analyticsParams: AnalyticsParams? = null,
|
||||||
|
) : AppRoute(path = "/markets_token_details/${token.id}/$showPortfolio") {
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AnalyticsParams(
|
||||||
|
val blockchain: String?,
|
||||||
|
val source: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.tangem.common.routing.utils
|
||||||
|
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
|
import com.tangem.common.routing.AppRouter
|
||||||
|
import com.tangem.core.decompose.navigation.Route
|
||||||
|
import com.tangem.core.decompose.navigation.Router
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary solution to convert [AppRouter] to [Router].
|
||||||
|
|
||||||
|
* (through manual ComponentContext creation).
|
||||||
|
*
|
||||||
|
* **Will be removed when all screens will be migrated to Decompose.**
|
||||||
|
*
|
||||||
|
* @return [Router] that wraps [AppRouter].
|
||||||
|
*/
|
||||||
|
fun AppRouter.asRouter(): Router {
|
||||||
|
return RouterProxy(appRouter = this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RouterProxy(
|
||||||
|
private val appRouter: AppRouter,
|
||||||
|
) : Router {
|
||||||
|
override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||||
|
if (route is AppRoute) {
|
||||||
|
appRouter.push(route, onComplete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceAll(vararg routes: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||||
|
routes.filterIsInstance<AppRoute>().let {
|
||||||
|
appRouter.replaceAll(*it.toTypedArray(), onComplete = onComplete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||||
|
appRouter.pop(onComplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||||
|
if (route is AppRoute) {
|
||||||
|
appRouter.popTo(route, onComplete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun popTo(routeClass: KClass<out Route>, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||||
|
appRouter.popTo(routeClass as KClass<out AppRoute>, onComplete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -125,6 +125,8 @@ fun MarketChart(
|
||||||
// Sometimes the chart is not drawn correctly (ex. in LazyLayout), so we need to force the redraw
|
// Sometimes the chart is not drawn correctly (ex. in LazyLayout), so we need to force the redraw
|
||||||
.drawBehind {
|
.drawBehind {
|
||||||
state.markerFraction
|
state.markerFraction
|
||||||
|
state.chartColor
|
||||||
|
state.markerHighlightRightSide
|
||||||
},
|
},
|
||||||
chart = chart,
|
chart = chart,
|
||||||
modelProducer = state.modelProducer,
|
modelProducer = state.modelProducer,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.tangem.common.ui.charts.state
|
||||||
|
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
fun MarketChartData.Data.sorted(): MarketChartData.Data {
|
||||||
|
val points = this.x.zip(this.y).sortedBy { it.first }
|
||||||
|
val (x, y) = points.unzip()
|
||||||
|
|
||||||
|
return MarketChartData.Data(
|
||||||
|
x = x.toImmutableList(),
|
||||||
|
y = y.toImmutableList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ dependencies {
|
||||||
implementation(deps.compose.ui.tooling)
|
implementation(deps.compose.ui.tooling)
|
||||||
implementation(deps.compose.navigation)
|
implementation(deps.compose.navigation)
|
||||||
implementation(deps.compose.navigation.hilt)
|
implementation(deps.compose.navigation.hilt)
|
||||||
|
implementation(deps.compose.coil)
|
||||||
|
|
||||||
/** Deps */
|
/** Deps */
|
||||||
implementation(deps.kotlin.immutable.collections)
|
implementation(deps.kotlin.immutable.collections)
|
||||||
|
|
@ -27,9 +28,14 @@ dependencies {
|
||||||
implementation(projects.core.utils)
|
implementation(projects.core.utils)
|
||||||
|
|
||||||
/** Project - Domain */
|
/** Project - Domain */
|
||||||
implementation(projects.domain.tokens.models)
|
|
||||||
implementation(projects.domain.wallets.models)
|
|
||||||
implementation(projects.domain.appCurrency.models)
|
implementation(projects.domain.appCurrency.models)
|
||||||
|
implementation(projects.domain.legacy)
|
||||||
|
implementation(projects.domain.staking.models)
|
||||||
|
implementation(projects.domain.tokens.models)
|
||||||
|
implementation(projects.domain.transaction.models)
|
||||||
|
implementation(projects.domain.wallets.models)
|
||||||
|
|
||||||
|
implementation(deps.tangem.card.core)
|
||||||
implementation(deps.tangem.blockchain) {
|
implementation(deps.tangem.blockchain) {
|
||||||
exclude(module = "joda-time")
|
exclude(module = "joda-time")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.tangem.common.ui.alerts
|
||||||
|
|
||||||
|
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
|
||||||
|
import com.tangem.common.ui.alerts.models.AlertTransactionErrorUM
|
||||||
|
import com.tangem.common.ui.alerts.models.AlertUM
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
|
import com.tangem.domain.transaction.error.SendTransactionError
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
|
class SendTransactionAlertConverter(
|
||||||
|
private val popBackStack: () -> Unit,
|
||||||
|
private val onFailedTxEmailClick: (String) -> Unit,
|
||||||
|
) : Converter<SendTransactionError, AlertUM?> {
|
||||||
|
override fun convert(value: SendTransactionError): AlertUM? {
|
||||||
|
return when (value) {
|
||||||
|
is SendTransactionError.DemoCardError -> AlertDemoModeUM(
|
||||||
|
onConfirmClick = popBackStack,
|
||||||
|
)
|
||||||
|
is SendTransactionError.TangemSdkError -> AlertTransactionErrorUM(
|
||||||
|
code = value.code.toString(),
|
||||||
|
cause = null,
|
||||||
|
causeTextReference = resourceReference(value.messageRes, wrappedList(value.args)),
|
||||||
|
onConfirmClick = { onFailedTxEmailClick(value.code.toString()) },
|
||||||
|
)
|
||||||
|
is SendTransactionError.BlockchainSdkError -> AlertTransactionErrorUM(
|
||||||
|
code = value.code.toString(),
|
||||||
|
cause = value.message,
|
||||||
|
onConfirmClick = { onFailedTxEmailClick("${value.code}: ${value.message.orEmpty()}") },
|
||||||
|
)
|
||||||
|
is SendTransactionError.DataError -> AlertTransactionErrorUM(
|
||||||
|
code = "",
|
||||||
|
cause = value.message,
|
||||||
|
onConfirmClick = { onFailedTxEmailClick(value.message.orEmpty()) },
|
||||||
|
)
|
||||||
|
is SendTransactionError.NetworkError -> AlertTransactionErrorUM(
|
||||||
|
code = value.code.orEmpty(),
|
||||||
|
cause = value.message.orEmpty(),
|
||||||
|
onConfirmClick = { onFailedTxEmailClick(value.message.orEmpty()) },
|
||||||
|
)
|
||||||
|
is SendTransactionError.UnknownError -> AlertTransactionErrorUM(
|
||||||
|
code = "",
|
||||||
|
cause = value.ex?.localizedMessage,
|
||||||
|
onConfirmClick = { onFailedTxEmailClick(value.ex?.localizedMessage.orEmpty()) },
|
||||||
|
)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.tangem.common.ui.alerts.models
|
||||||
|
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
|
||||||
|
data class AlertDemoModeUM(
|
||||||
|
override val onConfirmClick: () -> Unit,
|
||||||
|
) : AlertUM {
|
||||||
|
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
|
||||||
|
override val title: TextReference = resourceReference(id = R.string.warning_demo_mode_title)
|
||||||
|
override val message: TextReference = resourceReference(id = R.string.warning_demo_mode_message)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.common.ui.alerts.models
|
||||||
|
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
|
|
||||||
|
data class AlertTransactionErrorUM(
|
||||||
|
val code: String,
|
||||||
|
val cause: String?,
|
||||||
|
val causeTextReference: TextReference? = null,
|
||||||
|
override val onConfirmClick: (() -> Unit)? = null,
|
||||||
|
) : AlertUM {
|
||||||
|
override val title: TextReference = resourceReference(id = R.string.send_alert_transaction_failed_title)
|
||||||
|
override val message: TextReference = resourceReference(
|
||||||
|
id = R.string.send_alert_transaction_failed_text,
|
||||||
|
formatArgs = wrappedList(causeTextReference ?: cause.orEmpty(), code),
|
||||||
|
)
|
||||||
|
override val confirmButtonText: TextReference =
|
||||||
|
resourceReference(id = R.string.common_support)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.tangem.common.ui.alerts.models
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
interface AlertUM {
|
||||||
|
val title: TextReference?
|
||||||
|
val message: TextReference
|
||||||
|
val confirmButtonText: TextReference
|
||||||
|
val onConfirmClick: (() -> Unit)?
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.tangem.common.ui.amountScreen
|
package com.tangem.common.ui.amountScreen
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -20,24 +19,28 @@ import com.tangem.core.ui.res.TangemThemePreview
|
||||||
/**
|
/**
|
||||||
* Amount screen with field
|
* Amount screen with field
|
||||||
* @param amountState amount state
|
* @param amountState amount state
|
||||||
* @param isBalanceHiding flag hidden balances
|
* @param isBalanceHidden flag hidden balances
|
||||||
* @param clickIntents amount screen clicks
|
* @param clickIntents amount screen clicks
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AmountScreenContent(amountState: AmountState, isBalanceHiding: Boolean, clickIntents: AmountScreenClickIntents) {
|
fun AmountScreenContent(
|
||||||
|
amountState: AmountState,
|
||||||
|
isBalanceHidden: Boolean,
|
||||||
|
clickIntents: AmountScreenClickIntents,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
if (amountState !is AmountState.Data) return
|
if (amountState !is AmountState.Data) return
|
||||||
|
|
||||||
// Do not put fillMaxSize() in here
|
// Do not put fillMaxSize() in here
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.background(TangemTheme.colors.background.tertiary)
|
|
||||||
.padding(
|
.padding(
|
||||||
start = TangemTheme.dimens.spacing16,
|
start = TangemTheme.dimens.spacing16,
|
||||||
end = TangemTheme.dimens.spacing16,
|
end = TangemTheme.dimens.spacing16,
|
||||||
bottom = TangemTheme.dimens.spacing16,
|
bottom = TangemTheme.dimens.spacing16,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
amountField(amountState = amountState, isBalanceHiding = isBalanceHiding)
|
amountField(amountState = amountState, isBalanceHidden = isBalanceHidden)
|
||||||
buttons(
|
buttons(
|
||||||
segmentedButtonConfig = amountState.segmentedButtonConfig,
|
segmentedButtonConfig = amountState.segmentedButtonConfig,
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
|
|
@ -57,7 +60,7 @@ private fun SendAmountContentPreview(
|
||||||
TangemThemePreview {
|
TangemThemePreview {
|
||||||
AmountScreenContent(
|
AmountScreenContent(
|
||||||
amountState = amountState,
|
amountState = amountState,
|
||||||
isBalanceHiding = false,
|
isBalanceHidden = false,
|
||||||
clickIntents = AmountScreenClickIntentsStub,
|
clickIntents = AmountScreenClickIntentsStub,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.common.ui.amountScreen.converters.field
|
package com.tangem.common.ui.amountScreen.converters.field
|
||||||
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import com.tangem.common.ui.R
|
import com.tangem.common.ui.R
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||||
|
|
@ -8,6 +9,7 @@ import com.tangem.common.ui.amountScreen.utils.checkExceedBalance
|
||||||
import com.tangem.common.ui.amountScreen.utils.getCryptoValue
|
import com.tangem.common.ui.amountScreen.utils.getCryptoValue
|
||||||
import com.tangem.common.ui.amountScreen.utils.getFiatValue
|
import com.tangem.common.ui.amountScreen.utils.getFiatValue
|
||||||
import com.tangem.common.ui.amountScreen.utils.getKeyboardAction
|
import com.tangem.common.ui.amountScreen.utils.getKeyboardAction
|
||||||
|
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.utils.parseToBigDecimal
|
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||||
|
|
@ -61,7 +63,8 @@ class AmountFieldChangeTransformer(
|
||||||
value = cryptoValue,
|
value = cryptoValue,
|
||||||
fiatValue = fiatValue,
|
fiatValue = fiatValue,
|
||||||
isError = isExceedBalance,
|
isError = isExceedBalance,
|
||||||
error = resourceReference(R.string.send_validation_amount_exceeds_balance),
|
error = resourceReference(R.string.send_validation_amount_exceeds_balance).takeIf { isExceedBalance }
|
||||||
|
?: TextReference.EMPTY,
|
||||||
cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue),
|
cryptoAmount = amountTextField.cryptoAmount.copy(value = decimalCryptoValue),
|
||||||
fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue),
|
fiatAmount = amountTextField.fiatAmount.copy(value = decimalFiatValue),
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
|
|
@ -81,6 +84,10 @@ class AmountFieldChangeTransformer(
|
||||||
cryptoAmount = amountTextField.cryptoAmount.copy(value = BigDecimal.ZERO),
|
cryptoAmount = amountTextField.cryptoAmount.copy(value = BigDecimal.ZERO),
|
||||||
fiatAmount = amountTextField.fiatAmount.copy(value = BigDecimal.ZERO),
|
fiatAmount = amountTextField.fiatAmount.copy(value = BigDecimal.ZERO),
|
||||||
isError = false,
|
isError = false,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
imeAction = ImeAction.None,
|
||||||
|
keyboardType = KeyboardType.Number,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import com.tangem.common.ui.R
|
|
||||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
|
@ -62,7 +61,8 @@ class AmountFieldConverter(
|
||||||
cryptoAmount = cryptoAmount,
|
cryptoAmount = cryptoAmount,
|
||||||
fiatAmount = getAppCurrencyAmount(fiatDecimal, appCurrencyProvider()),
|
fiatAmount = getAppCurrencyAmount(fiatDecimal, appCurrencyProvider()),
|
||||||
isError = false,
|
isError = false,
|
||||||
error = TextReference.Res(R.string.send_validation_amount_exceeds_balance),
|
isWarning = false,
|
||||||
|
error = TextReference.EMPTY,
|
||||||
isFiatUnavailable = fiatRate == null,
|
isFiatUnavailable = fiatRate == null,
|
||||||
isValuePasted = false,
|
isValuePasted = false,
|
||||||
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
||||||
|
|
|
||||||
|
|
@ -35,5 +35,6 @@ data class AmountFieldModel(
|
||||||
val isValuePasted: Boolean,
|
val isValuePasted: Boolean,
|
||||||
val onValuePastedTriggerDismiss: () -> Unit,
|
val onValuePastedTriggerDismiss: () -> Unit,
|
||||||
val isError: Boolean,
|
val isError: Boolean,
|
||||||
|
val isWarning: Boolean,
|
||||||
val error: TextReference,
|
val error: TextReference,
|
||||||
)
|
)
|
||||||
|
|
@ -53,6 +53,7 @@ object AmountStatePreviewData {
|
||||||
fiatValue = "123.123",
|
fiatValue = "123.123",
|
||||||
isFiatUnavailable = false,
|
isFiatUnavailable = false,
|
||||||
isError = false,
|
isError = false,
|
||||||
|
isWarning = false,
|
||||||
error = TextReference.EMPTY,
|
error = TextReference.EMPTY,
|
||||||
isValuePasted = false,
|
isValuePasted = false,
|
||||||
onValuePastedTriggerDismiss = {},
|
onValuePastedTriggerDismiss = {},
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.requiredHeightIn
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
|
@ -112,6 +115,7 @@ private fun AmountSecondary(amountField: AmountFieldModel, appCurrencyCode: Stri
|
||||||
)
|
)
|
||||||
AmountFieldError(
|
AmountFieldError(
|
||||||
isError = amountField.isError,
|
isError = amountField.isError,
|
||||||
|
isWarning = amountField.isWarning,
|
||||||
error = amountField.error,
|
error = amountField.error,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(BottomCenter)
|
.align(BottomCenter)
|
||||||
|
|
@ -124,17 +128,24 @@ private fun AmountSecondary(amountField: AmountFieldModel, appCurrencyCode: Stri
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AmountFieldError(isError: Boolean, error: TextReference, modifier: Modifier = Modifier) {
|
private fun AmountFieldError(
|
||||||
|
isError: Boolean,
|
||||||
|
isWarning: Boolean,
|
||||||
|
error: TextReference,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = isError,
|
visible = isError || isWarning,
|
||||||
enter = fadeIn(),
|
enter = fadeIn(),
|
||||||
exit = fadeOut(),
|
exit = fadeOut(),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
|
val errorText = remember(this) { error }
|
||||||
|
val color = if (isError) TangemTheme.colors.text.warning else TangemTheme.colors.text.attention
|
||||||
Text(
|
Text(
|
||||||
text = error.resolveReference(),
|
text = errorText.resolveReference(),
|
||||||
style = TangemTheme.typography.caption2,
|
style = TangemTheme.typography.caption2,
|
||||||
color = TangemTheme.colors.text.warning,
|
color = color,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,15 @@ import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||||
|
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.utils.StringsSigns.STARS
|
|
||||||
|
|
||||||
private const val AMOUNT_FIELD_KEY = "amountFieldKey"
|
private const val AMOUNT_FIELD_KEY = "amountFieldKey"
|
||||||
|
|
||||||
internal fun LazyListScope.amountField(
|
internal fun LazyListScope.amountField(
|
||||||
amountState: AmountState.Data,
|
amountState: AmountState.Data,
|
||||||
isBalanceHiding: Boolean,
|
isBalanceHidden: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
item(key = AMOUNT_FIELD_KEY) {
|
item(key = AMOUNT_FIELD_KEY) {
|
||||||
|
|
@ -41,7 +41,7 @@ internal fun LazyListScope.amountField(
|
||||||
.padding(top = TangemTheme.dimens.spacing14),
|
.padding(top = TangemTheme.dimens.spacing14),
|
||||||
)
|
)
|
||||||
|
|
||||||
val balance = if (isBalanceHiding) STARS else amountState.walletBalance.resolveReference()
|
val balance = amountState.walletBalance.orMaskWithStars(isBalanceHidden).resolveReference()
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = balance,
|
targetState = balance,
|
||||||
label = "Hide Balance Animation",
|
label = "Hide Balance Animation",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -59,7 +58,7 @@ fun GiveTxPermissionBottomSheet(config: TangemBottomSheetConfig) {
|
||||||
BasicDialog(
|
BasicDialog(
|
||||||
message = content.data.dialogText.resolveReference(),
|
message = content.data.dialogText.resolveReference(),
|
||||||
title = stringResource(id = R.string.common_approve),
|
title = stringResource(id = R.string.common_approve),
|
||||||
confirmButton = DialogButton { isPermissionAlertShow = false },
|
confirmButton = DialogButtonUM { isPermissionAlertShow = false },
|
||||||
onDismissDialog = {},
|
onDismissDialog = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +165,7 @@ private fun AmountItem(
|
||||||
.clickable(
|
.clickable(
|
||||||
enabled = onChangeApproveType != null,
|
enabled = onChangeApproveType != null,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
indication = rememberRipple(),
|
indication = ripple(),
|
||||||
onClick = { isExpandSelector = true },
|
onClick = { isExpandSelector = true },
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,32 @@ package com.tangem.common.ui.navigationButtons
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.*
|
||||||
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.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import com.tangem.common.ui.navigationButtons.preview.NavigationButtonsPreview
|
import com.tangem.common.ui.navigationButtons.preview.NavigationButtonsPreview
|
||||||
|
import com.tangem.core.ui.components.Keyboard
|
||||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||||
|
import com.tangem.core.ui.components.keyboardAsState
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.isNullOrEmpty
|
||||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
@ -30,39 +35,34 @@ import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NavigationButtonsBlock(buttonState: NavigationButtonsState, modifier: Modifier = Modifier) {
|
fun NavigationButtonsBlock(
|
||||||
|
buttonState: NavigationButtonsState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
footerText: TextReference? = null,
|
||||||
|
) {
|
||||||
val state = buttonState as? NavigationButtonsState.Data
|
val state = buttonState as? NavigationButtonsState.Data
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
|
InfoText(footerText)
|
||||||
ExtraButtons(state?.extraButtons, state?.txUrl)
|
ExtraButtons(state?.extraButtons, state?.txUrl)
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||||
) {
|
) {
|
||||||
PreviousButton(state?.prevButton)
|
PreviousButton(state?.prevButton)
|
||||||
PrimaryButton(state?.primaryButton, modifier = Modifier.weight(1f))
|
NavigationPrimaryButton(state?.primaryButton, modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
|
|
||||||
SecondaryButton(state?.secondaryButton)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PrimaryButton(primaryButton: NavigationButton?, modifier: Modifier = Modifier) {
|
fun NavigationPrimaryButton(primaryButton: NavigationButton?, modifier: Modifier = Modifier) {
|
||||||
|
val wrappedButton by rememberNavigationButton(primaryButton)
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = primaryButton,
|
targetState = wrappedButton,
|
||||||
transitionSpec = {
|
transitionSpec = { navigationButtonsTransition() },
|
||||||
val isPrimaryToHide = targetState != null && initialState == null
|
|
||||||
val isPrimaryWasVisible = targetState == null && initialState != null
|
|
||||||
if (isPrimaryToHide || isPrimaryWasVisible) {
|
|
||||||
slideInVertically(initialOffsetY = { it / 2 }).plus(fadeIn())
|
|
||||||
.togetherWith(slideOutVertically(targetOffsetY = { it / 2 }).plus(fadeOut()))
|
|
||||||
} else {
|
|
||||||
fadeIn().togetherWith(fadeOut())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
label = "Animate show primary button",
|
label = "Animate show primary button",
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
|
@ -88,43 +88,6 @@ private fun PrimaryButton(primaryButton: NavigationButton?, modifier: Modifier =
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SecondaryButton(secondaryButton: NavigationButton?) {
|
|
||||||
AnimatedContent(
|
|
||||||
targetState = secondaryButton,
|
|
||||||
transitionSpec = {
|
|
||||||
val isPrimaryToHide = targetState != null && initialState == null
|
|
||||||
val isPrimaryWasVisible = targetState == null && initialState != null
|
|
||||||
if (isPrimaryToHide || isPrimaryWasVisible) {
|
|
||||||
slideInVertically(initialOffsetY = { it / 2 }).plus(fadeIn())
|
|
||||||
.togetherWith(slideOutVertically(targetOffsetY = { it / 2 }).plus(fadeOut()))
|
|
||||||
} else {
|
|
||||||
fadeIn().togetherWith(fadeOut())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
label = "Animate show secondary button",
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
) { button ->
|
|
||||||
if (button != null && button.textReference != TextReference.EMPTY) {
|
|
||||||
val icon = button.iconRes?.let { TangemButtonIconPosition.End(iconResId = it) }
|
|
||||||
?: TangemButtonIconPosition.None
|
|
||||||
|
|
||||||
TangemButton(
|
|
||||||
text = button.textReference.resolveReference(),
|
|
||||||
enabled = button.isEnabled,
|
|
||||||
onClick = button.onClick,
|
|
||||||
icon = icon,
|
|
||||||
showProgress = button.showProgress,
|
|
||||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
|
||||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing12),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Spacer(modifier = Modifier.fillMaxWidth())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PreviousButton(prevButton: NavigationButton?) {
|
private fun PreviousButton(prevButton: NavigationButton?) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
|
|
@ -182,6 +145,61 @@ private fun ExtraButtons(extraButtons: ImmutableList<NavigationButton>?, txUrl:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InfoText(footerText: TextReference?, modifier: Modifier = Modifier) {
|
||||||
|
var isVisibleProxy by remember { mutableStateOf(!footerText.isNullOrEmpty()) }
|
||||||
|
val keyboard by keyboardAsState()
|
||||||
|
|
||||||
|
// the text should appear when the keyboard is closed
|
||||||
|
LaunchedEffect(footerText, keyboard) {
|
||||||
|
if (footerText.isNullOrEmpty() && keyboard is Keyboard.Opened) {
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
isVisibleProxy = !footerText.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isVisibleProxy,
|
||||||
|
modifier = modifier,
|
||||||
|
enter = slideInVertically() + fadeIn(),
|
||||||
|
exit = fadeOut(tween(durationMillis = 300)),
|
||||||
|
label = "Animate footer text appearance",
|
||||||
|
) {
|
||||||
|
val text = remember(this) { requireNotNull(footerText) }
|
||||||
|
Text(
|
||||||
|
text = text.resolveReference(),
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = TangemTheme.dimens.spacing12),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun rememberNavigationButton(button: NavigationButton?): MutableState<NavigationButton?> {
|
||||||
|
return remember(
|
||||||
|
button?.iconRes,
|
||||||
|
button?.isIconVisible,
|
||||||
|
button?.isEnabled,
|
||||||
|
button?.showProgress,
|
||||||
|
button?.textReference,
|
||||||
|
) { mutableStateOf(button) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> AnimatedContentTransitionScope<T>.navigationButtonsTransition(): ContentTransform {
|
||||||
|
val isPrimaryToHide = targetState != null && initialState == null
|
||||||
|
val isPrimaryWasVisible = targetState == null && initialState != null
|
||||||
|
return if (isPrimaryToHide || isPrimaryWasVisible) {
|
||||||
|
slideInVertically(initialOffsetY = { it / 2 }).plus(fadeIn())
|
||||||
|
.togetherWith(slideOutVertically(targetOffsetY = { it / 2 }).plus(fadeOut()))
|
||||||
|
} else {
|
||||||
|
fadeIn().togetherWith(fadeOut())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// region Preview
|
// region Preview
|
||||||
@Preview(showBackground = true, widthDp = 360)
|
@Preview(showBackground = true, widthDp = 360)
|
||||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ sealed class NavigationButtonsState {
|
||||||
data class Data(
|
data class Data(
|
||||||
val primaryButton: NavigationButton,
|
val primaryButton: NavigationButton,
|
||||||
val prevButton: NavigationButton?,
|
val prevButton: NavigationButton?,
|
||||||
val secondaryButton: NavigationButton?,
|
|
||||||
val extraButtons: ImmutableList<NavigationButton>,
|
val extraButtons: ImmutableList<NavigationButton>,
|
||||||
val txUrl: String? = null,
|
val txUrl: String? = null,
|
||||||
|
val onTextClick: (String) -> Unit,
|
||||||
) : NavigationButtonsState()
|
) : NavigationButtonsState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,6 @@ internal object NavigationButtonsPreview {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
private val next = NavigationButton(
|
|
||||||
textReference = resourceReference(R.string.common_next),
|
|
||||||
isSecondary = false,
|
|
||||||
isIconVisible = false,
|
|
||||||
showProgress = false,
|
|
||||||
isEnabled = true,
|
|
||||||
onClick = {},
|
|
||||||
)
|
|
||||||
private val prev = NavigationButton(
|
private val prev = NavigationButton(
|
||||||
textReference = TextReference.EMPTY,
|
textReference = TextReference.EMPTY,
|
||||||
iconRes = R.drawable.ic_back_24,
|
iconRes = R.drawable.ic_back_24,
|
||||||
|
|
@ -60,8 +52,8 @@ internal object NavigationButtonsPreview {
|
||||||
val allButtons = NavigationButtonsState.Data(
|
val allButtons = NavigationButtonsState.Data(
|
||||||
primaryButton = finished,
|
primaryButton = finished,
|
||||||
prevButton = prev,
|
prevButton = prev,
|
||||||
secondaryButton = next,
|
|
||||||
extraButtons = extraButtons,
|
extraButtons = extraButtons,
|
||||||
txUrl = "https://tangem.com",
|
txUrl = "https://tangem.com",
|
||||||
|
onTextClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,302 @@
|
||||||
|
package com.tangem.common.ui.notifications
|
||||||
|
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
|
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
sealed class NotificationUM(val config: NotificationConfig) {
|
||||||
|
|
||||||
|
open class Error(
|
||||||
|
title: TextReference,
|
||||||
|
subtitle: TextReference,
|
||||||
|
iconResId: Int = R.drawable.ic_alert_24,
|
||||||
|
buttonState: NotificationConfig.ButtonsState? = null,
|
||||||
|
onCloseClick: (() -> Unit)? = null,
|
||||||
|
) : NotificationUM(
|
||||||
|
config = NotificationConfig(
|
||||||
|
title = title,
|
||||||
|
subtitle = subtitle,
|
||||||
|
iconResId = iconResId,
|
||||||
|
buttonsState = buttonState,
|
||||||
|
onCloseClick = onCloseClick,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
|
||||||
|
data object TotalExceedsBalance : Error(
|
||||||
|
title = resourceReference(R.string.send_notification_exceed_balance_title),
|
||||||
|
subtitle = resourceReference(R.string.send_notification_exceed_balance_text),
|
||||||
|
)
|
||||||
|
|
||||||
|
data object InvalidAmount : Error(
|
||||||
|
title = resourceReference(R.string.send_notification_invalid_amount_title),
|
||||||
|
subtitle = resourceReference(R.string.send_notification_invalid_amount_text),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MinimumAmountError(val amount: String) : Error(
|
||||||
|
title = resourceReference(R.string.send_notification_invalid_amount_title),
|
||||||
|
subtitle = resourceReference(
|
||||||
|
R.string.send_notification_invalid_minimum_amount_text,
|
||||||
|
wrappedList(amount, amount),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TransactionLimitError(
|
||||||
|
val cryptoCurrency: String,
|
||||||
|
val utxoLimit: String,
|
||||||
|
val amountLimit: String,
|
||||||
|
val onConfirmClick: () -> Unit,
|
||||||
|
) : Error(
|
||||||
|
title = resourceReference(R.string.send_notification_transaction_limit_title),
|
||||||
|
subtitle = resourceReference(
|
||||||
|
R.string.send_notification_transaction_limit_text,
|
||||||
|
wrappedList(cryptoCurrency, utxoLimit, amountLimit),
|
||||||
|
),
|
||||||
|
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||||
|
text = resourceReference(R.string.send_notification_leave_button, wrappedList(amountLimit)),
|
||||||
|
onClick = onConfirmClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TokenExceedsBalance(
|
||||||
|
val networkIconId: Int,
|
||||||
|
val currencyName: String,
|
||||||
|
val feeName: String,
|
||||||
|
val feeSymbol: String,
|
||||||
|
val networkName: String,
|
||||||
|
val mergeFeeNetworkName: Boolean = false,
|
||||||
|
val onClick: (() -> Unit)? = null,
|
||||||
|
) : Error(
|
||||||
|
title = resourceReference(
|
||||||
|
id = R.string.warning_send_blocked_funds_for_fee_title,
|
||||||
|
wrappedList(feeName),
|
||||||
|
),
|
||||||
|
subtitle = resourceReference(
|
||||||
|
id = R.string.warning_send_blocked_funds_for_fee_message,
|
||||||
|
formatArgs = wrappedList(currencyName, networkName, currencyName, feeName, feeSymbol),
|
||||||
|
),
|
||||||
|
iconResId = networkIconId,
|
||||||
|
buttonState = onClick?.let {
|
||||||
|
NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||||
|
text = resourceReference(
|
||||||
|
R.string.common_buy_currency,
|
||||||
|
wrappedList(
|
||||||
|
if (mergeFeeNetworkName) {
|
||||||
|
"$currencyName ($feeSymbol)"
|
||||||
|
} else {
|
||||||
|
feeName
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ExceedsBalance(
|
||||||
|
val networkIconId: Int,
|
||||||
|
val currencyName: String,
|
||||||
|
val feeName: String,
|
||||||
|
val feeSymbol: String,
|
||||||
|
val networkName: String,
|
||||||
|
val mergeFeeNetworkName: Boolean = false,
|
||||||
|
val onClick: (() -> Unit)? = null,
|
||||||
|
) : Error(
|
||||||
|
title = resourceReference(
|
||||||
|
id = R.string.warning_blocked_funds_for_fee_title,
|
||||||
|
wrappedList(feeName),
|
||||||
|
),
|
||||||
|
subtitle = resourceReference(
|
||||||
|
id = R.string.warning_blocked_funds_for_fee_message,
|
||||||
|
formatArgs = wrappedList(currencyName),
|
||||||
|
),
|
||||||
|
iconResId = networkIconId,
|
||||||
|
buttonState = onClick?.let {
|
||||||
|
NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||||
|
text = resourceReference(
|
||||||
|
R.string.common_buy_currency,
|
||||||
|
wrappedList(
|
||||||
|
if (mergeFeeNetworkName) {
|
||||||
|
"$currencyName ($feeSymbol)"
|
||||||
|
} else {
|
||||||
|
feeName
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ExistentialDeposit(val deposit: String, val onConfirmClick: () -> Unit) : Error(
|
||||||
|
title = resourceReference(R.string.send_notification_existential_deposit_title),
|
||||||
|
subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)),
|
||||||
|
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||||
|
text = resourceReference(R.string.send_notification_leave_button, wrappedList(deposit)),
|
||||||
|
onClick = onConfirmClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReserveAmount(val amount: String) : Error(
|
||||||
|
title = resourceReference(
|
||||||
|
id = R.string.send_notification_invalid_reserve_amount_title,
|
||||||
|
wrappedList(amount),
|
||||||
|
),
|
||||||
|
subtitle = resourceReference(id = R.string.send_notification_invalid_reserve_amount_text),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class Warning(
|
||||||
|
title: TextReference,
|
||||||
|
subtitle: TextReference,
|
||||||
|
iconResId: Int = R.drawable.img_attention_20,
|
||||||
|
buttonsState: NotificationConfig.ButtonsState? = null,
|
||||||
|
onCloseClick: (() -> Unit)? = null,
|
||||||
|
) : NotificationUM(
|
||||||
|
config = NotificationConfig(
|
||||||
|
title = title,
|
||||||
|
subtitle = subtitle,
|
||||||
|
iconResId = iconResId,
|
||||||
|
buttonsState = buttonsState,
|
||||||
|
onCloseClick = onCloseClick,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
data class HighFeeError(
|
||||||
|
val currencyName: String,
|
||||||
|
val amount: String,
|
||||||
|
val onConfirmClick: () -> Unit,
|
||||||
|
val onCloseClick: () -> Unit,
|
||||||
|
) : Warning(
|
||||||
|
title = resourceReference(R.string.send_notification_high_fee_title),
|
||||||
|
subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(currencyName, amount)),
|
||||||
|
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||||
|
text = resourceReference(R.string.send_notification_reduce_by, wrappedList(amount)),
|
||||||
|
onClick = onConfirmClick,
|
||||||
|
),
|
||||||
|
onCloseClick = onCloseClick,
|
||||||
|
)
|
||||||
|
|
||||||
|
data object FeeTooLow : Warning(
|
||||||
|
title = resourceReference(id = R.string.send_notification_transaction_delay_title),
|
||||||
|
subtitle = resourceReference(id = R.string.send_notification_transaction_delay_text),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TooHigh(
|
||||||
|
val value: String,
|
||||||
|
) : Warning(
|
||||||
|
title = resourceReference(id = R.string.send_notification_fee_too_high_title),
|
||||||
|
subtitle = resourceReference(id = R.string.send_notification_fee_too_high_text, wrappedList(value)),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class NetworkFeeUnreachable(val onRefresh: () -> Unit) : Warning(
|
||||||
|
title = resourceReference(R.string.send_fee_unreachable_error_title),
|
||||||
|
subtitle = resourceReference(R.string.send_fee_unreachable_error_text),
|
||||||
|
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||||
|
text = resourceReference(R.string.warning_button_refresh),
|
||||||
|
onClick = onRefresh,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TronAccountNotActivated(val tokenName: String) : Warning(
|
||||||
|
title = resourceReference(R.string.send_fee_unreachable_error_title),
|
||||||
|
subtitle = resourceReference(
|
||||||
|
R.string.send_tron_account_activation_error,
|
||||||
|
wrappedList(tokenName),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class FeeCoverageNotification(val cryptoAmount: String, val fiatAmount: String) : Warning(
|
||||||
|
title = resourceReference(R.string.send_network_fee_warning_title),
|
||||||
|
subtitle = resourceReference(
|
||||||
|
R.string.common_network_fee_warning_content,
|
||||||
|
wrappedList(cryptoAmount, fiatAmount),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class Info(
|
||||||
|
title: TextReference,
|
||||||
|
subtitle: TextReference,
|
||||||
|
iconResId: Int = R.drawable.ic_alert_circle_24,
|
||||||
|
buttonsState: NotificationConfig.ButtonsState? = null,
|
||||||
|
onCloseClick: (() -> Unit)? = null,
|
||||||
|
) : NotificationUM(
|
||||||
|
config = NotificationConfig(
|
||||||
|
title = title,
|
||||||
|
subtitle = subtitle,
|
||||||
|
iconResId = iconResId,
|
||||||
|
buttonsState = buttonsState,
|
||||||
|
onCloseClick = onCloseClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed interface Cardano {
|
||||||
|
|
||||||
|
data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Warning(
|
||||||
|
title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title),
|
||||||
|
subtitle = resourceReference(
|
||||||
|
id = R.string.cardano_coin_will_be_send_with_token_description,
|
||||||
|
formatArgs = wrappedList(minAdaValue, tokenName),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data object InsufficientBalanceToTransferCoin : Error(
|
||||||
|
title = resourceReference(id = R.string.cardano_max_amount_has_token_title),
|
||||||
|
subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class InsufficientBalanceToTransferToken(val tokenName: String) : Error(
|
||||||
|
title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title),
|
||||||
|
subtitle = resourceReference(
|
||||||
|
id = R.string.cardano_insufficient_balance_to_send_token_description,
|
||||||
|
formatArgs = wrappedList(tokenName),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface Koinos {
|
||||||
|
data class InsufficientRecoverableMana(
|
||||||
|
val mana: BigDecimal,
|
||||||
|
val maxMana: BigDecimal,
|
||||||
|
) : Error(
|
||||||
|
title = resourceReference(R.string.koinos_insufficient_mana_to_send_koin_title),
|
||||||
|
subtitle = resourceReference(
|
||||||
|
R.string.koinos_insufficient_mana_to_send_koin_description,
|
||||||
|
formatArgs = wrappedList(
|
||||||
|
BigDecimalFormatter.formatCryptoAmountShorted(mana, "", Blockchain.Koinos.decimals()),
|
||||||
|
BigDecimalFormatter.formatCryptoAmountShorted(maxMana, "", Blockchain.Koinos.decimals()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data object InsufficientBalance : Error(
|
||||||
|
title = resourceReference(R.string.koinos_insufficient_balance_to_send_koin_title),
|
||||||
|
subtitle = resourceReference(R.string.koinos_insufficient_balance_to_send_koin_description),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ManaExceedsBalance(
|
||||||
|
val availableKoinForTransfer: BigDecimal,
|
||||||
|
val onReduceClick: () -> Unit,
|
||||||
|
) : Error(
|
||||||
|
title = resourceReference(R.string.koinos_mana_exceeds_koin_balance_title),
|
||||||
|
subtitle = resourceReference(
|
||||||
|
R.string.koinos_mana_exceeds_koin_balance_description,
|
||||||
|
formatArgs = wrappedList(
|
||||||
|
BigDecimalFormatter.formatCryptoAmount(
|
||||||
|
availableKoinForTransfer,
|
||||||
|
Blockchain.Koinos.currency,
|
||||||
|
Blockchain.Koinos.decimals(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||||
|
text = resourceReference(R.string.send_notification_reduce_to, wrappedList(availableKoinForTransfer)),
|
||||||
|
onClick = onReduceClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,363 @@
|
||||||
|
package com.tangem.common.ui.notifications
|
||||||
|
|
||||||
|
import com.tangem.blockchain.common.BlockchainSdkError
|
||||||
|
import com.tangem.blockchain.common.transaction.Fee
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||||
|
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
||||||
|
import com.tangem.core.ui.extensions.networkIconResId
|
||||||
|
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||||
|
import com.tangem.core.ui.utils.parseBigDecimal
|
||||||
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
|
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||||
|
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
|
||||||
|
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||||
|
import com.tangem.domain.transaction.error.GetFeeError
|
||||||
|
import com.tangem.utils.extensions.orZero
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@Suppress("LargeClass")
|
||||||
|
object NotificationsFactory {
|
||||||
|
|
||||||
|
fun MutableList<NotificationUM>.addFeeUnreachableNotification(
|
||||||
|
feeError: GetFeeError,
|
||||||
|
tokenName: String,
|
||||||
|
onReload: () -> Unit,
|
||||||
|
) {
|
||||||
|
when (feeError) {
|
||||||
|
is GetFeeError.BlockchainErrors.TronActivationError -> add(
|
||||||
|
NotificationUM.Warning.TronAccountNotActivated(tokenName),
|
||||||
|
)
|
||||||
|
is GetFeeError.DataError,
|
||||||
|
is GetFeeError.UnknownError,
|
||||||
|
-> add(
|
||||||
|
NotificationUM.Warning.NetworkFeeUnreachable(onReload),
|
||||||
|
)
|
||||||
|
else -> {
|
||||||
|
/* do nothing */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableList<NotificationUM>.addExceedBalanceNotification(
|
||||||
|
feeAmount: BigDecimal,
|
||||||
|
sendingAmount: BigDecimal,
|
||||||
|
isSubtractionAvailable: Boolean,
|
||||||
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
|
minimumRequirement: BigDecimal? = null,
|
||||||
|
) {
|
||||||
|
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||||
|
if (!isSubtractionAvailable) return
|
||||||
|
|
||||||
|
val showNotification = sendingAmount + feeAmount > balance - minimumRequirement.orZero()
|
||||||
|
if (showNotification) {
|
||||||
|
add(NotificationUM.Error.TotalExceedsBalance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableList<NotificationUM>.addReserveAmountErrorNotification(
|
||||||
|
reserveAmount: BigDecimal?,
|
||||||
|
sendingAmount: BigDecimal,
|
||||||
|
cryptoCurrency: CryptoCurrency,
|
||||||
|
isAccountFunded: Boolean,
|
||||||
|
) {
|
||||||
|
if (!isAccountFunded && reserveAmount != null && reserveAmount > sendingAmount) {
|
||||||
|
add(
|
||||||
|
NotificationUM.Error.ReserveAmount(
|
||||||
|
BigDecimalFormatter.formatCryptoAmount(
|
||||||
|
cryptoAmount = sendingAmount,
|
||||||
|
cryptoCurrency = cryptoCurrency,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableList<NotificationUM>.addTransactionLimitErrorNotification(
|
||||||
|
utxoLimit: UtxoAmountLimit?,
|
||||||
|
cryptoCurrency: CryptoCurrency,
|
||||||
|
onReduceClick: (
|
||||||
|
reduceAmountTo: BigDecimal,
|
||||||
|
notification: Class<out NotificationUM>,
|
||||||
|
) -> Unit,
|
||||||
|
) {
|
||||||
|
if (utxoLimit != null) {
|
||||||
|
add(
|
||||||
|
NotificationUM.Error.TransactionLimitError(
|
||||||
|
cryptoCurrency = cryptoCurrency.name,
|
||||||
|
utxoLimit = utxoLimit.maxLimit.toPlainString(),
|
||||||
|
amountLimit = BigDecimalFormatter.formatCryptoAmount(
|
||||||
|
cryptoAmount = utxoLimit.maxAmount,
|
||||||
|
cryptoCurrency = cryptoCurrency,
|
||||||
|
),
|
||||||
|
onConfirmClick = {
|
||||||
|
onReduceClick(
|
||||||
|
utxoLimit.maxAmount,
|
||||||
|
NotificationUM.Error.TransactionLimitError::class.java,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableList<NotificationUM>.addExistentialWarningNotification(
|
||||||
|
existentialDeposit: BigDecimal?,
|
||||||
|
feeAmount: BigDecimal,
|
||||||
|
receivedAmount: BigDecimal,
|
||||||
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
|
onReduceClick: (
|
||||||
|
reduceAmountBy: BigDecimal,
|
||||||
|
reduceAmountByDiff: BigDecimal,
|
||||||
|
notification: Class<out NotificationUM>,
|
||||||
|
) -> Unit,
|
||||||
|
) {
|
||||||
|
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||||
|
val balance = cryptoCurrencyStatus.value.amount ?: return
|
||||||
|
val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) {
|
||||||
|
feeAmount
|
||||||
|
} else {
|
||||||
|
receivedAmount
|
||||||
|
}
|
||||||
|
val diff = balance.minus(spendingAmount)
|
||||||
|
if (existentialDeposit != null && diff >= BigDecimal.ZERO && existentialDeposit > diff) {
|
||||||
|
add(
|
||||||
|
NotificationUM.Error.ExistentialDeposit(
|
||||||
|
deposit = BigDecimalFormatter.formatCryptoAmountUncapped(
|
||||||
|
cryptoAmount = existentialDeposit,
|
||||||
|
cryptoCurrency = cryptoCurrency,
|
||||||
|
),
|
||||||
|
onConfirmClick = {
|
||||||
|
onReduceClick(
|
||||||
|
existentialDeposit,
|
||||||
|
existentialDeposit.minus(diff),
|
||||||
|
NotificationUM.Error.ExistentialDeposit::class.java,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableList<NotificationUM>.addFeeCoverageNotification(
|
||||||
|
isFeeCoverage: Boolean,
|
||||||
|
amountField: AmountFieldModel,
|
||||||
|
sendingValue: BigDecimal,
|
||||||
|
appCurrency: AppCurrency,
|
||||||
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
|
) {
|
||||||
|
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||||
|
val fiatRate = cryptoCurrencyStatus.value.fiatRate
|
||||||
|
val amountValue = amountField.cryptoAmount.value ?: return
|
||||||
|
|
||||||
|
val cryptoDiff = amountValue.minus(sendingValue)
|
||||||
|
if (isFeeCoverage) {
|
||||||
|
add(
|
||||||
|
NotificationUM.Warning.FeeCoverageNotification(
|
||||||
|
cryptoAmount = BigDecimalFormatter.formatCryptoAmountUncapped(
|
||||||
|
cryptoAmount = cryptoDiff,
|
||||||
|
cryptoCurrency = cryptoCurrency,
|
||||||
|
),
|
||||||
|
fiatAmount = getFiatString(
|
||||||
|
value = cryptoDiff,
|
||||||
|
rate = fiatRate,
|
||||||
|
appCurrency = appCurrency,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableList<NotificationUM>.addDustWarningNotification(
|
||||||
|
dustValue: BigDecimal?,
|
||||||
|
feeValue: BigDecimal,
|
||||||
|
sendingAmount: BigDecimal,
|
||||||
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
|
feeCurrencyStatus: CryptoCurrencyStatus?,
|
||||||
|
) {
|
||||||
|
if (dustValue == null) return
|
||||||
|
val isExceedsLimit = checkDustLimits(
|
||||||
|
feeAmount = feeValue,
|
||||||
|
receivedAmount = sendingAmount,
|
||||||
|
dustValue = dustValue,
|
||||||
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
|
feeCurrencyStatus = feeCurrencyStatus,
|
||||||
|
)
|
||||||
|
if (isExceedsLimit) {
|
||||||
|
add(
|
||||||
|
NotificationUM.Error.MinimumAmountError(
|
||||||
|
amount = dustValue.parseBigDecimal(cryptoCurrencyStatus.currency.decimals),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableList<NotificationUM>.addExceedsBalanceNotification(
|
||||||
|
cryptoCurrencyWarning: CryptoCurrencyWarning?,
|
||||||
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
|
shouldMergeFeeNetworkName: Boolean,
|
||||||
|
onClick: (CryptoCurrency) -> Unit,
|
||||||
|
onAnalyticsEvent: (CryptoCurrency) -> Unit,
|
||||||
|
) {
|
||||||
|
when (cryptoCurrencyWarning) {
|
||||||
|
is CryptoCurrencyWarning.BalanceNotEnoughForFee -> {
|
||||||
|
add(
|
||||||
|
NotificationUM.Error.TokenExceedsBalance(
|
||||||
|
networkIconId = cryptoCurrencyWarning.coinCurrency.networkIconResId,
|
||||||
|
networkName = cryptoCurrencyWarning.coinCurrency.name,
|
||||||
|
currencyName = cryptoCurrencyStatus.currency.name,
|
||||||
|
feeName = cryptoCurrencyWarning.coinCurrency.name,
|
||||||
|
feeSymbol = cryptoCurrencyWarning.coinCurrency.symbol,
|
||||||
|
mergeFeeNetworkName = shouldMergeFeeNetworkName,
|
||||||
|
onClick = {
|
||||||
|
onClick(cryptoCurrencyWarning.coinCurrency)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onAnalyticsEvent(cryptoCurrencyStatus.currency)
|
||||||
|
}
|
||||||
|
is CryptoCurrencyWarning.CustomTokenNotEnoughForFee -> {
|
||||||
|
val currency = cryptoCurrencyWarning.feeCurrency
|
||||||
|
add(
|
||||||
|
NotificationUM.Error.TokenExceedsBalance(
|
||||||
|
networkIconId = currency?.networkIconResId ?: R.drawable.ic_alert_24,
|
||||||
|
currencyName = cryptoCurrencyWarning.currency.name,
|
||||||
|
feeName = cryptoCurrencyWarning.feeCurrencyName,
|
||||||
|
feeSymbol = cryptoCurrencyWarning.feeCurrencySymbol,
|
||||||
|
networkName = cryptoCurrencyWarning.networkName,
|
||||||
|
mergeFeeNetworkName = shouldMergeFeeNetworkName,
|
||||||
|
onClick = {
|
||||||
|
currency?.let {
|
||||||
|
onClick(currency)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onAnalyticsEvent(cryptoCurrencyWarning.currency)
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MutableList<NotificationUM>.addValidateTransactionNotifications(
|
||||||
|
dustValue: BigDecimal,
|
||||||
|
fee: Fee?,
|
||||||
|
validationError: Throwable?,
|
||||||
|
cryptoCurrency: CryptoCurrency,
|
||||||
|
onReduceClick: (
|
||||||
|
reduceAmountTo: BigDecimal,
|
||||||
|
notification: Class<out NotificationUM>,
|
||||||
|
) -> Unit,
|
||||||
|
) {
|
||||||
|
when (validationError) {
|
||||||
|
is BlockchainSdkError.Cardano -> addCardanoTransactionValidationError(
|
||||||
|
error = validationError,
|
||||||
|
sendingCurrency = cryptoCurrency,
|
||||||
|
dustValue = dustValue,
|
||||||
|
)
|
||||||
|
is BlockchainSdkError.Koinos -> addKoinosTransactionValidationError(
|
||||||
|
error = validationError,
|
||||||
|
onReduceClick = onReduceClick,
|
||||||
|
)
|
||||||
|
null -> (fee as? Fee.CardanoToken)?.let {
|
||||||
|
add(
|
||||||
|
NotificationUM.Cardano.MinAdaValueCharged(
|
||||||
|
tokenName = cryptoCurrency.name,
|
||||||
|
minAdaValue = it.minAdaValue.parseBigDecimal(cryptoCurrency.decimals),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<NotificationUM>.addCardanoTransactionValidationError(
|
||||||
|
error: BlockchainSdkError.Cardano,
|
||||||
|
sendingCurrency: CryptoCurrency,
|
||||||
|
dustValue: BigDecimal?,
|
||||||
|
) {
|
||||||
|
when (error) {
|
||||||
|
BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> {
|
||||||
|
add(NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name))
|
||||||
|
}
|
||||||
|
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
|
||||||
|
when (sendingCurrency) {
|
||||||
|
is CryptoCurrency.Coin -> NotificationUM.Cardano.InsufficientBalanceToTransferCoin
|
||||||
|
is CryptoCurrency.Token -> {
|
||||||
|
NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
|
||||||
|
}
|
||||||
|
}.let(::add)
|
||||||
|
}
|
||||||
|
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
|
||||||
|
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
|
||||||
|
-> {
|
||||||
|
dustValue?.let {
|
||||||
|
add(
|
||||||
|
NotificationUM.Error.MinimumAmountError(
|
||||||
|
amount = it.parseBigDecimal(sendingCurrency.decimals),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<NotificationUM>.addKoinosTransactionValidationError(
|
||||||
|
error: BlockchainSdkError.Koinos,
|
||||||
|
onReduceClick: (
|
||||||
|
reduceAmountTo: BigDecimal,
|
||||||
|
notification: Class<out NotificationUM>,
|
||||||
|
) -> Unit,
|
||||||
|
) {
|
||||||
|
when (error) {
|
||||||
|
is BlockchainSdkError.Koinos.InsufficientBalance -> {
|
||||||
|
add(NotificationUM.Koinos.InsufficientBalance)
|
||||||
|
}
|
||||||
|
is BlockchainSdkError.Koinos.InsufficientMana -> {
|
||||||
|
add(
|
||||||
|
NotificationUM.Koinos.InsufficientRecoverableMana(
|
||||||
|
mana = error.manaBalance ?: BigDecimal.ZERO,
|
||||||
|
maxMana = error.maxMana ?: BigDecimal.ZERO,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is BlockchainSdkError.Koinos.ManaFeeExceedsBalance -> {
|
||||||
|
add(
|
||||||
|
NotificationUM.Koinos.ManaExceedsBalance(
|
||||||
|
availableKoinForTransfer = error.availableKoinForTransfer,
|
||||||
|
onReduceClick = {
|
||||||
|
onReduceClick(
|
||||||
|
error.availableKoinForTransfer,
|
||||||
|
NotificationUM.Koinos.InsufficientRecoverableMana::class.java,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkDustLimits(
|
||||||
|
feeAmount: BigDecimal,
|
||||||
|
receivedAmount: BigDecimal,
|
||||||
|
dustValue: BigDecimal,
|
||||||
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
|
feeCurrencyStatus: CryptoCurrencyStatus?,
|
||||||
|
): Boolean {
|
||||||
|
val change = when (cryptoCurrencyStatus.currency) {
|
||||||
|
is CryptoCurrency.Coin -> {
|
||||||
|
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||||
|
balance - (feeAmount + receivedAmount)
|
||||||
|
}
|
||||||
|
is CryptoCurrency.Token -> {
|
||||||
|
val balance = feeCurrencyStatus?.value?.amount ?: BigDecimal.ZERO
|
||||||
|
balance - feeAmount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||||
|
return receivedAmount < dustValue || isChangeLowerThanDust
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.tangem.common.ui.tokens
|
||||||
|
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
|
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||||
|
|
||||||
|
fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference {
|
||||||
|
return when (val unavailabilityReason = this) {
|
||||||
|
is ScenarioUnavailabilityReason.StakingUnavailable -> {
|
||||||
|
resourceReference(
|
||||||
|
id = R.string.token_button_unavailability_reason_staking_unavailable,
|
||||||
|
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is ScenarioUnavailabilityReason.PendingTransaction -> {
|
||||||
|
when (unavailabilityReason.withdrawalScenario) {
|
||||||
|
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||||
|
id = R.string.token_button_unavailability_reason_pending_transaction_send,
|
||||||
|
formatArgs = wrappedList(unavailabilityReason.networkName),
|
||||||
|
)
|
||||||
|
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
|
||||||
|
id = R.string.token_button_unavailability_reason_pending_transaction_sell,
|
||||||
|
formatArgs = wrappedList(unavailabilityReason.networkName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ScenarioUnavailabilityReason.EmptyBalance -> {
|
||||||
|
when (unavailabilityReason.withdrawalScenario) {
|
||||||
|
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||||
|
id = R.string.token_button_unavailability_reason_empty_balance_send,
|
||||||
|
)
|
||||||
|
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
|
||||||
|
id = R.string.token_button_unavailability_reason_empty_balance_sell,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ScenarioUnavailabilityReason.BuyUnavailable -> {
|
||||||
|
resourceReference(
|
||||||
|
id = R.string.token_button_unavailability_reason_buy_unavailable,
|
||||||
|
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is ScenarioUnavailabilityReason.NotExchangeable -> {
|
||||||
|
resourceReference(
|
||||||
|
id = R.string.token_button_unavailability_reason_not_exchangeable,
|
||||||
|
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is ScenarioUnavailabilityReason.NotSupportedBySellService -> {
|
||||||
|
resourceReference(
|
||||||
|
id = R.string.token_button_unavailability_reason_sell_unavailable,
|
||||||
|
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ScenarioUnavailabilityReason.Unreachable -> {
|
||||||
|
resourceReference(
|
||||||
|
id = R.string.token_button_unavailability_generic_description,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
|
||||||
|
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
|
||||||
|
)
|
||||||
|
ScenarioUnavailabilityReason.None -> {
|
||||||
|
throw IllegalArgumentException("The unavailability reason must be other than None")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
package com.tangem.common.ui.tokens
|
||||||
|
|
||||||
|
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||||
|
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
|
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
|
||||||
|
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||||
|
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||||
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||||
|
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||||
|
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
import com.tangem.utils.extensions.isZero
|
||||||
|
import com.tangem.utils.extensions.orZero
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token item state converter from [CryptoCurrencyStatus] to [TokenItemState]
|
||||||
|
*
|
||||||
|
* @property appCurrency app currency
|
||||||
|
* @property titleStateProvider title state provider
|
||||||
|
* @property subtitleStateProvider subtitle state provider
|
||||||
|
* @property onItemClick callback is invoked when item is clicked
|
||||||
|
* @property onItemLongClick callback is invoked when item is long clicked
|
||||||
|
*/
|
||||||
|
class TokenItemStateConverter(
|
||||||
|
private val appCurrency: AppCurrency,
|
||||||
|
private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = Companion::createTitleState,
|
||||||
|
private val subtitleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.SubtitleState? = {
|
||||||
|
createSubtitleState(it, appCurrency)
|
||||||
|
},
|
||||||
|
private val onItemClick: (CryptoCurrencyStatus) -> Unit,
|
||||||
|
private val onItemLongClick: ((CryptoCurrencyStatus) -> Unit)? = null,
|
||||||
|
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
||||||
|
|
||||||
|
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||||
|
|
||||||
|
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
|
||||||
|
return when (value.value) {
|
||||||
|
is CryptoCurrencyStatus.Loading -> value.mapToLoadingState()
|
||||||
|
is CryptoCurrencyStatus.Loaded,
|
||||||
|
is CryptoCurrencyStatus.Custom,
|
||||||
|
is CryptoCurrencyStatus.NoQuote,
|
||||||
|
is CryptoCurrencyStatus.NoAccount,
|
||||||
|
-> value.mapToTokenItemState()
|
||||||
|
is CryptoCurrencyStatus.MissedDerivation -> value.mapToNoAddressTokenItemState()
|
||||||
|
is CryptoCurrencyStatus.Unreachable,
|
||||||
|
is CryptoCurrencyStatus.NoAmount,
|
||||||
|
-> value.mapToUnreachableTokenItemState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CryptoCurrencyStatus.mapToLoadingState(): TokenItemState.Loading {
|
||||||
|
return TokenItemState.Loading(
|
||||||
|
id = currency.id.value,
|
||||||
|
iconState = iconStateConverter.convert(value = this),
|
||||||
|
titleState = titleStateProvider(this) as TokenItemState.TitleState.Content,
|
||||||
|
subtitleState = requireNotNull(subtitleStateProvider(this)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CryptoCurrencyStatus.mapToTokenItemState(): TokenItemState.Content {
|
||||||
|
return TokenItemState.Content(
|
||||||
|
id = currency.id.value,
|
||||||
|
iconState = iconStateConverter.convert(value = this),
|
||||||
|
titleState = titleStateProvider(this),
|
||||||
|
subtitleState = requireNotNull(subtitleStateProvider(this)),
|
||||||
|
fiatAmountState = TokenItemState.FiatAmountState.Content(
|
||||||
|
text = getFormattedFiatAmount(),
|
||||||
|
hasStaked = !getStakedBalance().isZero(),
|
||||||
|
),
|
||||||
|
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = getFormattedAmount()),
|
||||||
|
onItemClick = { onItemClick(this) },
|
||||||
|
onItemLongClick = onItemLongClick?.let {
|
||||||
|
{ it(this) }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CryptoCurrencyStatus.getFormattedAmount(): String {
|
||||||
|
val amount = value.amount?.plus(getStakedBalance()) ?: return DASH_SIGN
|
||||||
|
|
||||||
|
return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String {
|
||||||
|
val fiatYieldBalance = value.fiatRate?.times(getStakedBalance()).orZero()
|
||||||
|
val fiatAmount = value.fiatAmount?.plus(fiatYieldBalance) ?: return DASH_SIGN
|
||||||
|
|
||||||
|
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CryptoCurrencyStatus.getStakedBalance() =
|
||||||
|
(value.yieldBalance as? YieldBalance.Data)?.getTotalWithRewardsStakingBalance().orZero()
|
||||||
|
|
||||||
|
private fun CryptoCurrencyStatus.mapToUnreachableTokenItemState(): TokenItemState.Unreachable {
|
||||||
|
return TokenItemState.Unreachable(
|
||||||
|
id = currency.id.value,
|
||||||
|
iconState = iconStateConverter.convert(value = this),
|
||||||
|
titleState = titleStateProvider(this),
|
||||||
|
subtitleState = subtitleStateProvider(this),
|
||||||
|
onItemClick = { onItemClick(this) },
|
||||||
|
onItemLongClick = onItemLongClick?.let {
|
||||||
|
{ it(this) }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CryptoCurrencyStatus.mapToNoAddressTokenItemState(): TokenItemState.NoAddress {
|
||||||
|
return TokenItemState.NoAddress(
|
||||||
|
id = currency.id.value,
|
||||||
|
iconState = iconStateConverter.convert(this),
|
||||||
|
titleState = titleStateProvider(this),
|
||||||
|
subtitleState = subtitleStateProvider(this),
|
||||||
|
onItemLongClick = onItemLongClick?.let {
|
||||||
|
{ it(this) }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
|
||||||
|
fun createTitleState(currencyStatus: CryptoCurrencyStatus): TokenItemState.TitleState {
|
||||||
|
return when (val value = currencyStatus.value) {
|
||||||
|
is CryptoCurrencyStatus.Loading,
|
||||||
|
is CryptoCurrencyStatus.MissedDerivation,
|
||||||
|
is CryptoCurrencyStatus.Unreachable,
|
||||||
|
is CryptoCurrencyStatus.NoAmount,
|
||||||
|
-> {
|
||||||
|
TokenItemState.TitleState.Content(text = currencyStatus.currency.name)
|
||||||
|
}
|
||||||
|
is CryptoCurrencyStatus.Loaded,
|
||||||
|
is CryptoCurrencyStatus.Custom,
|
||||||
|
is CryptoCurrencyStatus.NoQuote,
|
||||||
|
is CryptoCurrencyStatus.NoAccount,
|
||||||
|
-> {
|
||||||
|
TokenItemState.TitleState.Content(
|
||||||
|
text = currencyStatus.currency.name,
|
||||||
|
hasPending = value.hasCurrentNetworkTransactions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createSubtitleState(
|
||||||
|
currencyStatus: CryptoCurrencyStatus,
|
||||||
|
appCurrency: AppCurrency,
|
||||||
|
): TokenItemState.SubtitleState? {
|
||||||
|
return when (currencyStatus.value) {
|
||||||
|
is CryptoCurrencyStatus.Loading -> TokenItemState.SubtitleState.Loading
|
||||||
|
is CryptoCurrencyStatus.Loaded,
|
||||||
|
is CryptoCurrencyStatus.Custom,
|
||||||
|
is CryptoCurrencyStatus.NoQuote,
|
||||||
|
is CryptoCurrencyStatus.NoAccount,
|
||||||
|
-> currencyStatus.getCryptoPriceState(appCurrency)
|
||||||
|
is CryptoCurrencyStatus.MissedDerivation,
|
||||||
|
is CryptoCurrencyStatus.Unreachable,
|
||||||
|
is CryptoCurrencyStatus.NoAmount,
|
||||||
|
-> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CryptoCurrencyStatus.getCryptoPriceState(appCurrency: AppCurrency): TokenItemState.SubtitleState {
|
||||||
|
val fiatRate = value.fiatRate
|
||||||
|
val priceChange = value.priceChange
|
||||||
|
|
||||||
|
return if (fiatRate != null && priceChange != null) {
|
||||||
|
TokenItemState.SubtitleState.CryptoPriceContent(
|
||||||
|
price = fiatRate.getFormattedCryptoPrice(appCurrency),
|
||||||
|
priceChangePercent = BigDecimalFormatter.formatPercent(
|
||||||
|
percent = priceChange,
|
||||||
|
useAbsoluteValue = true,
|
||||||
|
),
|
||||||
|
type = priceChange.getPriceChangeType(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
TokenItemState.SubtitleState.Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BigDecimal.getFormattedCryptoPrice(appCurrency: AppCurrency): String {
|
||||||
|
return BigDecimalFormatter.formatFiatAmountUncapped(
|
||||||
|
fiatAmount = this,
|
||||||
|
fiatCurrencyCode = appCurrency.code,
|
||||||
|
fiatCurrencySymbol = appCurrency.symbol,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BigDecimal.getPriceChangeType(): PriceChangeType {
|
||||||
|
return PriceChangeConverter.fromBigDecimal(value = this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.tangem.common.ui.tokens
|
||||||
|
|
||||||
|
import androidx.compose.animation.Animatable
|
||||||
|
import androidx.compose.animation.core.snap
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text view for token price.
|
||||||
|
*
|
||||||
|
* @param price Price of the token.
|
||||||
|
* @param priceChangeType Type of the price change.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun TokenPriceText(price: String, modifier: Modifier = Modifier, priceChangeType: PriceChangeType? = null) {
|
||||||
|
val growColor = TangemTheme.colors.text.accent
|
||||||
|
val fallColor = TangemTheme.colors.text.warning
|
||||||
|
val generalColor = TangemTheme.colors.text.primary1
|
||||||
|
|
||||||
|
val color = remember(generalColor) { Animatable(generalColor) }
|
||||||
|
var animationSkipped by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(price) {
|
||||||
|
if (animationSkipped.not()) {
|
||||||
|
animationSkipped = true
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priceChangeType != null) {
|
||||||
|
val nextColor = when (priceChangeType) {
|
||||||
|
PriceChangeType.UP,
|
||||||
|
-> growColor
|
||||||
|
PriceChangeType.DOWN -> fallColor
|
||||||
|
PriceChangeType.NEUTRAL -> return@LaunchedEffect
|
||||||
|
}
|
||||||
|
|
||||||
|
color.animateTo(nextColor, snap())
|
||||||
|
color.animateTo(generalColor, tween(durationMillis = 500))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = modifier,
|
||||||
|
text = price,
|
||||||
|
color = color.value,
|
||||||
|
maxLines = 1,
|
||||||
|
style = TangemTheme.typography.body2,
|
||||||
|
overflow = TextOverflow.Visible,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,213 @@
|
||||||
|
package com.tangem.common.ui.userwallet
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.CardColors
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
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.clip
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.util.fastForEach
|
||||||
|
import coil.compose.SubcomposeAsyncImage
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||||
|
import com.tangem.core.ui.components.RectangleShimmer
|
||||||
|
import com.tangem.core.ui.components.block.BlockCard
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.core.ui.coil.RotationTransformation
|
||||||
|
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UserWalletItem(
|
||||||
|
state: UserWalletItemUM,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
blockColors: CardColors = TangemBlockCardColors,
|
||||||
|
) {
|
||||||
|
BlockCard(
|
||||||
|
modifier = modifier,
|
||||||
|
colors = blockColors,
|
||||||
|
onClick = state.onClick,
|
||||||
|
enabled = state.isEnabled,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = TangemTheme.dimens.size68)
|
||||||
|
.padding(all = TangemTheme.dimens.spacing12),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||||
|
) {
|
||||||
|
CardImage(imageUrl = state.imageUrl)
|
||||||
|
NameAndInfo(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
name = state.name,
|
||||||
|
information = state.information,
|
||||||
|
)
|
||||||
|
|
||||||
|
when (state.endIcon) {
|
||||||
|
UserWalletItemUM.EndIcon.None -> {}
|
||||||
|
UserWalletItemUM.EndIcon.Arrow -> {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_right_24),
|
||||||
|
tint = TangemTheme.colors.icon.informative,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
UserWalletItemUM.EndIcon.Checkmark -> {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(R.drawable.ic_check_24),
|
||||||
|
tint = TangemTheme.colors.icon.accent,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NameAndInfo(name: TextReference, information: TextReference, modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.heightIn(min = TangemTheme.dimens.size40),
|
||||||
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = name.resolveReference(),
|
||||||
|
style = TangemTheme.typography.subtitle1,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = information.resolveReference(),
|
||||||
|
label = "User wallet information",
|
||||||
|
) { information ->
|
||||||
|
Text(
|
||||||
|
text = information,
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CardImage(imageUrl: String, modifier: Modifier = Modifier) {
|
||||||
|
val imageModifier = modifier
|
||||||
|
.width(TangemTheme.dimens.size24)
|
||||||
|
.height(TangemTheme.dimens.size36)
|
||||||
|
.clip(TangemTheme.shapes.roundedCornersSmall)
|
||||||
|
|
||||||
|
SubcomposeAsyncImage(
|
||||||
|
modifier = imageModifier,
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.transformations(RotationTransformation(angle = 90f))
|
||||||
|
.size(
|
||||||
|
width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() },
|
||||||
|
height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() },
|
||||||
|
)
|
||||||
|
.data(imageUrl)
|
||||||
|
.crossfade(enable = true)
|
||||||
|
.allowHardware(enable = false)
|
||||||
|
.build(),
|
||||||
|
loading = {
|
||||||
|
RectangleShimmer(
|
||||||
|
modifier = imageModifier,
|
||||||
|
radius = TangemTheme.dimens.size2,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
error = {
|
||||||
|
Image(
|
||||||
|
modifier = imageModifier,
|
||||||
|
imageVector = ImageVector.vectorResource(R.drawable.img_card_wallet_2_gray_22_36),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun Preview() {
|
||||||
|
TangemThemePreview {
|
||||||
|
val list = persistentListOf(
|
||||||
|
UserWalletItemUM(
|
||||||
|
id = UserWalletId("user_wallet_1".encodeToByteArray()),
|
||||||
|
name = stringReference("My Wallet"),
|
||||||
|
information = getInformation(3, "4 496,75 $"),
|
||||||
|
imageUrl = "",
|
||||||
|
isEnabled = true,
|
||||||
|
onClick = {},
|
||||||
|
),
|
||||||
|
UserWalletItemUM(
|
||||||
|
id = UserWalletId("user_wallet_2".encodeToByteArray()),
|
||||||
|
name = stringReference("Old wallet"),
|
||||||
|
information = getInformation(3, "4 496,75 $"),
|
||||||
|
imageUrl = "",
|
||||||
|
isEnabled = true,
|
||||||
|
onClick = {},
|
||||||
|
endIcon = UserWalletItemUM.EndIcon.Arrow,
|
||||||
|
),
|
||||||
|
UserWalletItemUM(
|
||||||
|
id = UserWalletId("user_wallet_3".encodeToByteArray()),
|
||||||
|
name = stringReference("Multi Card"),
|
||||||
|
information = getInformation(3, "4 496,75 $"),
|
||||||
|
imageUrl = "",
|
||||||
|
isEnabled = false,
|
||||||
|
endIcon = UserWalletItemUM.EndIcon.Checkmark,
|
||||||
|
onClick = {},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.background(TangemTheme.colors.background.tertiary)
|
||||||
|
.padding(TangemTheme.dimens.spacing12),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||||
|
) {
|
||||||
|
list.fastForEach { userWalletItemUM ->
|
||||||
|
UserWalletItem(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
state = userWalletItemUM,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInformation(cardCount: Int, totalBalance: String): TextReference {
|
||||||
|
val t1 = TextReference.PluralRes(
|
||||||
|
id = R.plurals.card_label_card_count,
|
||||||
|
count = cardCount,
|
||||||
|
formatArgs = wrappedList(cardCount),
|
||||||
|
)
|
||||||
|
val divider = stringReference(value = " • ")
|
||||||
|
val t2 = stringReference(totalBalance)
|
||||||
|
|
||||||
|
return TextReference.Combined(wrappedList(t1, divider, t2))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
package com.tangem.common.ui.userwallet.converter
|
||||||
|
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||||
|
import com.tangem.core.ui.extensions.*
|
||||||
|
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||||
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.common.util.getCardsCount
|
||||||
|
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||||
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
import com.tangem.utils.StringsSigns.DOT
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converter from [UserWallet] to [UserWalletItemUM]
|
||||||
|
*
|
||||||
|
* @property onClick lambda be invoked when item is clicked
|
||||||
|
* @property appCurrency selected app currency
|
||||||
|
* @property balance wallet balance
|
||||||
|
* @property isLoading wallet loading state
|
||||||
|
* @property isBalanceHidden wallet balance is hidden
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
class UserWalletItemUMConverter(
|
||||||
|
private val onClick: (UserWalletId) -> Unit,
|
||||||
|
private val appCurrency: AppCurrency? = null,
|
||||||
|
private val balance: TotalFiatBalance? = null,
|
||||||
|
private val isLoading: Boolean = true,
|
||||||
|
private val isBalanceHidden: Boolean = false,
|
||||||
|
private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None,
|
||||||
|
) : Converter<UserWallet, UserWalletItemUM> {
|
||||||
|
|
||||||
|
override fun convert(value: UserWallet): UserWalletItemUM {
|
||||||
|
return with(value) {
|
||||||
|
UserWalletItemUM(
|
||||||
|
id = walletId,
|
||||||
|
name = stringReference(name),
|
||||||
|
information = getInfo(
|
||||||
|
appCurrency = appCurrency,
|
||||||
|
balance = balance,
|
||||||
|
isBalanceHidden = isBalanceHidden,
|
||||||
|
isLoading = isLoading,
|
||||||
|
),
|
||||||
|
imageUrl = artworkUrl,
|
||||||
|
isEnabled = !isLocked,
|
||||||
|
endIcon = endIcon,
|
||||||
|
onClick = { onClick(value.walletId) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun UserWallet.getInfo(
|
||||||
|
appCurrency: AppCurrency?,
|
||||||
|
balance: TotalFiatBalance?,
|
||||||
|
isBalanceHidden: Boolean,
|
||||||
|
isLoading: Boolean,
|
||||||
|
): TextReference {
|
||||||
|
val dividerRef = stringReference(value = " $DOT ")
|
||||||
|
|
||||||
|
val cardCount = getCardsCount() ?: 1
|
||||||
|
val cardCountRef = TextReference.PluralRes(
|
||||||
|
id = R.plurals.card_label_card_count,
|
||||||
|
count = cardCount,
|
||||||
|
formatArgs = wrappedList(cardCount),
|
||||||
|
)
|
||||||
|
|
||||||
|
return when {
|
||||||
|
isBalanceHidden -> combinedReference(cardCountRef, dividerRef, TextReference.STARS)
|
||||||
|
isLocked -> combinedReference(cardCountRef, dividerRef, resourceReference(R.string.common_locked))
|
||||||
|
isLoading -> cardCountRef
|
||||||
|
else -> getBalanceInfo(balance, appCurrency, cardCountRef, dividerRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBalanceInfo(
|
||||||
|
balance: TotalFiatBalance?,
|
||||||
|
appCurrency: AppCurrency?,
|
||||||
|
cardCountRef: TextReference,
|
||||||
|
dividerRef: TextReference,
|
||||||
|
): TextReference {
|
||||||
|
val amount = when (balance) {
|
||||||
|
is TotalFiatBalance.Loaded -> balance.amount
|
||||||
|
is TotalFiatBalance.Failed,
|
||||||
|
is TotalFiatBalance.Loading,
|
||||||
|
null,
|
||||||
|
-> null
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (amount != null && appCurrency != null) {
|
||||||
|
val formattedAmount = BigDecimalFormatter.formatFiatAmount(
|
||||||
|
fiatAmount = amount,
|
||||||
|
fiatCurrencyCode = appCurrency.code,
|
||||||
|
fiatCurrencySymbol = appCurrency.symbol,
|
||||||
|
)
|
||||||
|
val amountRef = stringReference(formattedAmount)
|
||||||
|
combinedReference(cardCountRef, dividerRef, amountRef)
|
||||||
|
} else {
|
||||||
|
combinedReference(cardCountRef, dividerRef, stringReference(BigDecimalFormatter.EMPTY_BALANCE_SIGN))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.tangem.common.ui.userwallet.state
|
||||||
|
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
import javax.annotation.concurrent.Immutable
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class UserWalletItemUM(
|
||||||
|
val id: UserWalletId,
|
||||||
|
val name: TextReference,
|
||||||
|
val information: TextReference,
|
||||||
|
val imageUrl: String,
|
||||||
|
val isEnabled: Boolean,
|
||||||
|
val endIcon: EndIcon = EndIcon.None,
|
||||||
|
val onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
enum class EndIcon {
|
||||||
|
None,
|
||||||
|
Arrow,
|
||||||
|
Checkmark,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -81,6 +81,12 @@ sealed class AnalyticsParam {
|
||||||
override val feeType: FeeType,
|
override val feeType: FeeType,
|
||||||
) : TxSentFrom("Swap"), TxData
|
) : TxSentFrom("Swap"), TxData
|
||||||
|
|
||||||
|
data class Staking(
|
||||||
|
override val blockchain: String,
|
||||||
|
override val token: String,
|
||||||
|
override val feeType: FeeType,
|
||||||
|
) : TxSentFrom("Staking"), TxData
|
||||||
|
|
||||||
data class Approve(
|
data class Approve(
|
||||||
override val blockchain: String,
|
override val blockchain: String,
|
||||||
override val token: String,
|
override val token: String,
|
||||||
|
|
@ -131,7 +137,7 @@ sealed class AnalyticsParam {
|
||||||
|
|
||||||
companion object Key {
|
companion object Key {
|
||||||
const val BLOCKCHAIN = "blockchain"
|
const val BLOCKCHAIN = "blockchain"
|
||||||
const val TOKEN = "Token"
|
const val TOKEN_PARAM = "Token"
|
||||||
const val SOURCE = "Source"
|
const val SOURCE = "Source"
|
||||||
const val BALANCE = "Balance"
|
const val BALANCE = "Balance"
|
||||||
const val STATE = "State"
|
const val STATE = "State"
|
||||||
|
|
@ -152,5 +158,12 @@ sealed class AnalyticsParam {
|
||||||
const val VALIDATION = "Validation"
|
const val VALIDATION = "Validation"
|
||||||
const val BLOCKCHAIN_EXCEPTION_HOST = "exception_host"
|
const val BLOCKCHAIN_EXCEPTION_HOST = "exception_host"
|
||||||
const val BLOCKCHAIN_SELECTED_HOST = "selected_host"
|
const val BLOCKCHAIN_SELECTED_HOST = "selected_host"
|
||||||
|
|
||||||
|
// region swap
|
||||||
|
const val TOKEN_CATEGORY = "Token"
|
||||||
|
const val STATUS = "Status"
|
||||||
|
const val PROVIDER = "Provider"
|
||||||
|
const val PLACE = "Place"
|
||||||
|
//
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +55,7 @@ sealed class Basic(
|
||||||
this[AnalyticsParam.SOURCE] = sentFrom.value
|
this[AnalyticsParam.SOURCE] = sentFrom.value
|
||||||
if (sentFrom is AnalyticsParam.TxData) {
|
if (sentFrom is AnalyticsParam.TxData) {
|
||||||
this[AnalyticsParam.BLOCKCHAIN] = sentFrom.blockchain
|
this[AnalyticsParam.BLOCKCHAIN] = sentFrom.blockchain
|
||||||
this[AnalyticsParam.TOKEN] = sentFrom.token
|
this[AnalyticsParam.TOKEN_PARAM] = sentFrom.token
|
||||||
this[AnalyticsParam.FEE_TYPE] = sentFrom.feeType.value
|
this[AnalyticsParam.FEE_TYPE] = sentFrom.feeType.value
|
||||||
}
|
}
|
||||||
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
|
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.core.analytics
|
||||||
|
|
||||||
|
interface AppInstanceIdProvider {
|
||||||
|
|
||||||
|
suspend fun getAppInstanceId(): String?
|
||||||
|
|
||||||
|
fun getAppInstanceIdSync(): String?
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.tangem.core.analytics
|
||||||
|
|
||||||
|
class DummyAppInstanceIdProvider : AppInstanceIdProvider {
|
||||||
|
|
||||||
|
override suspend fun getAppInstanceId(): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAppInstanceIdSync(): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,8 +8,11 @@ import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardClaimingDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardClaimingDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardScheduleDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardScheduleDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.RewardTypeDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.RewardTypeDTO
|
||||||
|
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.ValidatorDTO.ValidatorStatusDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
|
||||||
|
import com.tangem.datasource.api.stakekit.models.response.model.error.AccessDeniedErrorTypeDTO
|
||||||
|
import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitErrorMessageDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionStatusDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionStatusDTO
|
||||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionTypeDTO
|
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionTypeDTO
|
||||||
|
|
||||||
|
|
@ -26,6 +29,7 @@ object UnknownEnumMoshiAdapter {
|
||||||
|
|
||||||
fun Moshi.Builder.addStakeKitEnumFallbackAdapters(): Moshi.Builder {
|
fun Moshi.Builder.addStakeKitEnumFallbackAdapters(): Moshi.Builder {
|
||||||
val map = mapOf(
|
val map = mapOf(
|
||||||
|
// valid response enums
|
||||||
BalanceTypeDTO::class.java to BalanceTypeDTO.UNKNOWN,
|
BalanceTypeDTO::class.java to BalanceTypeDTO.UNKNOWN,
|
||||||
NetworkTypeDTO::class.java to NetworkTypeDTO.UNKNOWN,
|
NetworkTypeDTO::class.java to NetworkTypeDTO.UNKNOWN,
|
||||||
RewardClaimingDTO::class.java to RewardClaimingDTO.UNKNOWN,
|
RewardClaimingDTO::class.java to RewardClaimingDTO.UNKNOWN,
|
||||||
|
|
@ -35,6 +39,10 @@ fun Moshi.Builder.addStakeKitEnumFallbackAdapters(): Moshi.Builder {
|
||||||
StakingActionTypeDTO::class.java to StakingActionTypeDTO.UNKNOWN,
|
StakingActionTypeDTO::class.java to StakingActionTypeDTO.UNKNOWN,
|
||||||
StakingTransactionStatusDTO::class.java to StakingTransactionStatusDTO.UNKNOWN,
|
StakingTransactionStatusDTO::class.java to StakingTransactionStatusDTO.UNKNOWN,
|
||||||
StakingTransactionTypeDTO::class.java to StakingTransactionTypeDTO.UNKNOWN,
|
StakingTransactionTypeDTO::class.java to StakingTransactionTypeDTO.UNKNOWN,
|
||||||
|
ValidatorStatusDTO::class.java to ValidatorStatusDTO.UNKNOWN,
|
||||||
|
// error enums
|
||||||
|
AccessDeniedErrorTypeDTO::class.java to AccessDeniedErrorTypeDTO.UNKNOWN,
|
||||||
|
StakeKitErrorMessageDTO::class.java to StakeKitErrorMessageDTO.UNKNOWN,
|
||||||
)
|
)
|
||||||
|
|
||||||
return apply {
|
return apply {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.tangem.datasource.api.markets.models.response
|
package com.tangem.datasource.api.markets.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class TokenMarketChartResponse(
|
data class TokenMarketChartResponse(
|
||||||
@Json(name = "prices")
|
@Json(name = "prices")
|
||||||
val prices: Map<Long, BigDecimal>,
|
val prices: Map<Long, BigDecimal>,
|
||||||
|
|
|
||||||
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