Updated on 2026-08-14
This commit is contained in:
commit
71748eff67
517 changed files with 14212 additions and 3419 deletions
|
|
@ -18,6 +18,7 @@ android {
|
||||||
namespace = "com.tangem.wallet"
|
namespace = "com.tangem.wallet"
|
||||||
testOptions {
|
testOptions {
|
||||||
animationsDisabled = true
|
animationsDisabled = true
|
||||||
|
execution = "ANDROIDX_TEST_ORCHESTRATOR"
|
||||||
}
|
}
|
||||||
packaging {
|
packaging {
|
||||||
jniLibs {
|
jniLibs {
|
||||||
|
|
@ -71,6 +72,10 @@ configurations.all {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configurations.androidTestImplementation {
|
||||||
|
exclude(module = "protobuf-lite")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.domain.legacy)
|
implementation(projects.domain.legacy)
|
||||||
|
|
@ -114,6 +119,8 @@ dependencies {
|
||||||
implementation(projects.domain.notifications)
|
implementation(projects.domain.notifications)
|
||||||
implementation(projects.domain.notifications.models)
|
implementation(projects.domain.notifications.models)
|
||||||
implementation(projects.domain.notifications.toggles)
|
implementation(projects.domain.notifications.toggles)
|
||||||
|
implementation(projects.domain.swap.models)
|
||||||
|
implementation(projects.domain.swap)
|
||||||
|
|
||||||
implementation(projects.common)
|
implementation(projects.common)
|
||||||
implementation(projects.common.routing)
|
implementation(projects.common.routing)
|
||||||
|
|
@ -160,6 +167,7 @@ dependencies {
|
||||||
implementation(projects.data.quotes)
|
implementation(projects.data.quotes)
|
||||||
implementation(projects.data.blockaid)
|
implementation(projects.data.blockaid)
|
||||||
implementation(projects.data.notifications)
|
implementation(projects.data.notifications)
|
||||||
|
implementation(projects.data.swap)
|
||||||
|
|
||||||
/** Features */
|
/** Features */
|
||||||
implementation(projects.features.referral.impl)
|
implementation(projects.features.referral.impl)
|
||||||
|
|
@ -170,6 +178,8 @@ dependencies {
|
||||||
implementation(projects.features.swap.domain)
|
implementation(projects.features.swap.domain)
|
||||||
implementation(projects.features.swap.domain.api)
|
implementation(projects.features.swap.domain.api)
|
||||||
implementation(projects.features.swap.data)
|
implementation(projects.features.swap.data)
|
||||||
|
implementation(projects.features.swapV2.api)
|
||||||
|
implementation(projects.features.swapV2.impl)
|
||||||
implementation(projects.features.tester.api)
|
implementation(projects.features.tester.api)
|
||||||
implementation(projects.features.tester.impl)
|
implementation(projects.features.tester.impl)
|
||||||
implementation(projects.features.wallet.api)
|
implementation(projects.features.wallet.api)
|
||||||
|
|
@ -210,8 +220,12 @@ dependencies {
|
||||||
implementation(projects.features.walletconnect.impl)
|
implementation(projects.features.walletconnect.impl)
|
||||||
implementation(projects.features.usedesk.api)
|
implementation(projects.features.usedesk.api)
|
||||||
implementation(projects.features.usedesk.impl)
|
implementation(projects.features.usedesk.impl)
|
||||||
implementation(projects.features.feeSelector.api)
|
implementation(projects.features.hotWallet.api)
|
||||||
implementation(projects.features.feeSelector.impl)
|
implementation(projects.features.hotWallet.impl)
|
||||||
|
implementation(projects.features.kyc.api)
|
||||||
|
implementation(projects.features.kyc.impl)
|
||||||
|
implementation(projects.features.welcome.api)
|
||||||
|
implementation(projects.features.welcome.impl)
|
||||||
|
|
||||||
/** AndroidX libraries */
|
/** AndroidX libraries */
|
||||||
implementation(deps.androidx.core.ktx)
|
implementation(deps.androidx.core.ktx)
|
||||||
|
|
@ -310,19 +324,13 @@ dependencies {
|
||||||
testImplementation(deps.test.mockk)
|
testImplementation(deps.test.mockk)
|
||||||
testImplementation(deps.test.truth)
|
testImplementation(deps.test.truth)
|
||||||
androidTestImplementation(deps.test.junit.android)
|
androidTestImplementation(deps.test.junit.android)
|
||||||
androidTestImplementation(deps.test.espresso){
|
androidTestImplementation(deps.test.espresso)
|
||||||
exclude(group = "com.google.protobuf", module = "protobuf-lite") //conflicting with firebasePerf
|
|
||||||
}
|
|
||||||
androidTestImplementation(deps.test.espresso.intents)
|
androidTestImplementation(deps.test.espresso.intents)
|
||||||
{
|
androidTestImplementation(deps.test.kaspresso)
|
||||||
exclude(group = "com.google.protobuf", module = "protobuf-lite") //conflicting with firebasePerf
|
androidTestImplementation(deps.test.kaspresso.compose)
|
||||||
}
|
|
||||||
androidTestImplementation(deps.test.compose.junit)
|
androidTestImplementation(deps.test.compose.junit)
|
||||||
androidTestImplementation(deps.test.hamcrest)
|
androidTestImplementation(deps.test.hamcrest)
|
||||||
androidTestImplementation(deps.test.hilt)
|
androidTestImplementation(deps.test.hilt)
|
||||||
androidTestImplementation(deps.test.ultron.android)
|
|
||||||
androidTestImplementation(deps.test.ultron.compose)
|
|
||||||
androidTestImplementation(deps.test.ultron.allure)
|
|
||||||
kaptAndroidTest(deps.test.hilt.compiler)
|
kaptAndroidTest(deps.test.hilt.compiler)
|
||||||
|
|
||||||
/** Chucker */
|
/** Chucker */
|
||||||
|
|
@ -340,6 +348,8 @@ dependencies {
|
||||||
implementation(deps.listenableFuture)
|
implementation(deps.listenableFuture)
|
||||||
implementation(deps.mlKit.barcodeScanning)
|
implementation(deps.mlKit.barcodeScanning)
|
||||||
|
|
||||||
|
androidTestUtil(deps.test.orchestrator)
|
||||||
|
|
||||||
/** Leakcanary */
|
/** Leakcanary */
|
||||||
debugImplementation(deps.leakcanary)
|
debugImplementation(deps.leakcanary)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,68 @@
|
||||||
package com.tangem.common
|
package com.tangem.common
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.datastore.dataStoreFile
|
||||||
|
import androidx.test.espresso.intent.Intents
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.rule.GrantPermissionRule
|
import androidx.test.rule.GrantPermissionRule
|
||||||
import com.atiurin.ultron.core.compose.config.UltronComposeConfig
|
import com.kaspersky.components.composesupport.config.withComposeSupport
|
||||||
import com.atiurin.ultron.core.compose.createUltronComposeRule
|
import com.kaspersky.kaspresso.kaspresso.Kaspresso
|
||||||
import com.atiurin.ultron.core.compose.listeners.ComposDebugListener
|
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
|
||||||
import com.atiurin.ultron.core.config.UltronCommonConfig
|
|
||||||
import com.atiurin.ultron.core.config.UltronConfig
|
|
||||||
import com.atiurin.ultron.core.test.UltronTest
|
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.sdk.api.TangemSdkManager
|
import com.tangem.sdk.api.TangemSdkManager
|
||||||
import com.tangem.tap.MainActivity
|
import com.tangem.tap.MainActivity
|
||||||
import dagger.hilt.android.testing.HiltAndroidRule
|
import dagger.hilt.android.testing.HiltAndroidRule
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.BeforeClass
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
import org.junit.rules.RuleChain
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.RunWith
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
abstract class BaseTestCase : UltronTest() {
|
abstract class BaseTestCase : TestCase(
|
||||||
|
kaspressoBuilder = Kaspresso.Builder.withComposeSupport()
|
||||||
|
) {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var tangemSdkManager: TangemSdkManager
|
lateinit var tangemSdkManager: TangemSdkManager
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appPreferencesStore: AppPreferencesStore
|
lateinit var appPreferencesStore: AppPreferencesStore
|
||||||
|
|
||||||
@get:Rule(order = 0)
|
private val hiltRule = HiltAndroidRule(this)
|
||||||
val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
|
private val permissionRule = GrantPermissionRule.grant(
|
||||||
Manifest.permission.POST_NOTIFICATIONS,
|
Manifest.permission.POST_NOTIFICATIONS,
|
||||||
Manifest.permission.CAMERA
|
Manifest.permission.CAMERA,
|
||||||
)
|
)
|
||||||
@get:Rule(order = 1)
|
val composeTestRule = createAndroidComposeRule<MainActivity>()
|
||||||
val hiltRule = HiltAndroidRule(this)
|
|
||||||
|
|
||||||
@get:Rule (order = 2)
|
@Rule
|
||||||
val injectionRule = ApplicationInjectionExecutionRule()
|
@JvmField
|
||||||
|
val ruleChain: TestRule = RuleChain
|
||||||
|
.outerRule(hiltRule)
|
||||||
|
.around(ApplicationInjectionExecutionRule())
|
||||||
|
.around(permissionRule)
|
||||||
|
.around(composeTestRule)
|
||||||
|
|
||||||
@get:Rule(order = 3)
|
protected fun setupHooks(
|
||||||
val composeRule = createUltronComposeRule<MainActivity>()
|
additionalBeforeSection: () -> Unit = {},
|
||||||
|
additionalAfterSection: () -> Unit = {},
|
||||||
override val beforeTest: () -> Unit = {
|
) = before {
|
||||||
hiltRule.inject()
|
hiltRule.inject()
|
||||||
runBlocking {
|
Intents.init()
|
||||||
delay(INIT_DELAY)
|
additionalBeforeSection()
|
||||||
}
|
}.after {
|
||||||
}
|
additionalAfterSection()
|
||||||
|
Intents.release()
|
||||||
override val afterTest: () -> Unit = {
|
|
||||||
runBlocking {
|
|
||||||
appPreferencesStore.editData { prefs -> prefs.clear() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@BeforeClass
|
private const val INIT_DELAY = 1000L
|
||||||
@JvmStatic
|
|
||||||
fun config() {
|
|
||||||
UltronConfig.applyRecommended()
|
|
||||||
UltronComposeConfig.applyRecommended()
|
|
||||||
UltronCommonConfig.addListener(ComposDebugListener())
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val INIT_DELAY = 2000L
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.common.extensions
|
||||||
|
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
|
||||||
|
fun KNode.clickWithAssertion() {
|
||||||
|
assertIsDisplayed()
|
||||||
|
performClick()
|
||||||
|
}
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package com.tangem.common.extensions
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
|
|
||||||
object TestDataUtils {
|
|
||||||
fun getResourceString(resourceId: Int): String {
|
|
||||||
return InstrumentationRegistry.getInstrumentation().targetContext.resources.getString(resourceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package com.tangem.scenarios
|
|
||||||
|
|
||||||
import com.atiurin.ultron.allure.step.step
|
|
||||||
import com.atiurin.ultron.extensions.assertIsDisplayed
|
|
||||||
import com.atiurin.ultron.extensions.click
|
|
||||||
import com.tangem.domain.models.scan.ProductType
|
|
||||||
import com.tangem.screens.DisclaimerPage
|
|
||||||
import com.tangem.screens.MainPage
|
|
||||||
import com.tangem.screens.StoriesPage
|
|
||||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
|
||||||
|
|
||||||
object MainPageScenario {
|
|
||||||
fun open(productType: ProductType? = null) {
|
|
||||||
if (productType != null) {
|
|
||||||
MockProvider.setMocks(productType)
|
|
||||||
}
|
|
||||||
step("Click on \"Accept\" button") {
|
|
||||||
DisclaimerPage.acceptButton.click()
|
|
||||||
}
|
|
||||||
step("Click on \"Scan\" button emulating scan error") {
|
|
||||||
StoriesPage.scanButton.click()
|
|
||||||
}
|
|
||||||
step("Assert: main is displayed") {
|
|
||||||
MainPage.container.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
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.screens.TestTopBar
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<TestTopBar>(testRule) {
|
||||||
|
step("Close Markets tooltip"){
|
||||||
|
performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package com.tangem.screens
|
|
||||||
|
|
||||||
import androidx.compose.ui.test.hasText
|
|
||||||
import com.atiurin.ultron.page.Page
|
|
||||||
import com.tangem.common.extensions.TestDataUtils.getResourceString
|
|
||||||
import com.tangem.wallet.R
|
|
||||||
|
|
||||||
object DetailsPage : Page<DetailsPage>() {
|
|
||||||
val walletConnectButton = hasText(getResourceString(R.string.wallet_connect_title))
|
|
||||||
val walletNameButton = hasText(getResourceString(R.string.manage_tokens_network_selector_wallet))
|
|
||||||
val scanCardButton = hasText(getResourceString(R.string.scan_card_settings_button))
|
|
||||||
val buyTangemButton = hasText(getResourceString(R.string.details_buy_wallet))
|
|
||||||
val appSettingsButton = hasText(getResourceString(R.string.app_settings_title))
|
|
||||||
val contactSupportButton = hasText(getResourceString(R.string.details_row_title_contact_to_support))
|
|
||||||
val toSButton = hasText(getResourceString(R.string.disclaimer_title))
|
|
||||||
}
|
|
||||||
|
|
@ -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,9 +0,0 @@
|
||||||
package com.tangem.screens
|
|
||||||
|
|
||||||
import androidx.compose.ui.test.hasTestTag
|
|
||||||
import com.atiurin.ultron.page.Page
|
|
||||||
import com.tangem.core.ui.test.TestTags
|
|
||||||
|
|
||||||
object DisclaimerPage : Page<DisclaimerPage>() {
|
|
||||||
val acceptButton = hasTestTag(TestTags.DISCLAIMER_SCREEN_ACCEPT_BUTTON)
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package com.tangem.screens
|
|
||||||
|
|
||||||
import androidx.compose.ui.test.hasTestTag
|
|
||||||
import com.atiurin.ultron.page.Page
|
|
||||||
import com.tangem.core.ui.test.TestTags
|
|
||||||
|
|
||||||
object MainPage : Page<MainPage>() {
|
|
||||||
val container = hasTestTag(TestTags.MAIN_SCREEN)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.core.ui.test.TestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
|
||||||
|
class MainTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<MainTestScreen>(
|
||||||
|
semanticsProvider = semanticsProvider,
|
||||||
|
viewBuilderAction = { hasTestTag(TestTags.MAIN_SCREEN) }
|
||||||
|
)
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package com.tangem.screens
|
|
||||||
|
|
||||||
import androidx.compose.ui.test.hasTestTag
|
|
||||||
import com.atiurin.ultron.page.Page
|
|
||||||
import com.tangem.core.ui.test.TestTags
|
|
||||||
|
|
||||||
object StoriesPage : Page<StoriesPage>() {
|
|
||||||
val scanButton = hasTestTag(TestTags.STORIES_SCREEN_SCAN_BUTTON)
|
|
||||||
val orderButton = hasTestTag(TestTags.STORIES_SCREEN_ORDER_BUTTON)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
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.views.KView
|
||||||
|
import io.github.kakaocup.kakao.text.KButton
|
||||||
|
|
||||||
|
class StoriesTestScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<StoriesTestScreen>(
|
||||||
|
semanticsProvider = semanticsProvider,
|
||||||
|
viewBuilderAction = { hasTestTag(TestTags.STORIES_SCREEN) }
|
||||||
|
) {
|
||||||
|
|
||||||
|
val scanButton: KNode = child {
|
||||||
|
hasTestTag(TestTags.STORIES_SCREEN_SCAN_BUTTON)
|
||||||
|
}
|
||||||
|
|
||||||
|
val orderButton: KNode = child {
|
||||||
|
hasTestTag(TestTags.STORIES_SCREEN_ORDER_BUTTON)
|
||||||
|
}
|
||||||
|
|
||||||
|
val enableNFCAlert: KView = KView {
|
||||||
|
withId(R.id.alertTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
val cancelButton: KButton = KButton {
|
||||||
|
withId(android.R.id.button2)
|
||||||
|
}
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package com.tangem.screens
|
|
||||||
|
|
||||||
import androidx.compose.ui.test.hasTestTag
|
|
||||||
import com.atiurin.ultron.page.Page
|
|
||||||
import com.tangem.core.ui.test.TestTags
|
|
||||||
|
|
||||||
object TopBarPage : Page<TopBarPage>() {
|
|
||||||
val moreButton = hasTestTag(TestTags.MAIN_SCREEN_MORE_BUTTON)
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package com.tangem.screens
|
|
||||||
|
|
||||||
import androidx.compose.ui.test.hasText
|
|
||||||
import com.atiurin.ultron.page.Page
|
|
||||||
import com.tangem.common.extensions.TestDataUtils.getResourceString
|
|
||||||
import com.tangem.wallet.R
|
|
||||||
|
|
||||||
object WalletSettingsPage : Page<WalletSettingsPage>() {
|
|
||||||
val linkMoreCardsButton = hasText(getResourceString(R.string.details_row_title_create_backup))
|
|
||||||
val cardSettingsButton = hasText(getResourceString(R.string.card_settings_title))
|
|
||||||
val referralProgramButton = hasText(getResourceString(R.string.details_referral_title))
|
|
||||||
val forgetWalletButton = hasText(getResourceString(R.string.settings_forget_wallet))
|
|
||||||
}
|
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,138 +1,157 @@
|
||||||
package com.tangem.tests
|
package com.tangem.tests
|
||||||
|
|
||||||
import com.atiurin.ultron.allure.step.step
|
|
||||||
import com.atiurin.ultron.extensions.assertIsDisplayed
|
|
||||||
import com.atiurin.ultron.extensions.assertIsNotDisplayed
|
|
||||||
import com.atiurin.ultron.extensions.click
|
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
import com.tangem.domain.models.scan.ProductType
|
import com.tangem.domain.models.scan.ProductType
|
||||||
import com.tangem.scenarios.MainPageScenario
|
import com.tangem.scenarios.OpenMainScreenScenario
|
||||||
import com.tangem.screens.DetailsPage
|
import com.tangem.screens.DetailsTestScreen
|
||||||
import com.tangem.screens.TopBarPage
|
import com.tangem.screens.TestTopBar
|
||||||
import com.tangem.screens.WalletSettingsPage
|
import com.tangem.screens.WalletSettingsTestScreen
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@HiltAndroidTest
|
@HiltAndroidTest
|
||||||
class DetailsScreenTest : BaseTestCase() {
|
class DetailsScreenTest : BaseTestCase() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun walletWithoutBackupDetails() {
|
fun walletWithoutBackupDetails() =
|
||||||
MainPageScenario.open()
|
setupHooks().run {
|
||||||
step("Open wallet details") {
|
scenario(OpenMainScreenScenario(composeTestRule))
|
||||||
TopBarPage.moreButton.click()
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
step("Assert wallet connect button is visible") {
|
|
||||||
DetailsPage.walletConnectButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert scan card button is visible") {
|
|
||||||
DetailsPage.scanCardButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert buy Tangem card button is visible") {
|
|
||||||
DetailsPage.buyTangemButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert app settings button is visible") {
|
|
||||||
DetailsPage.appSettingsButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert contact support button is visible") {
|
|
||||||
DetailsPage.contactSupportButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert terms or service button is visible") {
|
|
||||||
DetailsPage.toSButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Open wallet settings screen") {
|
|
||||||
DetailsPage.walletNameButton.click()
|
|
||||||
}
|
|
||||||
step("Assert Link more cards button is visible") {
|
|
||||||
WalletSettingsPage.linkMoreCardsButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert Card Settings button is visible") {
|
|
||||||
WalletSettingsPage.cardSettingsButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert Referral program button is visible") {
|
|
||||||
WalletSettingsPage.referralProgramButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert Forget wallet button is visible") {
|
|
||||||
WalletSettingsPage.forgetWalletButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun wallet2Details() {
|
fun wallet2Details() =
|
||||||
MainPageScenario.open(ProductType.Wallet2)
|
setupHooks().run {
|
||||||
step("Open wallet details") {
|
scenario(OpenMainScreenScenario(composeTestRule, ProductType.Wallet2))
|
||||||
TopBarPage.moreButton.click()
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
step("Assert wallet connect button is visible") {
|
|
||||||
DetailsPage.walletConnectButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert scan card button is visible") {
|
|
||||||
DetailsPage.scanCardButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert buy Tangem card button is visible") {
|
|
||||||
DetailsPage.buyTangemButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert app settings button is visible") {
|
|
||||||
DetailsPage.appSettingsButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert contact support button is visible") {
|
|
||||||
DetailsPage.contactSupportButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert terms or service button is visible") {
|
|
||||||
DetailsPage.toSButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Open wallet settings screen") {
|
|
||||||
DetailsPage.walletNameButton.click()
|
|
||||||
}
|
|
||||||
step("Assert Link more cards button does not exist") {
|
|
||||||
WalletSettingsPage.linkMoreCardsButton.assertIsNotDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert Card Settings button is visible") {
|
|
||||||
WalletSettingsPage.cardSettingsButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert Referral program button is visible") {
|
|
||||||
WalletSettingsPage.referralProgramButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert Forget wallet button is visible") {
|
|
||||||
WalletSettingsPage.forgetWalletButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun noteDetails() {
|
fun noteDetails() =
|
||||||
MainPageScenario.open(ProductType.Note)
|
setupHooks().run {
|
||||||
step("Open wallet details") {
|
scenario(OpenMainScreenScenario(composeTestRule, ProductType.Note))
|
||||||
TopBarPage.moreButton.click()
|
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 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
step("Assert wallet connect button does not exist") {
|
|
||||||
DetailsPage.walletConnectButton.assertIsNotDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert scan card button is visible") {
|
|
||||||
DetailsPage.scanCardButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert buy Tangem card button is visible") {
|
|
||||||
DetailsPage.buyTangemButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert app settings button is visible") {
|
|
||||||
DetailsPage.appSettingsButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert contact support button is visible") {
|
|
||||||
DetailsPage.contactSupportButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert terms or service button is visible") {
|
|
||||||
DetailsPage.toSButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Open wallet settings screen") {
|
|
||||||
DetailsPage.walletNameButton.click()
|
|
||||||
}
|
|
||||||
step("Assert Card Settings button is visible") {
|
|
||||||
WalletSettingsPage.cardSettingsButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert Referral program button does not exist") {
|
|
||||||
WalletSettingsPage.referralProgramButton.assertIsNotDisplayed()
|
|
||||||
}
|
|
||||||
step("Assert Forget wallet button is visible") {
|
|
||||||
WalletSettingsPage.forgetWalletButton.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.tests
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.scenarios.OpenMainScreenScenario
|
||||||
|
import com.tangem.tap.MainActivity
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class MainScreenTest : BaseTestCase() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun goToMain() {
|
||||||
|
setupHooks().run {
|
||||||
|
scenario(OpenMainScreenScenario(composeTestRule))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,42 +1,40 @@
|
||||||
package com.tangem.tests
|
package com.tangem.tests
|
||||||
|
|
||||||
import com.atiurin.ultron.allure.step.step
|
|
||||||
import com.atiurin.ultron.extensions.assertIsDisplayed
|
|
||||||
import com.atiurin.ultron.extensions.assertIsNotDisplayed
|
|
||||||
import com.atiurin.ultron.extensions.click
|
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.screens.DisclaimerPage
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
import com.tangem.screens.MainPage
|
import com.tangem.screens.DisclaimerTestScreen
|
||||||
import com.tangem.screens.StoriesPage
|
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 org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@HiltAndroidTest
|
@HiltAndroidTest
|
||||||
class ScanErrorTest : BaseTestCase() {
|
class ScanErrorTest : BaseTestCase() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun goToMain() {
|
fun goToMain() =
|
||||||
step("Click on \"Accept\" button") {
|
setupHooks().run {
|
||||||
DisclaimerPage.acceptButton.click()
|
ComposeScreen.onComposeScreen<DisclaimerTestScreen>(composeTestRule) {
|
||||||
|
step("Click on \"Accept\" button") {
|
||||||
|
acceptButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<StoriesTestScreen>(composeTestRule) {
|
||||||
|
step("Click on \"Scan\" button emulating scan error") {
|
||||||
|
MockProvider.setEmulateError()
|
||||||
|
scanButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
step("Click on \"Scan\" button again without emulating error") {
|
||||||
|
MockProvider.resetEmulateError()
|
||||||
|
scanButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<MainTestScreen>(composeTestRule) {
|
||||||
|
step("Make sure wallet screen is visible") {
|
||||||
|
assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
step("Emulate scan error") {
|
|
||||||
MockProvider.setEmulateError()
|
|
||||||
}
|
|
||||||
step("Click on \"Scan\" button emulating scan error") {
|
|
||||||
StoriesPage.scanButton.click()
|
|
||||||
}
|
|
||||||
step("Assert: Error is displayed") {
|
|
||||||
MainPage.container.assertIsNotDisplayed()
|
|
||||||
}
|
|
||||||
step("Emulate success scan") {
|
|
||||||
MockProvider.resetEmulateError()
|
|
||||||
}
|
|
||||||
step("Click on \"Scan\" button") {
|
|
||||||
StoriesPage.scanButton.click()
|
|
||||||
}
|
|
||||||
step("Assert: wallet screen is displayed") {
|
|
||||||
MainPage.container.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,36 +1,41 @@
|
||||||
package com.tangem.tests
|
package com.tangem.tests
|
||||||
|
|
||||||
import android.content.Intent.ACTION_VIEW
|
import android.content.Intent.ACTION_VIEW
|
||||||
import androidx.test.espresso.intent.Intents
|
import androidx.test.espresso.intent.matcher.UriMatchers
|
||||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
|
||||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasData
|
|
||||||
import com.atiurin.ultron.allure.step.step
|
|
||||||
import com.atiurin.ultron.core.config.UltronConfig.UiAutomator.Companion.uiDevice
|
|
||||||
import com.atiurin.ultron.extensions.click
|
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.screens.DisclaimerPage
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
import com.tangem.screens.StoriesPage
|
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 org.hamcrest.core.AllOf.allOf
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.kakao.intent.KIntent
|
||||||
|
import org.hamcrest.Matchers
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@HiltAndroidTest
|
@HiltAndroidTest
|
||||||
class StoriesTest : BaseTestCase() {
|
class StoriesTest : BaseTestCase() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun checkOrderButton() {
|
fun clickOnOrderButton() =
|
||||||
Intents.init()
|
setupHooks().run {
|
||||||
step("Click Accept on ToS") {
|
ComposeScreen.onComposeScreen<DisclaimerTestScreen>(composeTestRule) {
|
||||||
DisclaimerPage.acceptButton.click()
|
step("Click on \"Accept\" button") {
|
||||||
|
acceptButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ComposeScreen.onComposeScreen<StoriesTestScreen>(composeTestRule) {
|
||||||
|
step("Click on \"Order\" button") {
|
||||||
|
orderButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
step("Assert: browser opened") {
|
||||||
|
val expectedIntent = KIntent {
|
||||||
|
hasAction(ACTION_VIEW)
|
||||||
|
hasData { toString().startsWith(NEW_BUY_WALLET_URL) }
|
||||||
|
}
|
||||||
|
expectedIntent.intended()
|
||||||
|
device.uiDevice.pressBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
step("Click order button ") {
|
|
||||||
StoriesPage.orderButton.click()
|
|
||||||
}
|
|
||||||
step("Assert: browser is opened ") {
|
|
||||||
Intents.intended(allOf(hasAction(ACTION_VIEW), hasData(NEW_BUY_WALLET_URL)))
|
|
||||||
uiDevice.pressBack()
|
|
||||||
Intents.release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -45,7 +45,14 @@
|
||||||
android:roundIcon="@mipmap/ic_launcher"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
tools:ignore="GoogleAppIndexingWarning"
|
tools:ignore="GoogleAppIndexingWarning"
|
||||||
tools:replace="android:allowBackup, android:fullBackupContent, android:label">
|
tools:replace="android:allowBackup, android:fullBackupContent, android:label, android:largeHeap">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
property to make the "android:screenOrientation" work on API <37
|
||||||
|
https://developer.android.com/about/versions/16/behavior-changes-16
|
||||||
|
-->
|
||||||
|
<property android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"
|
||||||
|
android:value="true" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.gms.wallet.api.enabled"
|
android:name="com.google.android.gms.wallet.api.enabled"
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ import com.tangem.tap.routing.utils.DeepLinkFactory
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||||
import com.tangem.utils.extensions.uriValidate
|
import com.tangem.utils.extensions.uriValidate
|
||||||
|
import com.tangem.wallet.BuildConfig
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
@ -353,7 +354,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
intentProcessor.removeAll()
|
intentProcessor.removeAll()
|
||||||
// workaround: kill process when activity destroy to avoid state when lock() wallets
|
// workaround: kill process when activity destroy to avoid state when lock() wallets
|
||||||
// and navigation to unlock screen was skipped because system kills activity but not process
|
// and navigation to unlock screen was skipped because system kills activity but not process
|
||||||
android.os.Process.killProcess(android.os.Process.myPid())
|
if (BuildConfig.BUILD_TYPE != MOCKED_BUILD_TYPE) {
|
||||||
|
android.os.Process.killProcess(android.os.Process.myPid())
|
||||||
|
}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -556,5 +559,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val APP_THEME_LOAD_TIMEOUT = 2
|
private const val APP_THEME_LOAD_TIMEOUT = 2
|
||||||
|
private const val MOCKED_BUILD_TYPE = "mocked"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,11 +3,9 @@ package com.tangem.tap.data
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
import com.tangem.common.services.secure.SecureStorage
|
|
||||||
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
||||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||||
import com.tangem.sdk.storage.AndroidSecureStorage
|
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||||
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -21,11 +19,9 @@ internal class DefaultVisaAuthTokenStorage @Inject constructor(
|
||||||
) : VisaAuthTokenStorage {
|
) : VisaAuthTokenStorage {
|
||||||
|
|
||||||
private val secureStorage by lazy {
|
private val secureStorage by lazy {
|
||||||
AndroidSecureStorage(
|
AndroidSecureStorageV2(
|
||||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
appContext = applicationContext,
|
||||||
context = applicationContext,
|
name = "visa_auth_storage",
|
||||||
storageName = "visa_auth_storage",
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,9 @@ package com.tangem.tap.data
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.tangem.common.extensions.toByteArray
|
import com.tangem.common.extensions.toByteArray
|
||||||
import com.tangem.common.extensions.toInt
|
import com.tangem.common.extensions.toInt
|
||||||
import com.tangem.common.services.secure.SecureStorage
|
|
||||||
import com.tangem.datasource.local.visa.VisaOTPStorage
|
import com.tangem.datasource.local.visa.VisaOTPStorage
|
||||||
import com.tangem.datasource.local.visa.VisaOtpData
|
import com.tangem.datasource.local.visa.VisaOtpData
|
||||||
import com.tangem.sdk.storage.AndroidSecureStorage
|
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||||
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -21,11 +19,9 @@ class DefaultVisaOTPStorage @Inject constructor(
|
||||||
) : VisaOTPStorage {
|
) : VisaOTPStorage {
|
||||||
|
|
||||||
private val secureStorage by lazy {
|
private val secureStorage by lazy {
|
||||||
AndroidSecureStorage(
|
AndroidSecureStorageV2(
|
||||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
appContext = applicationContext,
|
||||||
context = applicationContext,
|
name = "visa_otp_storage",
|
||||||
storageName = "visa_otp_storage",
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,10 @@ internal object RootAppComponentContextModule {
|
||||||
componentBuilder: ModelComponent.Builder,
|
componentBuilder: ModelComponent.Builder,
|
||||||
@GlobalUiMessageSender messageSender: UiMessageSender,
|
@GlobalUiMessageSender messageSender: UiMessageSender,
|
||||||
): AppComponentContext {
|
): AppComponentContext {
|
||||||
|
val activity = context as AppCompatActivity
|
||||||
return DefaultAppComponentContext(
|
return DefaultAppComponentContext(
|
||||||
componentContext = (context as AppCompatActivity).defaultComponentContext(),
|
componentContext = activity.defaultComponentContext(),
|
||||||
|
activity = activity,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
hiltComponentBuilder = componentBuilder,
|
hiltComponentBuilder = componentBuilder,
|
||||||
messageSender = messageSender,
|
messageSender = messageSender,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import com.tangem.domain.managetokens.*
|
||||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
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.networks.multi.MultiNetworkStatusFetcher
|
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||||
import com.tangem.domain.staking.repositories.StakingRepository
|
import com.tangem.domain.staking.repositories.StakingRepository
|
||||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||||
|
|
@ -36,8 +36,10 @@ internal object ManageTokensDomainModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideCreateCurrencyUseCase(customTokensRepository: CustomTokensRepository): CreateCurrencyUseCase {
|
fun provideCreateCryptoCurrencyUseCase(
|
||||||
return CreateCurrencyUseCase(customTokensRepository)
|
customTokensRepository: CustomTokensRepository,
|
||||||
|
): CreateCryptoCurrencyUseCase {
|
||||||
|
return CreateCryptoCurrencyUseCase(customTokensRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
@ -71,7 +73,7 @@ internal object ManageTokensDomainModule {
|
||||||
derivationsRepository: DerivationsRepository,
|
derivationsRepository: DerivationsRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||||
tokensFeatureToggles: TokensFeatureToggles,
|
tokensFeatureToggles: TokensFeatureToggles,
|
||||||
): SaveManagedTokensUseCase {
|
): SaveManagedTokensUseCase {
|
||||||
|
|
@ -82,7 +84,7 @@ internal object ManageTokensDomainModule {
|
||||||
derivationsRepository = derivationsRepository,
|
derivationsRepository = derivationsRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher = multiQuoteFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||||
tokensFeatureToggles = tokensFeatureToggles,
|
tokensFeatureToggles = tokensFeatureToggles,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import com.tangem.domain.markets.*
|
||||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||||
import com.tangem.domain.promo.PromoRepository
|
import com.tangem.domain.promo.PromoRepository
|
||||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||||
import com.tangem.domain.staking.repositories.StakingRepository
|
import com.tangem.domain.staking.repositories.StakingRepository
|
||||||
|
|
@ -52,8 +52,8 @@ object MarketsDomainModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGetTokenQuotesUseCase(singleQuoteSupplier: SingleQuoteSupplier): GetCurrencyQuotesUseCase {
|
fun provideGetTokenQuotesUseCase(singleQuoteStatusSupplier: SingleQuoteStatusSupplier): GetCurrencyQuotesUseCase {
|
||||||
return GetCurrencyQuotesUseCase(singleQuoteSupplier = singleQuoteSupplier)
|
return GetCurrencyQuotesUseCase(singleQuoteStatusSupplier = singleQuoteStatusSupplier)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
@ -64,7 +64,7 @@ object MarketsDomainModule {
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||||
tokensFeatureToggles: TokensFeatureToggles,
|
tokensFeatureToggles: TokensFeatureToggles,
|
||||||
): SaveMarketTokensUseCase {
|
): SaveMarketTokensUseCase {
|
||||||
|
|
@ -74,7 +74,7 @@ object MarketsDomainModule {
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher = multiQuoteFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||||
tokensFeatureToggles = tokensFeatureToggles,
|
tokensFeatureToggles = tokensFeatureToggles,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package com.tangem.tap.di.domain
|
||||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||||
import com.tangem.domain.nft.*
|
import com.tangem.domain.nft.*
|
||||||
import com.tangem.domain.nft.repository.NFTRepository
|
import com.tangem.domain.nft.repository.NFTRepository
|
||||||
import com.tangem.domain.quotes.single.SingleQuoteFetcher
|
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
|
||||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
|
@ -92,18 +92,18 @@ internal object NFTDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGetNFTPriceUseCase(
|
fun provideGetNFTPriceUseCase(
|
||||||
nftRepository: NFTRepository,
|
nftRepository: NFTRepository,
|
||||||
singleQuoteSupplier: SingleQuoteSupplier,
|
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||||
): GetNFTPriceUseCase {
|
): GetNFTPriceUseCase {
|
||||||
return GetNFTPriceUseCase(nftRepository, singleQuoteSupplier)
|
return GetNFTPriceUseCase(nftRepository, singleQuoteStatusSupplier)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideFetchNFTPriceUseCase(
|
fun provideFetchNFTPriceUseCase(
|
||||||
nftRepository: NFTRepository,
|
nftRepository: NFTRepository,
|
||||||
singleQuoteFetcher: SingleQuoteFetcher,
|
singleQuoteStatusFetcher: SingleQuoteStatusFetcher,
|
||||||
): FetchNFTPriceUseCase {
|
): FetchNFTPriceUseCase {
|
||||||
return FetchNFTPriceUseCase(nftRepository, singleQuoteFetcher)
|
return FetchNFTPriceUseCase(nftRepository, singleQuoteStatusFetcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
|
import com.tangem.domain.swap.SwapErrorResolver
|
||||||
|
import com.tangem.domain.swap.SwapRepositoryV2
|
||||||
|
import com.tangem.domain.swap.SwapTransactionRepository
|
||||||
|
import com.tangem.domain.swap.usecase.GetSwapPairsUseCase
|
||||||
|
import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase
|
||||||
|
import com.tangem.domain.swap.usecase.GetSwapSupportedPairsUseCase
|
||||||
|
import com.tangem.domain.swap.usecase.SelectInitialPairUseCase
|
||||||
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
|
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
|
||||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
import com.tangem.feature.swap.domain.api.SwapRepository as OldSwapRepository
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
|
|
@ -17,7 +24,53 @@ internal object SwapDomainModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGetAvailablePairsUseCase(swapRepository: SwapRepository): GetAvailablePairsUseCase {
|
fun provideGetAvailablePairsUseCase(swapRepository: OldSwapRepository): GetAvailablePairsUseCase {
|
||||||
return GetAvailablePairsUseCase(swapRepository = swapRepository)
|
return GetAvailablePairsUseCase(swapRepository = swapRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetSwapSupportedPairsUseCase(
|
||||||
|
swapRepositoryV2: SwapRepositoryV2,
|
||||||
|
swapErrorResolver: SwapErrorResolver,
|
||||||
|
): GetSwapSupportedPairsUseCase {
|
||||||
|
return GetSwapSupportedPairsUseCase(
|
||||||
|
swapRepositoryV2 = swapRepositoryV2,
|
||||||
|
swapErrorResolver = swapErrorResolver,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetSwapPairsUseCase(
|
||||||
|
swapRepositoryV2: SwapRepositoryV2,
|
||||||
|
swapErrorResolver: SwapErrorResolver,
|
||||||
|
): GetSwapPairsUseCase {
|
||||||
|
return GetSwapPairsUseCase(
|
||||||
|
swapRepositoryV2 = swapRepositoryV2,
|
||||||
|
swapErrorResolver = swapErrorResolver,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSelectInitialPairUseCase(
|
||||||
|
swapTransactionRepository: SwapTransactionRepository,
|
||||||
|
): SelectInitialPairUseCase {
|
||||||
|
return SelectInitialPairUseCase(
|
||||||
|
swapTransactionRepository = swapTransactionRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetSwapQuoteUseCase(
|
||||||
|
swapRepositoryV2: SwapRepositoryV2,
|
||||||
|
swapErrorResolver: SwapErrorResolver,
|
||||||
|
): GetSwapQuoteUseCase {
|
||||||
|
return GetSwapQuoteUseCase(
|
||||||
|
swapRepositoryV2 = swapRepositoryV2,
|
||||||
|
swapErrorResolver = swapErrorResolver,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,9 +8,9 @@ import com.tangem.domain.networks.repository.NetworksRepository
|
||||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||||
import com.tangem.domain.promo.PromoRepository
|
import com.tangem.domain.promo.PromoRepository
|
||||||
import com.tangem.domain.quotes.QuotesRepositoryV2
|
import com.tangem.domain.quotes.QuotesRepository
|
||||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||||
import com.tangem.domain.staking.repositories.StakingRepository
|
import com.tangem.domain.staking.repositories.StakingRepository
|
||||||
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
||||||
|
|
@ -42,7 +42,7 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||||
tokensFeatureToggles: TokensFeatureToggles,
|
tokensFeatureToggles: TokensFeatureToggles,
|
||||||
): AddCryptoCurrenciesUseCase {
|
): AddCryptoCurrenciesUseCase {
|
||||||
|
|
@ -50,7 +50,7 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher = multiQuoteFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||||
tokensFeatureToggles = tokensFeatureToggles,
|
tokensFeatureToggles = tokensFeatureToggles,
|
||||||
)
|
)
|
||||||
|
|
@ -62,7 +62,7 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||||
tokensFeatureToggles: TokensFeatureToggles,
|
tokensFeatureToggles: TokensFeatureToggles,
|
||||||
): FetchTokenListUseCase {
|
): FetchTokenListUseCase {
|
||||||
|
|
@ -70,7 +70,7 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher = multiQuoteFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||||
tokensFeatureToggles = tokensFeatureToggles,
|
tokensFeatureToggles = tokensFeatureToggles,
|
||||||
)
|
)
|
||||||
|
|
@ -161,7 +161,7 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||||
tokensFeatureToggles: TokensFeatureToggles,
|
tokensFeatureToggles: TokensFeatureToggles,
|
||||||
): FetchCurrencyStatusUseCase {
|
): FetchCurrencyStatusUseCase {
|
||||||
|
|
@ -169,7 +169,7 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||||
multiQuoteFetcher = multiQuoteFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||||
tokensFeatureToggles = tokensFeatureToggles,
|
tokensFeatureToggles = tokensFeatureToggles,
|
||||||
)
|
)
|
||||||
|
|
@ -181,7 +181,7 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||||
tokensFeatureToggles: TokensFeatureToggles,
|
tokensFeatureToggles: TokensFeatureToggles,
|
||||||
): FetchCardTokenListUseCase {
|
): FetchCardTokenListUseCase {
|
||||||
|
|
@ -189,7 +189,7 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher = multiQuoteFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||||
tokensFeatureToggles = tokensFeatureToggles,
|
tokensFeatureToggles = tokensFeatureToggles,
|
||||||
)
|
)
|
||||||
|
|
@ -346,11 +346,11 @@ internal object TokensDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideRefreshMultiCurrencyWalletQuotesUseCase(
|
fun provideRefreshMultiCurrencyWalletQuotesUseCase(
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
): RefreshMultiCurrencyWalletQuotesUseCase {
|
): RefreshMultiCurrencyWalletQuotesUseCase {
|
||||||
return RefreshMultiCurrencyWalletQuotesUseCase(
|
return RefreshMultiCurrencyWalletQuotesUseCase(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
multiQuoteFetcher = multiQuoteFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,27 +368,27 @@ internal object TokensDomainModule {
|
||||||
fun provideBaseCurrenciesStatusesOperations(
|
fun provideBaseCurrenciesStatusesOperations(
|
||||||
tokensFeatureToggles: TokensFeatureToggles,
|
tokensFeatureToggles: TokensFeatureToggles,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
quotesRepositoryV2: QuotesRepositoryV2,
|
quotesRepository: QuotesRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
singleQuoteSupplier: SingleQuoteSupplier,
|
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||||
): BaseCurrenciesStatusesOperations {
|
): BaseCurrenciesStatusesOperations {
|
||||||
return CachedCurrenciesStatusesOperations(
|
return CachedCurrenciesStatusesOperations(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
quotesRepositoryV2 = quotesRepositoryV2,
|
quotesRepository = quotesRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||||
multiQuoteFetcher = multiQuoteFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
singleQuoteSupplier = singleQuoteSupplier,
|
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
|
||||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||||
tokensFeatureToggles = tokensFeatureToggles,
|
tokensFeatureToggles = tokensFeatureToggles,
|
||||||
|
|
@ -400,27 +400,27 @@ internal object TokensDomainModule {
|
||||||
fun provideBaseCurrencyStatusOperations(
|
fun provideBaseCurrencyStatusOperations(
|
||||||
tokensFeatureToggles: TokensFeatureToggles,
|
tokensFeatureToggles: TokensFeatureToggles,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
quotesRepositoryV2: QuotesRepositoryV2,
|
quotesRepository: QuotesRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||||
singleQuoteSupplier: SingleQuoteSupplier,
|
singleQuoteStatusSupplier: SingleQuoteStatusSupplier,
|
||||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||||
): BaseCurrencyStatusOperations {
|
): BaseCurrencyStatusOperations {
|
||||||
return CachedCurrenciesStatusesOperations(
|
return CachedCurrenciesStatusesOperations(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
quotesRepositoryV2 = quotesRepositoryV2,
|
quotesRepository = quotesRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||||
multiQuoteFetcher = multiQuoteFetcher,
|
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||||
singleQuoteSupplier = singleQuoteSupplier,
|
singleQuoteStatusSupplier = singleQuoteStatusSupplier,
|
||||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||||
tokensFeatureToggles = tokensFeatureToggles,
|
tokensFeatureToggles = tokensFeatureToggles,
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,18 @@ internal object TransactionDomainModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideOpenTrustlineUseCase(
|
||||||
|
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||||
|
walletManagersFacade: WalletManagersFacade,
|
||||||
|
): OpenTrustlineUseCase {
|
||||||
|
return OpenTrustlineUseCase(
|
||||||
|
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||||
|
walletManagersFacade = walletManagersFacade,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideDismissIncompleteTransactionUseCase(
|
fun provideDismissIncompleteTransactionUseCase(
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.sdk.storage.AndroidSecureStorage
|
import com.tangem.sdk.storage.AndroidSecureStorage
|
||||||
|
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||||
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
||||||
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
|
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
|
||||||
import com.tangem.tap.domain.userWalletList.implementation.GeneralUserWalletsListManager
|
import com.tangem.tap.domain.userWalletList.implementation.GeneralUserWalletsListManager
|
||||||
|
|
@ -80,6 +81,10 @@ internal object UserWalletsListManagerModule {
|
||||||
context = applicationContext,
|
context = applicationContext,
|
||||||
storageName = "user_wallets_storage",
|
storageName = "user_wallets_storage",
|
||||||
),
|
),
|
||||||
|
androidSecureStorageV2 = AndroidSecureStorageV2(
|
||||||
|
appContext = applicationContext,
|
||||||
|
name = "user_wallets_storage2",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val authenticatedStorage = AuthenticatedStorage(
|
val authenticatedStorage = AuthenticatedStorage(
|
||||||
|
|
|
||||||
|
|
@ -83,11 +83,14 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
|
|
||||||
Timber.i("Requesting challenge for wallet authorization")
|
Timber.i("Requesting challenge for wallet authorization")
|
||||||
|
|
||||||
val challengeResponse = visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value)
|
val challengeResponse = visaAuthRepository.getCardWalletAuthChallenge(
|
||||||
.getOrElse {
|
cardId = card.cardId,
|
||||||
Timber.i("Failed to get Access token for Wallet public key authorization")
|
// This is the wallet public key, not the address and it's alright, as the API expects it in this format
|
||||||
return CompletionResult.Failure(it.tangemError)
|
cardWalletAddress = wallet.publicKey.toHexString(),
|
||||||
}
|
).getOrElse {
|
||||||
|
Timber.i("Failed to get Access token for Wallet public key authorization")
|
||||||
|
return CompletionResult.Failure(it.tangemError)
|
||||||
|
}
|
||||||
|
|
||||||
val signChallengeResult = signChallengeWithWallet(
|
val signChallengeResult = signChallengeWithWallet(
|
||||||
publicKey = wallet.publicKey,
|
publicKey = wallet.publicKey,
|
||||||
|
|
@ -96,15 +99,16 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
|
|
||||||
return when (signChallengeResult) {
|
return when (signChallengeResult) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
val signature = signChallengeResult.data.cardSignature ?: run {
|
val signature = signChallengeResult.data.walletSignature
|
||||||
Timber.i("Failed to sign challenge with Wallet public key")
|
val salt = signChallengeResult.data.salt
|
||||||
return CompletionResult.Failure(VisaCardScanError.FailedToSignChallenge.tangemError)
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.i("Challenge signed with Wallet public key")
|
Timber.i("Challenge signed with Wallet public key")
|
||||||
handleWalletAuthorizationTokens(
|
handleWalletAuthorizationTokens(
|
||||||
cardWalletAddress = walletAddress.value,
|
cardWalletAddress = walletAddress.value,
|
||||||
signedChallenge = challengeResponse.toSignedChallenge(signature.toHexString()),
|
signedChallenge = challengeResponse.toSignedChallenge(
|
||||||
|
signedChallenge = signature.toHexString(),
|
||||||
|
salt = salt.toHexString(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import com.tangem.core.ui.utils.findActivity
|
||||||
import com.tangem.tap.common.redux.AppState
|
import com.tangem.tap.common.redux.AppState
|
||||||
import com.tangem.tap.features.home.api.HomeComponent
|
import com.tangem.tap.features.home.api.HomeComponent
|
||||||
import com.tangem.tap.features.home.compose.StoriesScreen
|
import com.tangem.tap.features.home.compose.StoriesScreen
|
||||||
|
import com.tangem.tap.features.home.compose.StoriesScreenV2
|
||||||
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
|
||||||
|
|
@ -57,12 +58,22 @@ internal class DefaultHomeComponent @AssistedInject constructor(
|
||||||
val activity = LocalContext.current.findActivity()
|
val activity = LocalContext.current.findActivity()
|
||||||
BackHandler(onBack = activity::finish)
|
BackHandler(onBack = activity::finish)
|
||||||
SystemBarsIconsDisposable(darkIcons = false)
|
SystemBarsIconsDisposable(darkIcons = false)
|
||||||
StoriesScreen(
|
if (homeState.value.isV2StoriesEnabled) {
|
||||||
homeState = homeState,
|
StoriesScreenV2(
|
||||||
onScanButtonClick = model::onScanClick,
|
homeState = homeState,
|
||||||
onShopButtonClick = model::onShopClick,
|
onCreateNewWalletButtonClick = model::onCreateNewWalletScreen,
|
||||||
onSearchTokensClick = model::onSearchClick,
|
onAddExistingWalletButtonClick = model::onAddExistingWalletScreen,
|
||||||
)
|
onScanButtonClick = model::onScanClick,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
StoriesScreen(
|
||||||
|
homeState = homeState,
|
||||||
|
onScanButtonClick = model::onScanClick,
|
||||||
|
onShopButtonClick = model::onShopClick,
|
||||||
|
onSearchTokensClick = model::onSearchClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
ChangeRootBackgroundColorEffect(Color(color = 0xFF010101))
|
ChangeRootBackgroundColorEffect(Color(color = 0xFF010101))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,14 @@ internal class HomeModel @Inject constructor(
|
||||||
.launchIn(modelScope)
|
.launchIn(modelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onCreateNewWalletScreen() {
|
||||||
|
// TODO implement navigation to create new wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAddExistingWalletScreen() {
|
||||||
|
// TODO implement navigation to add existing wallet
|
||||||
|
}
|
||||||
|
|
||||||
fun onScanClick() {
|
fun onScanClick() {
|
||||||
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard())
|
analyticsEventHandler.send(IntroductionProcess.ButtonScanCard())
|
||||||
scanCard()
|
scanCard()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,252 @@
|
||||||
|
@file:Suppress("MagicNumber")
|
||||||
|
|
||||||
|
package com.tangem.tap.features.home.compose
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.core.ui.test.TestTags
|
||||||
|
import com.tangem.tap.features.home.compose.content.*
|
||||||
|
import com.tangem.tap.features.home.compose.views.HomeButtonsV2
|
||||||
|
import com.tangem.tap.features.home.compose.views.StoriesProgressBar
|
||||||
|
import com.tangem.tap.features.home.redux.HomeState
|
||||||
|
import com.tangem.tap.features.home.redux.Stories
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun StoriesScreenV2(
|
||||||
|
homeState: MutableState<HomeState>,
|
||||||
|
onCreateNewWalletButtonClick: () -> Unit,
|
||||||
|
onAddExistingWalletButtonClick: () -> Unit,
|
||||||
|
onScanButtonClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
val state = homeState.value
|
||||||
|
|
||||||
|
var currentStory by remember { mutableStateOf(state.firstStory) }
|
||||||
|
val currentStoryIndex by rememberUpdatedState(newValue = state.stepOf(currentStory))
|
||||||
|
|
||||||
|
val goToPreviousStory = remember(currentStory, currentStoryIndex) {
|
||||||
|
{ currentStory = state.stories[max(0, currentStoryIndex - 1)] }
|
||||||
|
}
|
||||||
|
val goToNextStory = remember(currentStory, currentStoryIndex) {
|
||||||
|
{
|
||||||
|
currentStory = if (currentStoryIndex < state.stories.lastIndex) {
|
||||||
|
state.stories[currentStoryIndex + 1]
|
||||||
|
} else {
|
||||||
|
state.firstStory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo refactor [REDACTED_TASK_KEY]
|
||||||
|
StoriesScreenContentV2(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.testTag(TestTags.STORIES_SCREEN),
|
||||||
|
config = StoriesScreenContentV2Config(
|
||||||
|
storiesSize = state.stories.lastIndex,
|
||||||
|
currentStoryIndex = currentStoryIndex,
|
||||||
|
currentStory = currentStory,
|
||||||
|
isScanInProgress = homeState.value.scanInProgress,
|
||||||
|
onGoToPreviousStory = goToPreviousStory,
|
||||||
|
onGoToNextStory = goToNextStory,
|
||||||
|
onCreateNewWalletButtonClick = onCreateNewWalletButtonClick,
|
||||||
|
onAddExistingWalletButtonClick = onAddExistingWalletButtonClick,
|
||||||
|
onScanButtonClick = onScanButtonClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use StoriesContainer from core/ui")
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
@Composable
|
||||||
|
private fun StoriesScreenContentV2(config: StoriesScreenContentV2Config, modifier: Modifier = Modifier) {
|
||||||
|
var isPressed by remember { mutableStateOf(value = false) }
|
||||||
|
|
||||||
|
val isPaused = isPressed || config.isScanInProgress
|
||||||
|
val currentStoryDuration = config.currentStory.duration
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier.background(Color(0xFF010101)),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectTapGestures(
|
||||||
|
onPress = {
|
||||||
|
val pressStartTime = System.currentTimeMillis()
|
||||||
|
isPressed = true
|
||||||
|
this.tryAwaitRelease()
|
||||||
|
val pressEndTime = System.currentTimeMillis()
|
||||||
|
val totalPressTime = pressEndTime - pressStartTime
|
||||||
|
if (totalPressTime < 200) config.onGoToPreviousStory()
|
||||||
|
isPressed = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectTapGestures(
|
||||||
|
onPress = {
|
||||||
|
val pressStartTime = System.currentTimeMillis()
|
||||||
|
isPressed = true
|
||||||
|
this.tryAwaitRelease()
|
||||||
|
val pressEndTime = System.currentTimeMillis()
|
||||||
|
val totalPressTime = pressEndTime - pressStartTime
|
||||||
|
if (totalPressTime < 200) config.onGoToNextStory()
|
||||||
|
isPressed = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.statusBarsPadding()
|
||||||
|
.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
StoriesProgressBar(
|
||||||
|
steps = config.storiesSize,
|
||||||
|
currentStep = config.currentStoryIndex,
|
||||||
|
stepDuration = currentStoryDuration,
|
||||||
|
paused = isPaused,
|
||||||
|
onStepFinish = config.onGoToNextStory,
|
||||||
|
)
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.ic_tangem_logo),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.FillHeight,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
start = TangemTheme.dimens.spacing16,
|
||||||
|
top = TangemTheme.dimens.spacing16,
|
||||||
|
)
|
||||||
|
.height(TangemTheme.dimens.size18)
|
||||||
|
.align(Alignment.Start),
|
||||||
|
)
|
||||||
|
when (config.currentStory) {
|
||||||
|
Stories.TangemIntro -> FirstStoriesContent(
|
||||||
|
isPaused = isPaused,
|
||||||
|
duration = currentStoryDuration,
|
||||||
|
)
|
||||||
|
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet()
|
||||||
|
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
|
||||||
|
isPaused = isPaused,
|
||||||
|
stepDuration = currentStoryDuration,
|
||||||
|
)
|
||||||
|
Stories.Currencies -> StoriesCurrencies(isPaused, currentStoryDuration)
|
||||||
|
Stories.Web3 -> StoriesWeb3(isPaused, currentStoryDuration)
|
||||||
|
Stories.WalletForEveryone -> StoriesWalletForEveryone(currentStoryDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.padding(bottom = TangemTheme.dimens.spacing16)
|
||||||
|
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||||
|
) {
|
||||||
|
HomeButtonsV2(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
btnScanStateInProgress = config.isScanInProgress,
|
||||||
|
onScanButtonClick = config.onScanButtonClick,
|
||||||
|
onCreateNewWalletButtonClick = config.onCreateNewWalletButtonClick,
|
||||||
|
onAddExistingWalletButtonClick = config.onAddExistingWalletButtonClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class StoriesScreenContentV2Config(
|
||||||
|
val storiesSize: Int,
|
||||||
|
val currentStoryIndex: Int,
|
||||||
|
val currentStory: Stories,
|
||||||
|
val isScanInProgress: Boolean,
|
||||||
|
val onGoToPreviousStory: () -> Unit = {},
|
||||||
|
val onGoToNextStory: () -> Unit = {},
|
||||||
|
val onCreateNewWalletButtonClick: () -> Unit = {},
|
||||||
|
val onAddExistingWalletButtonClick: () -> Unit = {},
|
||||||
|
val onScanButtonClick: () -> Unit = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
// region Preview
|
||||||
|
@Preview(showBackground = true, widthDp = 360)
|
||||||
|
@Composable
|
||||||
|
private fun StoriesScreenContentV2Preview(
|
||||||
|
@PreviewParameter(StoriesScreenContentV2ConfigProvider::class) config: StoriesScreenContentV2Config,
|
||||||
|
) {
|
||||||
|
TangemThemePreview {
|
||||||
|
StoriesScreenContentV2(config = config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StoriesScreenContentV2ConfigProvider : CollectionPreviewParameterProvider<StoriesScreenContentV2Config>(
|
||||||
|
collection = listOf(
|
||||||
|
StoriesScreenContentV2Config(
|
||||||
|
storiesSize = 6,
|
||||||
|
currentStoryIndex = 0,
|
||||||
|
currentStory = Stories.TangemIntro,
|
||||||
|
isScanInProgress = true,
|
||||||
|
),
|
||||||
|
StoriesScreenContentV2Config(
|
||||||
|
storiesSize = 6,
|
||||||
|
currentStoryIndex = 1,
|
||||||
|
currentStory = Stories.RevolutionaryWallet,
|
||||||
|
isScanInProgress = false,
|
||||||
|
),
|
||||||
|
StoriesScreenContentV2Config(
|
||||||
|
storiesSize = 6,
|
||||||
|
currentStoryIndex = 2,
|
||||||
|
currentStory = Stories.UltraSecureBackup,
|
||||||
|
isScanInProgress = false,
|
||||||
|
),
|
||||||
|
StoriesScreenContentV2Config(
|
||||||
|
storiesSize = 6,
|
||||||
|
currentStoryIndex = 3,
|
||||||
|
currentStory = Stories.Currencies,
|
||||||
|
isScanInProgress = false,
|
||||||
|
),
|
||||||
|
StoriesScreenContentV2Config(
|
||||||
|
storiesSize = 6,
|
||||||
|
currentStoryIndex = 4,
|
||||||
|
currentStory = Stories.Web3,
|
||||||
|
isScanInProgress = false,
|
||||||
|
),
|
||||||
|
StoriesScreenContentV2Config(
|
||||||
|
storiesSize = 6,
|
||||||
|
currentStoryIndex = 5,
|
||||||
|
currentStory = Stories.WalletForEveryone,
|
||||||
|
isScanInProgress = false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// endregion Preview
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
package com.tangem.tap.features.home.compose.views
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||||
|
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.core.ui.test.TestTags
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun HomeButtonsV2(
|
||||||
|
btnScanStateInProgress: Boolean,
|
||||||
|
onScanButtonClick: () -> Unit,
|
||||||
|
onCreateNewWalletButtonClick: () -> Unit,
|
||||||
|
onAddExistingWalletButtonClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
CreateNewWalletButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.testTag(TestTags.STORIES_SCREEN_CREATE_NEW_WALLET_BUTTON),
|
||||||
|
onClick = onCreateNewWalletButtonClick,
|
||||||
|
)
|
||||||
|
AddExistingWalletButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.testTag(TestTags.STORIES_SCREEN_ADD_EXISTING_WALLET_BUTTON),
|
||||||
|
onClick = onAddExistingWalletButtonClick,
|
||||||
|
)
|
||||||
|
ScanCardButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.testTag(TestTags.STORIES_SCREEN_SCAN_BUTTON),
|
||||||
|
showProgress = btnScanStateInProgress,
|
||||||
|
onClick = onScanButtonClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CreateNewWalletButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||||
|
StoriesButton(
|
||||||
|
modifier = modifier,
|
||||||
|
text = stringResourceSafe(id = R.string.home_button_create_new_wallet),
|
||||||
|
useDarkerColors = false,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AddExistingWalletButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||||
|
StoriesButton(
|
||||||
|
modifier = modifier,
|
||||||
|
text = stringResourceSafe(id = R.string.home_button_add_existing_wallet),
|
||||||
|
useDarkerColors = true,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScanCardButton(showProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||||
|
StoriesButton(
|
||||||
|
modifier = modifier,
|
||||||
|
text = stringResourceSafe(id = R.string.home_button_scan),
|
||||||
|
useDarkerColors = true,
|
||||||
|
icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_tangem_24),
|
||||||
|
onClick = onClick,
|
||||||
|
showProgress = showProgress,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Preview
|
||||||
|
@Preview(showBackground = true, widthDp = 360)
|
||||||
|
@Composable
|
||||||
|
private fun HomeButtonsV2Preview(@PreviewParameter(HomeButtonsV2ParameterProvider::class) state: HomeButtonsV2State) {
|
||||||
|
TangemThemePreview {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.background(Color.Black),
|
||||||
|
) {
|
||||||
|
HomeButtonsV2(
|
||||||
|
btnScanStateInProgress = state.btnScanStateInProgress,
|
||||||
|
onCreateNewWalletButtonClick = {},
|
||||||
|
onAddExistingWalletButtonClick = {},
|
||||||
|
onScanButtonClick = {},
|
||||||
|
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HomeButtonsV2ParameterProvider : CollectionPreviewParameterProvider<HomeButtonsV2State>(
|
||||||
|
collection = listOf(
|
||||||
|
HomeButtonsV2State(
|
||||||
|
btnScanStateInProgress = false,
|
||||||
|
),
|
||||||
|
HomeButtonsV2State(
|
||||||
|
btnScanStateInProgress = true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class HomeButtonsV2State(
|
||||||
|
val btnScanStateInProgress: Boolean,
|
||||||
|
)
|
||||||
|
// endregion Preview
|
||||||
|
|
@ -7,6 +7,7 @@ import org.rekotlin.StateType
|
||||||
// todo refactor [REDACTED_TASK_KEY]
|
// todo refactor [REDACTED_TASK_KEY]
|
||||||
data class HomeState(
|
data class HomeState(
|
||||||
val scanInProgress: Boolean = false,
|
val scanInProgress: Boolean = false,
|
||||||
|
val isV2StoriesEnabled: Boolean = false,
|
||||||
val stories: ImmutableList<Stories> = getRestrictedStories().toImmutableList(),
|
val stories: ImmutableList<Stories> = getRestrictedStories().toImmutableList(),
|
||||||
) : StateType {
|
) : StateType {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
FROM ubuntu:24.04
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
ENV ANDROID_HOME=/opt/android-sdk
|
|
||||||
ENV BUNDLE_PATH=vendor/bundle
|
|
||||||
ENV PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools/34.0.0:$BUNDLE_PATH/bin
|
|
||||||
ENV LC_ALL=en_US.UTF-8
|
|
||||||
ENV LANG=en_US.UTF-8
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
locales \
|
|
||||||
openjdk-17-jdk \
|
|
||||||
wget \
|
|
||||||
unzip \
|
|
||||||
curl \
|
|
||||||
git \
|
|
||||||
ruby \
|
|
||||||
ruby-dev \
|
|
||||||
build-essential \
|
|
||||||
jq \
|
|
||||||
&& locale-gen en_US.UTF-8 \
|
|
||||||
&& update-locale LANG=en_US.UTF-8 \
|
|
||||||
&& apt-get clean
|
|
||||||
|
|
||||||
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
|
||||||
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
|
||||||
echo "deb [signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list && \
|
|
||||||
apt-get update && apt-get install -y gh && \
|
|
||||||
apt-get clean
|
|
||||||
|
|
||||||
RUN mkdir -p $ANDROID_HOME/cmdline-tools/latest && \
|
|
||||||
wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -O /tmp/cmdline-tools.zip && \
|
|
||||||
unzip /tmp/cmdline-tools.zip -d $ANDROID_HOME/cmdline-tools/latest && \
|
|
||||||
mv $ANDROID_HOME/cmdline-tools/latest/cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest/ && \
|
|
||||||
rm -rf $ANDROID_HOME/cmdline-tools/latest/cmdline-tools && \
|
|
||||||
rm -f /tmp/cmdline-tools.zip && \
|
|
||||||
yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses && \
|
|
||||||
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-31" "platforms;android-34" "build-tools;34.0.0"
|
|
||||||
|
|
||||||
RUN wget https://github.com/lzhiyong/android-sdk-tools/releases/download/34.0.3/android-sdk-tools-static-aarch64.zip -O /tmp/android-sdk-tools-static-aarch64.zip && \
|
|
||||||
unzip /tmp/android-sdk-tools-static-aarch64.zip -d /tmp/android-sdk-tools-static-arm && \
|
|
||||||
cp -r /tmp/android-sdk-tools-static-arm/build-tools/* $ANDROID_HOME/build-tools/34.0.0/ && \
|
|
||||||
rm -rf /tmp/android-sdk-tools-static-arm /tmp/android-sdk-tools-static-aarch64.zip
|
|
||||||
|
|
||||||
RUN gem install bundler:2.5.23
|
|
||||||
RUN gem install fastlane -v 2.225.0 -N -V
|
|
||||||
RUN gem install fastlane-plugin-firebase_app_distribution -v 0.9.1 -N -V
|
|
||||||
|
|
||||||
COPY ../Gemfile Gemfile.lock ./
|
|
||||||
RUN bundle install --jobs=4 --retry=3 --verbose
|
|
||||||
|
|
||||||
RUN bundle exec fastlane -v
|
|
||||||
|
|
||||||
CMD ["bash"]
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
FROM ubuntu:24.04
|
|
||||||
|
|
||||||
ENV LC_ALL=en_US.UTF-8
|
|
||||||
ENV LANG=en_US.UTF-8
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
locales \
|
|
||||||
curl \
|
|
||||||
git \
|
|
||||||
&& locale-gen en_US.UTF-8 \
|
|
||||||
&& update-locale LANG=en_US.UTF-8 \
|
|
||||||
&& apt-get clean
|
|
||||||
|
|
||||||
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
|
||||||
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
|
||||||
echo "deb [signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list && \
|
|
||||||
apt-get update && apt-get install -y gh && \
|
|
||||||
apt-get clean
|
|
||||||
|
|
||||||
RUN curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \
|
|
||||||
apt-get install -y git-lfs && \
|
|
||||||
git lfs install && \
|
|
||||||
apt-get clean
|
|
||||||
|
|
||||||
CMD ["bash"]
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
org.gradle.jvmargs=-Xmx7g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
|
||||||
org.gradle.parallel=true
|
|
||||||
org.gradle.workers.max=8
|
|
||||||
org.gradle.daemon=false
|
|
||||||
org.gradle.unsafe.configuration-cache=false
|
|
||||||
org.gradle.console=plain
|
|
||||||
org.gradle.caching=false
|
|
||||||
|
|
||||||
android.useAndroidX=true
|
|
||||||
android.nonTransitiveRClass=false
|
|
||||||
android.aapt2FromMavenOverride=/opt/android-sdk/build-tools/34.0.0/aapt2
|
|
||||||
|
|
||||||
kotlin.incremental=true
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -eo pipefail
|
|
||||||
|
|
||||||
# Input args
|
|
||||||
if [ -z "$1" ]; then
|
|
||||||
echo "Error: No base branch argument provided or empty string given."
|
|
||||||
echo "Usage: $0 <base-branch>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
base_branch=$1
|
|
||||||
|
|
||||||
# Initialize arrays to store branch-commit mapping
|
|
||||||
declare -a branches
|
|
||||||
declare -a commits
|
|
||||||
|
|
||||||
# Source refs to find branches from
|
|
||||||
local_refs="refs/heads/releases/*" # For debug and development
|
|
||||||
#TODO remove pre_release branches matching after 5.21 release
|
|
||||||
remote_refs=$(git for-each-ref --sort=-committerdate --format="%(refname:short)" refs/remotes/origin/ | grep -E "origin/([a-zA-Z0-9._-]+_pre_release|releases/[a-zA-Z0-9._-]+)$")
|
|
||||||
|
|
||||||
# Iterate over all remote release branches, sorted by commit date (most recent first)
|
|
||||||
index=0
|
|
||||||
for branch in $remote_refs; do
|
|
||||||
# Find the latest commit that is an ancestor of base_branch using --first-parent strategy
|
|
||||||
candidate_commit=$(git rev-list --first-parent "$branch..$base_branch" | tail -1)
|
|
||||||
|
|
||||||
if [ -n "$candidate_commit" ]; then
|
|
||||||
branches[$index]="$branch"
|
|
||||||
commits[$index]="$candidate_commit"
|
|
||||||
index=$((index + 1))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Debug output of branches and commits
|
|
||||||
echo "Branches and their corresponding commits:" >&2
|
|
||||||
for i in "${!branches[@]}"; do
|
|
||||||
echo "${branches[$i]} -> ${commits[$i]}" >&2
|
|
||||||
done
|
|
||||||
|
|
||||||
# Find the branch with the most recent commit
|
|
||||||
latest_branch=""
|
|
||||||
latest_commit=""
|
|
||||||
for i in "${!branches[@]}"; do
|
|
||||||
branch=${branches[$i]}
|
|
||||||
commit=${commits[$i]}
|
|
||||||
if [ -z "$latest_commit" ] || [ "$(git rev-list --count $latest_commit..$commit)" -gt 0 ]; then
|
|
||||||
latest_branch=$branch
|
|
||||||
latest_commit=$commit
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Output validation
|
|
||||||
if [ -z "$latest_branch" ]; then
|
|
||||||
echo "Error: Can't find the latest 'release/*' branch for the base branch '$base_branch'" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Stripping 'origin/' prefix if needed
|
|
||||||
latest_branch="${latest_branch#origin/}"
|
|
||||||
|
|
||||||
echo "$latest_branch" > "find-latest-release-branch.output"
|
|
||||||
echo "Latest release branch created directly from '$base_branch' or its ancestor: '$latest_branch'"
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=512m -XX:+UseParallelGC -XX:ParallelGCThreads=3 -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
|
||||||
org.gradle.parallel=true
|
|
||||||
org.gradle.workers.max=3
|
|
||||||
org.gradle.daemon=false
|
|
||||||
org.gradle.unsafe.configuration-cache=false
|
|
||||||
org.gradle.console=plain
|
|
||||||
org.gradle.caching=false
|
|
||||||
|
|
||||||
android.useAndroidX=true
|
|
||||||
android.nonTransitiveRClass=false
|
|
||||||
android.aapt2FromMavenOverride=/opt/android-sdk/build-tools/34.0.0/aapt2
|
|
||||||
|
|
||||||
kotlin.incremental=false
|
|
||||||
|
|
@ -33,4 +33,5 @@ dependencies {
|
||||||
implementation(tangemDeps.card.core)
|
implementation(tangemDeps.card.core)
|
||||||
|
|
||||||
implementation(deps.test.junit5)
|
implementation(deps.test.junit5)
|
||||||
|
implementation(deps.test.truth)
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,22 @@
|
||||||
package com.tangem.common.test.data.quote
|
package com.tangem.common.test.data.quote
|
||||||
|
|
||||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||||
import com.tangem.datasource.local.quote.converter.QuoteConverter
|
|
||||||
import com.tangem.domain.models.StatusSource
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.tokens.model.Quote
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.quote.QuoteStatus
|
||||||
|
import com.tangem.utils.extensions.orZero
|
||||||
|
|
||||||
fun QuotesResponse.Quote.toDomain(rawCurrencyId: String, source: StatusSource = StatusSource.ACTUAL): Quote {
|
fun QuotesResponse.Quote.toDomain(rawCurrencyId: String, source: StatusSource = StatusSource.ACTUAL): QuoteStatus {
|
||||||
return QuoteConverter(source = source).convert(value = mapOf(rawCurrencyId to this).entries.first())
|
return QuoteStatus(
|
||||||
|
rawCurrencyId = CryptoCurrency.RawID(rawCurrencyId),
|
||||||
|
value = QuoteStatus.Data(
|
||||||
|
source = source,
|
||||||
|
fiatRate = price.orZero(),
|
||||||
|
priceChange = priceChange24h.orZero().movePointLeft(2),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Pair<String, QuotesResponse.Quote>.toDomain(source: StatusSource = StatusSource.ACTUAL): Quote {
|
fun Pair<String, QuotesResponse.Quote>.toDomain(source: StatusSource = StatusSource.ACTUAL): QuoteStatus {
|
||||||
return QuoteConverter(source = source).convert(value = mapOf(this).entries.first())
|
return second.toDomain(rawCurrencyId = first, source = source)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.tangem.common.test.utils
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.google.common.truth.Truth
|
||||||
|
|
||||||
|
fun <B> assertEither(actual: Either<Throwable, B>, expected: Either<Throwable, B>) {
|
||||||
|
actual
|
||||||
|
.onRight { Truth.assertThat(it).isEqualTo(expected) }
|
||||||
|
.onLeft {
|
||||||
|
val expectedError = expected.leftOrNull() ?: error("Expected must be Either.Left")
|
||||||
|
|
||||||
|
Truth.assertThat(it::class.java).isEqualTo(expectedError::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import com.tangem.common.ui.amountScreen.models.AmountState
|
||||||
import com.tangem.common.ui.amountScreen.preview.AmountScreenClickIntentsStub
|
import com.tangem.common.ui.amountScreen.preview.AmountScreenClickIntentsStub
|
||||||
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
||||||
import com.tangem.common.ui.amountScreen.ui.amountField
|
import com.tangem.common.ui.amountScreen.ui.amountField
|
||||||
|
import com.tangem.common.ui.amountScreen.ui.amountFieldV2
|
||||||
import com.tangem.common.ui.amountScreen.ui.buttons
|
import com.tangem.common.ui.amountScreen.ui.buttons
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
|
@ -40,18 +41,28 @@ fun AmountScreenContent(
|
||||||
bottom = TangemTheme.dimens.spacing16,
|
bottom = TangemTheme.dimens.spacing16,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
amountField(
|
if (amountState.isRedesignEnabled) {
|
||||||
amountState = amountState,
|
amountFieldV2(
|
||||||
isBalanceHidden = isBalanceHidden,
|
amountState = amountState,
|
||||||
onValueChange = clickIntents::onAmountValueChange,
|
onValueChange = clickIntents::onAmountValueChange,
|
||||||
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
||||||
)
|
onCurrencyChange = clickIntents::onCurrencyChangeClick,
|
||||||
buttons(
|
onMaxAmountClick = clickIntents::onMaxValueClick,
|
||||||
segmentedButtonConfig = amountState.segmentedButtonConfig,
|
)
|
||||||
clickIntents = clickIntents,
|
} else {
|
||||||
isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled,
|
amountField(
|
||||||
selectedButton = amountState.selectedButton,
|
amountState = amountState,
|
||||||
)
|
isBalanceHidden = isBalanceHidden,
|
||||||
|
onValueChange = clickIntents::onAmountValueChange,
|
||||||
|
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
||||||
|
)
|
||||||
|
buttons(
|
||||||
|
segmentedButtonConfig = amountState.segmentedButtonConfig,
|
||||||
|
clickIntents = clickIntents,
|
||||||
|
isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled,
|
||||||
|
selectedButton = amountState.selectedButton,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,6 +86,7 @@ private class SendAmountContentPreviewProvider : PreviewParameterProvider<Amount
|
||||||
override val values: Sequence<AmountState>
|
override val values: Sequence<AmountState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
AmountStatePreviewData.amountState,
|
AmountStatePreviewData.amountState,
|
||||||
|
AmountStatePreviewData.amountStateV2,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
@ -58,10 +58,11 @@ class AmountStateConverter(
|
||||||
return AmountState.Data(
|
return AmountState.Data(
|
||||||
title = value.title,
|
title = value.title,
|
||||||
availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
|
availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
|
||||||
|
tokenName = stringReference(status.currency.name),
|
||||||
tokenIconState = iconStateConverter.convert(status),
|
tokenIconState = iconStateConverter.convert(status),
|
||||||
amountTextField = amountFieldConverter.convert(value.value),
|
amountTextField = amountFieldConverter.convert(value.value),
|
||||||
isPrimaryButtonEnabled = false,
|
isPrimaryButtonEnabled = false,
|
||||||
appCurrencyCode = appCurrency.code,
|
appCurrency = appCurrency,
|
||||||
segmentedButtonConfig = persistentListOf(
|
segmentedButtonConfig = persistentListOf(
|
||||||
AmountSegmentedButtonsConfig(
|
AmountSegmentedButtonsConfig(
|
||||||
title = stringReference(status.currency.symbol),
|
title = stringReference(status.currency.symbol),
|
||||||
|
|
@ -80,6 +81,7 @@ class AmountStateConverter(
|
||||||
),
|
),
|
||||||
isSegmentedButtonsEnabled = !noFeeRate,
|
isSegmentedButtonsEnabled = !noFeeRate,
|
||||||
selectedButton = 0,
|
selectedButton = 0,
|
||||||
|
isRedesignEnabled = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +101,7 @@ class AmountStateConverterV2(
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
private val maxEnterAmount: EnterAmountBoundary,
|
private val maxEnterAmount: EnterAmountBoundary,
|
||||||
private val iconStateConverter: CryptoCurrencyToIconStateConverter,
|
private val iconStateConverter: CryptoCurrencyToIconStateConverter,
|
||||||
|
private val isRedesignEnabled: Boolean,
|
||||||
) : Converter<AmountParameters, AmountState> {
|
) : Converter<AmountParameters, AmountState> {
|
||||||
|
|
||||||
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
|
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
|
@ -116,11 +119,16 @@ class AmountStateConverterV2(
|
||||||
|
|
||||||
return AmountState.Data(
|
return AmountState.Data(
|
||||||
title = value.title,
|
title = value.title,
|
||||||
availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
|
availableBalance = if (isRedesignEnabled) {
|
||||||
|
resourceReference(R.string.common_balance, wrappedList(crypto))
|
||||||
|
} else {
|
||||||
|
resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat))
|
||||||
|
},
|
||||||
|
tokenName = stringReference(cryptoCurrencyStatus.currency.name),
|
||||||
tokenIconState = iconStateConverter.convert(cryptoCurrencyStatus),
|
tokenIconState = iconStateConverter.convert(cryptoCurrencyStatus),
|
||||||
amountTextField = amountFieldConverter.convert(value.value),
|
amountTextField = amountFieldConverter.convert(value.value),
|
||||||
isPrimaryButtonEnabled = false,
|
isPrimaryButtonEnabled = false,
|
||||||
appCurrencyCode = appCurrency.code,
|
appCurrency = appCurrency,
|
||||||
segmentedButtonConfig = persistentListOf(
|
segmentedButtonConfig = persistentListOf(
|
||||||
AmountSegmentedButtonsConfig(
|
AmountSegmentedButtonsConfig(
|
||||||
title = stringReference(cryptoCurrencyStatus.currency.symbol),
|
title = stringReference(cryptoCurrencyStatus.currency.symbol),
|
||||||
|
|
@ -139,6 +147,7 @@ class AmountStateConverterV2(
|
||||||
),
|
),
|
||||||
isSegmentedButtonsEnabled = !noFeeRate,
|
isSegmentedButtonsEnabled = !noFeeRate,
|
||||||
selectedButton = 0,
|
selectedButton = 0,
|
||||||
|
isRedesignEnabled = isRedesignEnabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.common.ui.amountScreen.models
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import kotlinx.collections.immutable.PersistentList
|
import kotlinx.collections.immutable.PersistentList
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
|
@ -21,7 +22,7 @@ sealed class AmountState {
|
||||||
* @param selectedButton selected currency index
|
* @param selectedButton selected currency index
|
||||||
* @param isSegmentedButtonsEnabled indicates if currency switches is enabled
|
* @param isSegmentedButtonsEnabled indicates if currency switches is enabled
|
||||||
* @param amountTextField amount field state
|
* @param amountTextField amount field state
|
||||||
* @param appCurrencyCode app currency code
|
* @param appCurrency app currency
|
||||||
* @param isEditingDisabled indicated whether amount is editable
|
* @param isEditingDisabled indicated whether amount is editable
|
||||||
* @param reduceAmountBy reduces amount to be sent by specified value
|
* @param reduceAmountBy reduces amount to be sent by specified value
|
||||||
* @param isIgnoreReduce ignores reduce amount value
|
* @param isIgnoreReduce ignores reduce amount value
|
||||||
|
|
@ -30,15 +31,17 @@ sealed class AmountState {
|
||||||
override val isPrimaryButtonEnabled: Boolean,
|
override val isPrimaryButtonEnabled: Boolean,
|
||||||
val title: TextReference,
|
val title: TextReference,
|
||||||
val availableBalance: TextReference,
|
val availableBalance: TextReference,
|
||||||
|
val tokenName: TextReference,
|
||||||
val tokenIconState: CurrencyIconState,
|
val tokenIconState: CurrencyIconState,
|
||||||
val segmentedButtonConfig: PersistentList<AmountSegmentedButtonsConfig>,
|
val segmentedButtonConfig: PersistentList<AmountSegmentedButtonsConfig>,
|
||||||
val selectedButton: Int,
|
val selectedButton: Int,
|
||||||
val isSegmentedButtonsEnabled: Boolean,
|
val isSegmentedButtonsEnabled: Boolean,
|
||||||
val amountTextField: AmountFieldModel,
|
val amountTextField: AmountFieldModel,
|
||||||
val appCurrencyCode: String,
|
val appCurrency: AppCurrency,
|
||||||
val isEditingDisabled: Boolean = false,
|
val isEditingDisabled: Boolean = false,
|
||||||
val reduceAmountBy: BigDecimal = BigDecimal.ZERO,
|
val reduceAmountBy: BigDecimal = BigDecimal.ZERO,
|
||||||
val isIgnoreReduce: Boolean = false,
|
val isIgnoreReduce: Boolean = false,
|
||||||
|
val isRedesignEnabled: Boolean,
|
||||||
) : AmountState()
|
) : AmountState()
|
||||||
|
|
||||||
data class Empty(
|
data class Empty(
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,16 @@ package com.tangem.common.ui.amountScreen.preview
|
||||||
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import com.tangem.common.ui.R
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig
|
import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.tokens.model.Amount
|
import com.tangem.domain.tokens.model.Amount
|
||||||
import com.tangem.domain.tokens.model.AmountType
|
import com.tangem.domain.tokens.model.AmountType
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -15,6 +19,8 @@ import java.math.BigDecimal
|
||||||
|
|
||||||
object AmountStatePreviewData {
|
object AmountStatePreviewData {
|
||||||
|
|
||||||
|
val emptyState = AmountState.Empty()
|
||||||
|
|
||||||
val amountState = AmountState.Data(
|
val amountState = AmountState.Data(
|
||||||
isPrimaryButtonEnabled = false,
|
isPrimaryButtonEnabled = false,
|
||||||
title = stringReference("Family Wallet"),
|
title = stringReference("Family Wallet"),
|
||||||
|
|
@ -31,7 +37,8 @@ object AmountStatePreviewData {
|
||||||
isFiat = true,
|
isFiat = true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
appCurrencyCode = "usd",
|
appCurrency = AppCurrency.Default,
|
||||||
|
tokenName = stringReference("Tether"),
|
||||||
amountTextField = AmountFieldModel(
|
amountTextField = AmountFieldModel(
|
||||||
value = "",
|
value = "",
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
|
|
@ -60,6 +67,7 @@ object AmountStatePreviewData {
|
||||||
),
|
),
|
||||||
isSegmentedButtonsEnabled = true,
|
isSegmentedButtonsEnabled = true,
|
||||||
selectedButton = 0,
|
selectedButton = 0,
|
||||||
|
isRedesignEnabled = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
val amountWithValueState = amountState.copy(
|
val amountWithValueState = amountState.copy(
|
||||||
|
|
@ -75,6 +83,11 @@ object AmountStatePreviewData {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val amountStateV2 = amountState.copy(
|
||||||
|
isRedesignEnabled = true,
|
||||||
|
availableBalance = resourceReference(R.string.common_balance, wrappedList("2 130,88 USDT")),
|
||||||
|
)
|
||||||
|
|
||||||
val amountWithValueFiatState = amountWithValueState.copy(
|
val amountWithValueFiatState = amountWithValueState.copy(
|
||||||
amountTextField = amountWithValueState.amountTextField.copy(isFiatValue = false),
|
amountTextField = amountWithValueState.amountTextField.copy(isFiatValue = false),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
|
||||||
val fiatAmount = amount.fiatAmount.value.format {
|
val fiatAmount = amount.fiatAmount.value.format {
|
||||||
fiat(
|
fiat(
|
||||||
fiatCurrencySymbol = amount.fiatAmount.currencySymbol,
|
fiatCurrencySymbol = amount.fiatAmount.currencySymbol,
|
||||||
fiatCurrencyCode = amountState.appCurrencyCode,
|
fiatCurrencyCode = amountState.appCurrency.code,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
package com.tangem.common.ui.amountScreen.ui
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.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.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||||
|
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
||||||
|
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||||
|
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AmountBlockV2(
|
||||||
|
amountState: AmountState,
|
||||||
|
currencyIconState: CurrencyIconState,
|
||||||
|
isClickDisabled: Boolean,
|
||||||
|
isEditingDisabled: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
if (amountState !is AmountState.Data) return
|
||||||
|
val amount = amountState.amountTextField
|
||||||
|
|
||||||
|
val cryptoAmount = amount.cryptoAmount.value?.format {
|
||||||
|
crypto(
|
||||||
|
symbol = "",
|
||||||
|
decimals = amount.cryptoAmount.decimals,
|
||||||
|
)
|
||||||
|
}.orEmpty()
|
||||||
|
|
||||||
|
val fiatAmount = amount.fiatAmount.value.format {
|
||||||
|
fiat(
|
||||||
|
fiatCurrencySymbol = amount.fiatAmount.currencySymbol,
|
||||||
|
fiatCurrencyCode = amountState.appCurrency.code,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val (firstAmount, secondAmount) = if (amount.isFiatValue) {
|
||||||
|
fiatAmount to cryptoAmount
|
||||||
|
} else {
|
||||||
|
cryptoAmount to fiatAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
val title = TextReference.Str(
|
||||||
|
stringResourceSafe(
|
||||||
|
R.string.send_from_wallet_name,
|
||||||
|
amountState.title
|
||||||
|
.resolveReference(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val currencyTitle = amount.cryptoAmount.currencySymbol
|
||||||
|
|
||||||
|
AmountBlockV2(
|
||||||
|
title = title,
|
||||||
|
balance = amountState.availableBalance,
|
||||||
|
currencyTitle = currencyTitle,
|
||||||
|
currencyIconState = currencyIconState,
|
||||||
|
firstAmount = firstAmount,
|
||||||
|
secondAmount = secondAmount,
|
||||||
|
isClickDisabled = isClickDisabled,
|
||||||
|
isEditingDisabled = isEditingDisabled,
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
|
@Composable
|
||||||
|
internal fun AmountBlockV2(
|
||||||
|
title: TextReference,
|
||||||
|
balance: TextReference,
|
||||||
|
currencyTitle: String,
|
||||||
|
currencyIconState: CurrencyIconState,
|
||||||
|
firstAmount: String,
|
||||||
|
secondAmount: String,
|
||||||
|
isClickDisabled: Boolean,
|
||||||
|
isEditingDisabled: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||||
|
.background(TangemTheme.colors.background.action)
|
||||||
|
.clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick)
|
||||||
|
.padding(TangemTheme.dimens.spacing16),
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
text = title.resolveReference(),
|
||||||
|
style = TangemTheme.typography.body2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
)
|
||||||
|
Spacer(modifier = modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
text = balance.resolveReference(),
|
||||||
|
style = TangemTheme.typography.body2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = firstAmount,
|
||||||
|
style = TangemTheme.typography.h2,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = secondAmount,
|
||||||
|
style = TangemTheme.typography.body2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.padding(top = 6.dp),
|
||||||
|
) {
|
||||||
|
CurrencyIcon(
|
||||||
|
state = currencyIconState,
|
||||||
|
modifier = Modifier.padding(horizontal = 14.dp),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = currencyTitle,
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Preview
|
||||||
|
@Preview
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun AmountBlockPreview(@PreviewParameter(AmountBlockV2PreviewProvider::class) value: AmountState) {
|
||||||
|
TangemThemePreview {
|
||||||
|
AmountBlockV2(
|
||||||
|
amountState = value,
|
||||||
|
currencyIconState = CurrencyIconState.Empty(),
|
||||||
|
isClickDisabled = false,
|
||||||
|
isEditingDisabled = false,
|
||||||
|
onClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AmountBlockV2PreviewProvider : PreviewParameterProvider<AmountState> {
|
||||||
|
override val values: Sequence<AmountState>
|
||||||
|
get() = sequenceOf(
|
||||||
|
AmountStatePreviewData.amountState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
@ -31,56 +31,6 @@ import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.utils.rememberDecimalFormat
|
import com.tangem.core.ui.utils.rememberDecimalFormat
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
@Deprecated("Use AmountField with clicks")
|
|
||||||
@Composable
|
|
||||||
internal fun AmountField(amountField: AmountFieldModel, appCurrencyCode: String) {
|
|
||||||
val decimalFormat = rememberDecimalFormat()
|
|
||||||
val isFiatValue = amountField.isFiatValue
|
|
||||||
val currencyCode = if (isFiatValue) appCurrencyCode else null
|
|
||||||
val (primaryAmount, primaryValue) = if (isFiatValue) {
|
|
||||||
amountField.fiatAmount to amountField.fiatValue
|
|
||||||
} else {
|
|
||||||
amountField.cryptoAmount to amountField.value
|
|
||||||
}
|
|
||||||
val requester = remember { FocusRequester() }
|
|
||||||
|
|
||||||
AmountTextField(
|
|
||||||
value = primaryValue,
|
|
||||||
decimals = primaryAmount.decimals,
|
|
||||||
visualTransformation = AmountVisualTransformation(
|
|
||||||
decimals = primaryAmount.decimals,
|
|
||||||
symbol = primaryAmount.currencySymbol,
|
|
||||||
currencyCode = currencyCode,
|
|
||||||
decimalFormat = decimalFormat,
|
|
||||||
),
|
|
||||||
onValueChange = amountField.onValueChange,
|
|
||||||
keyboardOptions = amountField.keyboardOptions,
|
|
||||||
keyboardActions = amountField.keyboardActions,
|
|
||||||
textStyle = TangemTheme.typography.h2.copy(
|
|
||||||
color = TangemTheme.colors.text.primary1,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
),
|
|
||||||
isAutoResize = true,
|
|
||||||
isValuePasted = amountField.isValuePasted,
|
|
||||||
onValuePastedTriggerDismiss = amountField.onValuePastedTriggerDismiss,
|
|
||||||
modifier = Modifier
|
|
||||||
.focusRequester(requester)
|
|
||||||
.padding(
|
|
||||||
top = TangemTheme.dimens.spacing24,
|
|
||||||
start = TangemTheme.dimens.spacing12,
|
|
||||||
end = TangemTheme.dimens.spacing12,
|
|
||||||
)
|
|
||||||
.requiredHeightIn(min = TangemTheme.dimens.size32),
|
|
||||||
)
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = Unit) {
|
|
||||||
delay(timeMillis = 200)
|
|
||||||
requester.requestFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
AmountSecondary(amountField, appCurrencyCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AmountField(
|
internal fun AmountField(
|
||||||
amountField: AmountFieldModel,
|
amountField: AmountFieldModel,
|
||||||
|
|
@ -97,7 +47,7 @@ internal fun AmountField(
|
||||||
amountField.cryptoAmount to amountField.value
|
amountField.cryptoAmount to amountField.value
|
||||||
}
|
}
|
||||||
val requester = remember { FocusRequester() }
|
val requester = remember { FocusRequester() }
|
||||||
|
val symbolColor = if (primaryValue.isBlank()) TangemTheme.colors.text.disabled else TangemTheme.colors.text.primary1
|
||||||
AmountTextField(
|
AmountTextField(
|
||||||
value = primaryValue,
|
value = primaryValue,
|
||||||
decimals = primaryAmount.decimals,
|
decimals = primaryAmount.decimals,
|
||||||
|
|
@ -106,6 +56,7 @@ internal fun AmountField(
|
||||||
symbol = primaryAmount.currencySymbol,
|
symbol = primaryAmount.currencySymbol,
|
||||||
currencyCode = currencyCode,
|
currencyCode = currencyCode,
|
||||||
decimalFormat = decimalFormat,
|
decimalFormat = decimalFormat,
|
||||||
|
symbolColor = symbolColor,
|
||||||
),
|
),
|
||||||
onValueChange = onValueChange,
|
onValueChange = onValueChange,
|
||||||
keyboardOptions = amountField.keyboardOptions,
|
keyboardOptions = amountField.keyboardOptions,
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,32 @@ package com.tangem.common.ui.amountScreen.ui
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.ripple
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.common.ui.R
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||||
|
import com.tangem.core.ui.components.SpacerWMax
|
||||||
|
import com.tangem.core.ui.components.TextShimmer
|
||||||
|
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||||
|
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||||
|
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
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.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
private const val AMOUNT_FIELD_KEY = "amountFieldKey"
|
private const val AMOUNT_FIELD_KEY = "amountFieldKey"
|
||||||
|
|
@ -64,10 +76,142 @@ internal fun LazyListScope.amountField(
|
||||||
)
|
)
|
||||||
AmountField(
|
AmountField(
|
||||||
amountField = amountState.amountTextField,
|
amountField = amountState.amountTextField,
|
||||||
appCurrencyCode = amountState.appCurrencyCode,
|
appCurrencyCode = amountState.appCurrency.code,
|
||||||
onValueChange = onValueChange,
|
onValueChange = onValueChange,
|
||||||
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
|
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun LazyListScope.amountFieldV2(
|
||||||
|
amountState: AmountState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
onValuePastedTriggerDismiss: () -> Unit,
|
||||||
|
onCurrencyChange: (Boolean) -> Unit,
|
||||||
|
onMaxAmountClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
item(key = AMOUNT_FIELD_KEY) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(TangemTheme.colors.background.action),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.padding(top = 48.dp, bottom = 28.dp),
|
||||||
|
) {
|
||||||
|
if (amountState !is AmountState.Data) {
|
||||||
|
TextShimmer(
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
modifier = Modifier.width(60.dp),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = amountState.title.resolveReference(),
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AmountFieldV2(
|
||||||
|
amountUM = amountState,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
|
||||||
|
onCurrencyChange = onCurrencyChange,
|
||||||
|
modifier = Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp),
|
||||||
|
thickness = 0.5.dp,
|
||||||
|
color = TangemTheme.colors.stroke.primary,
|
||||||
|
)
|
||||||
|
AmountInfo(
|
||||||
|
amountUM = amountState,
|
||||||
|
onMaxAmountClick = onMaxAmountClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AmountInfo(amountUM: AmountState, onMaxAmountClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||||
|
val tokenIconState = (amountUM as? AmountState.Data)?.tokenIconState ?: CurrencyIconState.Loading
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
CurrencyIcon(
|
||||||
|
state = tokenIconState,
|
||||||
|
shouldDisplayNetwork = true,
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
start = 16.dp,
|
||||||
|
top = 16.dp,
|
||||||
|
bottom = 16.dp,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
AmountInfoMain(amountUM = amountUM)
|
||||||
|
SpacerWMax()
|
||||||
|
Text(
|
||||||
|
text = stringResourceSafe(R.string.send_max_amount),
|
||||||
|
style = TangemTheme.typography.caption1,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp)
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(TangemTheme.colors.background.secondary)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = ripple(),
|
||||||
|
onClick = onMaxAmountClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AmountInfoMain(amountUM: AmountState, modifier: Modifier = Modifier) {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = amountUM !is AmountState.Data,
|
||||||
|
modifier = modifier,
|
||||||
|
) { isContent ->
|
||||||
|
if (isContent) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
) {
|
||||||
|
TextShimmer(
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
modifier = Modifier.width(56.dp),
|
||||||
|
)
|
||||||
|
TextShimmer(
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
modifier = Modifier.width(72.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val amountUM = amountUM as AmountState.Data
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = amountUM.tokenName.resolveReference(),
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
EllipsisText(
|
||||||
|
text = amountUM.availableBalance.resolveReference(),
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
ellipsis = TextEllipsis.OffsetEnd(amountUM.amountTextField.cryptoAmount.currencySymbol.length),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,329 @@
|
||||||
|
package com.tangem.common.ui.amountScreen.ui
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Alignment.Companion.BottomCenter
|
||||||
|
import androidx.compose.ui.Alignment.Companion.TopCenter
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||||
|
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
||||||
|
import com.tangem.core.ui.components.TextShimmer
|
||||||
|
import com.tangem.core.ui.components.currency.fiaticon.FiatIcon
|
||||||
|
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||||
|
import com.tangem.core.ui.components.fields.AmountTextField
|
||||||
|
import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.core.ui.utils.rememberDecimalFormat
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
private const val ROTATED_DEGREE = 180f
|
||||||
|
private const val INITIAL_DEGREE = 0f
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AmountFieldV2(
|
||||||
|
amountUM: AmountState,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
onValuePastedTriggerDismiss: () -> Unit,
|
||||||
|
onCurrencyChange: (Boolean) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val decimalFormat = rememberDecimalFormat()
|
||||||
|
|
||||||
|
val requester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
if (amountUM is AmountState.Empty) {
|
||||||
|
TextShimmer(
|
||||||
|
style = TangemTheme.typography.head,
|
||||||
|
modifier = Modifier.width(150.dp),
|
||||||
|
)
|
||||||
|
} else if (amountUM is AmountState.Data) {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = amountUM.amountTextField.isFiatValue,
|
||||||
|
transitionSpec = { primaryFieldCurrencyAnimation() },
|
||||||
|
label = "Primary field change animation",
|
||||||
|
) { isFiatValue ->
|
||||||
|
val currencyCode = if (isFiatValue) amountUM.appCurrency.code else null
|
||||||
|
val (primaryAmount, primaryValue) = if (isFiatValue) {
|
||||||
|
amountUM.amountTextField.fiatAmount to amountUM.amountTextField.fiatValue
|
||||||
|
} else {
|
||||||
|
amountUM.amountTextField.cryptoAmount to amountUM.amountTextField.value
|
||||||
|
}
|
||||||
|
AmountTextField(
|
||||||
|
value = primaryValue,
|
||||||
|
decimals = primaryAmount.decimals,
|
||||||
|
visualTransformation = AmountVisualTransformation(
|
||||||
|
decimals = primaryAmount.decimals,
|
||||||
|
symbol = primaryAmount.currencySymbol,
|
||||||
|
currencyCode = currencyCode,
|
||||||
|
decimalFormat = decimalFormat,
|
||||||
|
symbolColor = TangemTheme.colors.text.disabled,
|
||||||
|
),
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
keyboardOptions = amountUM.amountTextField.keyboardOptions,
|
||||||
|
keyboardActions = amountUM.amountTextField.keyboardActions,
|
||||||
|
textStyle = TangemTheme.typography.head.copy(
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
),
|
||||||
|
isAutoResize = true,
|
||||||
|
isValuePasted = amountUM.amountTextField.isValuePasted,
|
||||||
|
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
|
||||||
|
modifier = Modifier
|
||||||
|
.focusRequester(requester)
|
||||||
|
.requiredHeightIn(min = 44.dp),
|
||||||
|
)
|
||||||
|
LaunchedEffect(key1 = Unit) {
|
||||||
|
delay(timeMillis = 200)
|
||||||
|
requester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AmountSecondary(
|
||||||
|
amountUM = amountUM,
|
||||||
|
onCurrencyChange = onCurrencyChange,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -> Unit) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.animateContentSize()
|
||||||
|
.padding(top = 8.dp),
|
||||||
|
) {
|
||||||
|
if (amountUM is AmountState.Empty) {
|
||||||
|
TextShimmer(
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(90.dp)
|
||||||
|
.padding(bottom = 20.dp)
|
||||||
|
.align(TopCenter),
|
||||||
|
)
|
||||||
|
} else if (amountUM is AmountState.Data) {
|
||||||
|
AmountFieldCurrencyInfo(
|
||||||
|
amountUM = amountUM,
|
||||||
|
onCurrencyChange = onCurrencyChange,
|
||||||
|
)
|
||||||
|
AmountFieldError(
|
||||||
|
isError = amountUM.amountTextField.isError,
|
||||||
|
isWarning = amountUM.amountTextField.isWarning,
|
||||||
|
error = amountUM.amountTextField.error,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(BottomCenter)
|
||||||
|
.padding(top = 20.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BoxScope.AmountFieldCurrencyInfo(amountUM: AmountState.Data, onCurrencyChange: (Boolean) -> Unit) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(TopCenter)
|
||||||
|
.padding(bottom = 20.dp)
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null,
|
||||||
|
onClick = { onCurrencyChange(!amountUM.amountTextField.isFiatValue) },
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
val iconRotateState by animateFloatAsState(
|
||||||
|
targetValue = if (amountUM.amountTextField.isFiatValue) ROTATED_DEGREE else INITIAL_DEGREE,
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 400,
|
||||||
|
easing = FastOutSlowInEasing,
|
||||||
|
),
|
||||||
|
label = "Currency change icon animation",
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
painter = rememberVectorPainter(
|
||||||
|
ImageVector.vectorResource(R.drawable.ic_exchange_vertical_24),
|
||||||
|
),
|
||||||
|
tint = TangemTheme.colors.icon.accent,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(16.dp)
|
||||||
|
.graphicsLayer {
|
||||||
|
rotationY = iconRotateState
|
||||||
|
},
|
||||||
|
)
|
||||||
|
AmountFieldCurrencyIcon(
|
||||||
|
amountUM = amountUM,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AmountFieldCurrencyIcon(amountUM: AmountState.Data) {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = amountUM.amountTextField.isFiatValue,
|
||||||
|
transitionSpec = { secondaryFieldCurrencyAnimation() },
|
||||||
|
label = "Secondary field change animation",
|
||||||
|
) { isFiatValue ->
|
||||||
|
val secondaryAmount =
|
||||||
|
if (isFiatValue) amountUM.amountTextField.cryptoAmount else amountUM.amountTextField.fiatAmount
|
||||||
|
val text = secondaryAmount.value.format {
|
||||||
|
if (isFiatValue) {
|
||||||
|
crypto(secondaryAmount.currencySymbol, secondaryAmount.decimals)
|
||||||
|
} else {
|
||||||
|
fiat(
|
||||||
|
fiatCurrencySymbol = amountUM.appCurrency.symbol,
|
||||||
|
fiatCurrencyCode = amountUM.appCurrency.code,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = TangemTheme.typography.caption2.copy(textDirection = TextDirection.ContentOrLtr),
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
if (isFiatValue) {
|
||||||
|
CurrencyIcon(
|
||||||
|
state = amountUM.tokenIconState,
|
||||||
|
shouldDisplayNetwork = false,
|
||||||
|
modifier = Modifier.size(12.dp),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
FiatIcon(
|
||||||
|
url = amountUM.appCurrency.iconSmallUrl,
|
||||||
|
size = 12.dp,
|
||||||
|
isGrayscale = false,
|
||||||
|
modifier = Modifier.size(12.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AmountFieldError(
|
||||||
|
isError: Boolean,
|
||||||
|
isWarning: Boolean,
|
||||||
|
error: TextReference,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isError || isWarning,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
modifier = modifier,
|
||||||
|
label = "Error field appearance animation",
|
||||||
|
) {
|
||||||
|
val errorText = remember(this, error) { error }
|
||||||
|
val color = if (isError) TangemTheme.colors.text.warning else TangemTheme.colors.text.attention
|
||||||
|
Text(
|
||||||
|
text = errorText.resolveReference(),
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
color = color,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <S> AnimatedContentTransitionScope<S>.secondaryFieldCurrencyAnimation(): ContentTransform {
|
||||||
|
return (
|
||||||
|
fadeIn(
|
||||||
|
animationSpec = tween(durationMillis = 300),
|
||||||
|
) + scaleIn(
|
||||||
|
animationSpec = tween(durationMillis = 300),
|
||||||
|
initialScale = 0.9f,
|
||||||
|
)
|
||||||
|
).togetherWith(
|
||||||
|
fadeOut(
|
||||||
|
animationSpec = tween(durationMillis = 300),
|
||||||
|
) + scaleOut(
|
||||||
|
animationSpec = tween(durationMillis = 300),
|
||||||
|
targetScale = 0.9f,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <S> AnimatedContentTransitionScope<S>.primaryFieldCurrencyAnimation(): ContentTransform {
|
||||||
|
return (
|
||||||
|
fadeIn(
|
||||||
|
animationSpec = tween(durationMillis = 300),
|
||||||
|
) + scaleIn(
|
||||||
|
animationSpec = tween(durationMillis = 300),
|
||||||
|
initialScale = 0.9f,
|
||||||
|
) + slideInVertically(initialOffsetY = { it / 2 })
|
||||||
|
)
|
||||||
|
.togetherWith(
|
||||||
|
fadeOut(
|
||||||
|
animationSpec = tween(durationMillis = 300),
|
||||||
|
) + scaleOut(
|
||||||
|
animationSpec = tween(durationMillis = 300),
|
||||||
|
targetScale = 0.9f,
|
||||||
|
) + slideOutVertically(targetOffsetY = { it / 2 }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Preview
|
||||||
|
@Composable
|
||||||
|
@Preview(showBackground = true, widthDp = 360)
|
||||||
|
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
private fun AmountFieldV2_Preview(@PreviewParameter(AmountFieldV2PreviewProvider::class) params: AmountState) {
|
||||||
|
TangemThemePreview {
|
||||||
|
AmountFieldV2(
|
||||||
|
amountUM = params,
|
||||||
|
onValueChange = {},
|
||||||
|
onValuePastedTriggerDismiss = { },
|
||||||
|
onCurrencyChange = {},
|
||||||
|
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AmountFieldV2PreviewProvider : PreviewParameterProvider<AmountState> {
|
||||||
|
override val values: Sequence<AmountState>
|
||||||
|
get() = sequenceOf(
|
||||||
|
AmountStatePreviewData.emptyState,
|
||||||
|
AmountStatePreviewData.amountState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
@ -13,12 +13,18 @@ fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurr
|
||||||
return stringReference(formattedFiat)
|
return stringReference(formattedFiat)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFiatString(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): String {
|
fun getFiatString(
|
||||||
|
value: BigDecimal?,
|
||||||
|
rate: BigDecimal?,
|
||||||
|
appCurrency: AppCurrency,
|
||||||
|
approximate: Boolean = false,
|
||||||
|
): String {
|
||||||
if (value == null || rate == null) return EMPTY_BALANCE_SIGN
|
if (value == null || rate == null) return EMPTY_BALANCE_SIGN
|
||||||
val feeValue = value.multiply(rate)
|
val feeValue = value.multiply(rate)
|
||||||
return BigDecimalFormatter.formatFiatAmount(
|
return BigDecimalFormatter.formatFiatAmount(
|
||||||
fiatAmount = feeValue,
|
fiatAmount = feeValue,
|
||||||
fiatCurrencyCode = appCurrency.code,
|
fiatCurrencyCode = appCurrency.code,
|
||||||
fiatCurrencySymbol = appCurrency.symbol,
|
fiatCurrencySymbol = appCurrency.symbol,
|
||||||
|
withApproximateSign = approximate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.extensions.wrappedList
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||||
|
|
||||||
|
@Suppress("CyclomaticComplexMethod")
|
||||||
fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference {
|
fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference {
|
||||||
return when (val unavailabilityReason = this) {
|
return when (val unavailabilityReason = this) {
|
||||||
is ScenarioUnavailabilityReason.StakingUnavailable -> {
|
is ScenarioUnavailabilityReason.StakingUnavailable -> {
|
||||||
|
|
@ -46,6 +47,9 @@ fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference {
|
||||||
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
|
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
|
||||||
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
|
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
|
||||||
)
|
)
|
||||||
|
ScenarioUnavailabilityReason.TrustlineRequired -> resourceReference(
|
||||||
|
id = R.string.warning_receive_blocked_token_trustline_required_message,
|
||||||
|
)
|
||||||
ScenarioUnavailabilityReason.UsedOutdatedData -> {
|
ScenarioUnavailabilityReason.UsedOutdatedData -> {
|
||||||
resourceReference(id = R.string.token_button_unavailability_reason_out_of_date_balance)
|
resourceReference(id = R.string.token_button_unavailability_reason_out_of_date_balance)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ private fun NameAndInfo(
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
@Composable
|
@Composable
|
||||||
private fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modifier) {
|
fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modifier) {
|
||||||
val imageModifier = modifier
|
val imageModifier = modifier
|
||||||
.width(TangemTheme.dimens.size36)
|
.width(TangemTheme.dimens.size36)
|
||||||
.height(TangemTheme.dimens.size24)
|
.height(TangemTheme.dimens.size24)
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,7 @@ sealed class AnalyticsParam {
|
||||||
const val ACTION = "Action"
|
const val ACTION = "Action"
|
||||||
const val COLLECTIONS = "Collections"
|
const val COLLECTIONS = "Collections"
|
||||||
const val NFT = "Nft"
|
const val NFT = "Nft"
|
||||||
|
const val NONCE = "Nonce"
|
||||||
const val STANDARD = "Standard"
|
const val STANDARD = "Standard"
|
||||||
const val NO_COLLECTION = "No collection"
|
const val NO_COLLECTION = "No collection"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,5 +62,17 @@
|
||||||
{
|
{
|
||||||
"name": "USEDESK_ENABLED",
|
"name": "USEDESK_ENABLED",
|
||||||
"version": "undefined"
|
"version": "undefined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SEND_VIA_SWAP_ENABLED",
|
||||||
|
"version": "undefined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SWAP_REDESIGN_ENABLED",
|
||||||
|
"version": "undefined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SEND_REDESIGN_ENABLED",
|
||||||
|
"version": "undefined"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ internal class DefaultVersionProvider @Inject constructor(
|
||||||
context.packageName,
|
context.packageName,
|
||||||
PackageManager.PackageInfoFlags.of(0),
|
PackageManager.PackageInfoFlags.of(0),
|
||||||
)
|
)
|
||||||
.versionName
|
.versionName!!
|
||||||
} else {
|
} else {
|
||||||
context.packageManager.getPackageInfo(context.packageName, 0).versionName
|
context.packageManager.getPackageInfo(context.packageName, 0).versionName!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Trace(
|
data class Trace(
|
||||||
@Json(name = "exposed") val exposed: Exposed,
|
@Json(name = "exposed") val exposed: Exposed?,
|
||||||
@Json(name = "asset") val asset: NftAsset,
|
@Json(name = "asset") val asset: NftAsset,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ import com.squareup.moshi.JsonClass
|
||||||
data class ValidationResponse(
|
data class ValidationResponse(
|
||||||
@Json(name = "status") val status: String,
|
@Json(name = "status") val status: String,
|
||||||
@Json(name = "result_type") val resultType: String,
|
@Json(name = "result_type") val resultType: String,
|
||||||
|
@Json(name = "description") val description: String?,
|
||||||
)
|
)
|
||||||
|
|
@ -24,7 +24,7 @@ sealed class ApiConfig {
|
||||||
Express,
|
Express,
|
||||||
TangemTech,
|
TangemTech,
|
||||||
StakeKit,
|
StakeKit,
|
||||||
TangemVisa,
|
TangemPay,
|
||||||
Attestation,
|
Attestation,
|
||||||
BlockAid,
|
BlockAid,
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +34,7 @@ sealed class ApiConfig {
|
||||||
is Express -> ID.Express
|
is Express -> ID.Express
|
||||||
is TangemTech -> ID.TangemTech
|
is TangemTech -> ID.TangemTech
|
||||||
is StakeKit -> ID.StakeKit
|
is StakeKit -> ID.StakeKit
|
||||||
is TangemVisa -> ID.TangemVisa
|
is TangemPay -> ID.TangemPay
|
||||||
is Attestation -> ID.Attestation
|
is Attestation -> ID.Attestation
|
||||||
is BlockAid -> ID.BlockAid
|
is BlockAid -> ID.BlockAid
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package com.tangem.datasource.api.common.config
|
||||||
import com.tangem.utils.ProviderSuspend
|
import com.tangem.utils.ProviderSuspend
|
||||||
import com.tangem.utils.version.AppVersionProvider
|
import com.tangem.utils.version.AppVersionProvider
|
||||||
|
|
||||||
internal class TangemVisa(
|
internal class TangemPay(
|
||||||
private val appVersionProvider: AppVersionProvider,
|
private val appVersionProvider: AppVersionProvider,
|
||||||
) : ApiConfig() {
|
) : ApiConfig() {
|
||||||
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
package com.tangem.datasource.api.visa
|
package com.tangem.datasource.api.pay
|
||||||
|
|
||||||
import com.tangem.datasource.api.common.response.ApiResponse
|
import com.tangem.datasource.api.common.response.ApiResponse
|
||||||
import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest
|
import com.tangem.datasource.api.pay.models.request.ActivationByCardWalletRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest
|
import com.tangem.datasource.api.pay.models.request.ActivationByCustomerWalletRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.ActivationStatusRequest
|
import com.tangem.datasource.api.pay.models.request.ActivationStatusRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.ExchangeAccessTokenRequest
|
import com.tangem.datasource.api.pay.models.request.ExchangeAccessTokenRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.GenerateNoneByCardIdRequest
|
import com.tangem.datasource.api.pay.models.request.GenerateNoneByCardIdRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.GenerateNoneByCardWalletRequest
|
import com.tangem.datasource.api.pay.models.request.GenerateNoneByCardWalletRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.GetAccessTokenByCardIdRequest
|
import com.tangem.datasource.api.pay.models.request.GetAccessTokenByCardIdRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.GetAccessTokenByCardWalletRequest
|
import com.tangem.datasource.api.pay.models.request.GetAccessTokenByCardWalletRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.GetCardWalletAcceptanceRequest
|
import com.tangem.datasource.api.pay.models.request.GetCardWalletAcceptanceRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.GetCustomerWalletAcceptanceRequest
|
import com.tangem.datasource.api.pay.models.request.GetCustomerWalletAcceptanceRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.RefreshTokenByCardIdRequest
|
import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardIdRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.RefreshTokenByCardWalletRequest
|
import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardWalletRequest
|
||||||
import com.tangem.datasource.api.visa.models.request.SetPinCodeRequest
|
import com.tangem.datasource.api.pay.models.request.SetPinCodeRequest
|
||||||
import com.tangem.datasource.api.visa.models.response.*
|
import com.tangem.datasource.api.pay.models.response.*
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
interface TangemVisaApi {
|
interface TangemPayApi {
|
||||||
|
|
||||||
// region: auth
|
// region: auth
|
||||||
|
|
||||||
|
|
@ -104,4 +104,7 @@ interface TangemVisaApi {
|
||||||
@Query("limit") limit: Int,
|
@Query("limit") limit: Int,
|
||||||
@Query("offset") offset: Int,
|
@Query("offset") offset: Int,
|
||||||
): ApiResponse<VisaTxHistoryResponse>
|
): ApiResponse<VisaTxHistoryResponse>
|
||||||
|
|
||||||
|
@GET("v1/customer/kyc")
|
||||||
|
suspend fun getKycAccess(@Header("Authorization") authHeader: String): ApiResponse<KycAccessInfoResponse>
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -7,4 +7,5 @@ import com.squareup.moshi.JsonClass
|
||||||
data class GenerateNoneByCardWalletRequest(
|
data class GenerateNoneByCardWalletRequest(
|
||||||
@Json(name = "auth_type") val authType: String = "card_wallet",
|
@Json(name = "auth_type") val authType: String = "card_wallet",
|
||||||
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
||||||
|
@Json(name = "card_id") val cardId: String,
|
||||||
)
|
)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -8,4 +8,5 @@ data class GetAccessTokenByCardWalletRequest(
|
||||||
@Json(name = "auth_type") val authType: String = "card_wallet",
|
@Json(name = "auth_type") val authType: String = "card_wallet",
|
||||||
@Json(name = "session_id") val sessionId: String,
|
@Json(name = "session_id") val sessionId: String,
|
||||||
@Json(name = "signature") val signature: String,
|
@Json(name = "signature") val signature: String,
|
||||||
|
@Json(name = "salt") val salt: String,
|
||||||
)
|
)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.request
|
package com.tangem.datasource.api.pay.models.request
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.response
|
package com.tangem.datasource.api.pay.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.response
|
package com.tangem.datasource.api.pay.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.response
|
package com.tangem.datasource.api.pay.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.tangem.datasource.api.pay.models.response
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class KycAccessInfoResponse(
|
||||||
|
@Json(name = "result") val result: Result,
|
||||||
|
) {
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Result(
|
||||||
|
@Json(name = "token") val token: String,
|
||||||
|
@Json(name = "locale") val locale: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.response
|
package com.tangem.datasource.api.pay.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.response
|
package com.tangem.datasource.api.pay.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.response
|
package com.tangem.datasource.api.pay.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.datasource.api.visa.models.response
|
package com.tangem.datasource.api.pay.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
@ -65,7 +65,7 @@ interface TangemTechApi {
|
||||||
suspend fun getQuotes(
|
suspend fun getQuotes(
|
||||||
@Query("currencyId") currencyId: String,
|
@Query("currencyId") currencyId: String,
|
||||||
@Query("coinIds") coinIds: String,
|
@Query("coinIds") coinIds: String,
|
||||||
@Query("fields") fields: String = "price,priceChange24h,lastUpdatedAt",
|
@Query("fields") fields: String,
|
||||||
): ApiResponse<QuotesResponse>
|
): ApiResponse<QuotesResponse>
|
||||||
|
|
||||||
@GET("promotion")
|
@GET("promotion")
|
||||||
|
|
@ -176,13 +176,4 @@ interface TangemTechApi {
|
||||||
@GET("user-wallets/wallets/by-app/{app_id}")
|
@GET("user-wallets/wallets/by-app/{app_id}")
|
||||||
suspend fun getWallets(@Path("app_id") appId: String): ApiResponse<List<WalletResponse>>
|
suspend fun getWallets(@Path("app_id") appId: String): ApiResponse<List<WalletResponse>>
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
companion object {
|
|
||||||
val marketsQuoteFields = listOf(
|
|
||||||
"price",
|
|
||||||
"priceChange24h",
|
|
||||||
"priceChange1w",
|
|
||||||
"priceChange30d",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +53,7 @@ internal object ApiConfigsModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@IntoSet
|
@IntoSet
|
||||||
fun provideTangemVisaConfig(appVersionProvider: AppVersionProvider): ApiConfig = TangemVisa(appVersionProvider)
|
fun provideTangemVisaConfig(appVersionProvider: AppVersionProvider): ApiConfig = TangemPay(appVersionProvider)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@IntoSet
|
@IntoSet
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import com.tangem.datasource.api.onramp.OnrampApi
|
||||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApiV2
|
import com.tangem.datasource.api.tangemTech.TangemTechApiV2
|
||||||
import com.tangem.datasource.api.visa.TangemVisaApi
|
import com.tangem.datasource.api.pay.TangemPayApi
|
||||||
import com.tangem.datasource.local.logs.AppLogsStore
|
import com.tangem.datasource.local.logs.AppLogsStore
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.datasource.utils.*
|
import com.tangem.datasource.utils.*
|
||||||
|
|
@ -206,9 +206,9 @@ internal object NetworkModule {
|
||||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||||
apiConfigsManager: ApiConfigsManager,
|
apiConfigsManager: ApiConfigsManager,
|
||||||
appLogsStore: AppLogsStore,
|
appLogsStore: AppLogsStore,
|
||||||
): TangemVisaApi {
|
): TangemPayApi {
|
||||||
return createApi<TangemVisaApi>(
|
return createApi<TangemPayApi>(
|
||||||
id = ApiConfig.ID.TangemVisa,
|
id = ApiConfig.ID.TangemPay,
|
||||||
moshi = moshi,
|
moshi = moshi,
|
||||||
context = context,
|
context = context,
|
||||||
apiConfigsManager = apiConfigsManager,
|
apiConfigsManager = apiConfigsManager,
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package com.tangem.datasource.local.quote.converter
|
|
||||||
|
|
||||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
|
||||||
import com.tangem.domain.models.StatusSource
|
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
|
||||||
import com.tangem.domain.tokens.model.Quote
|
|
||||||
import com.tangem.utils.converter.Converter
|
|
||||||
import com.tangem.utils.extensions.orZero
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converter from [QuotesResponse.Quote] to [Quote.Value]
|
|
||||||
*
|
|
||||||
* @property source status source
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
class QuoteConverter(
|
|
||||||
private val source: StatusSource,
|
|
||||||
) :
|
|
||||||
Converter<Map.Entry<String, QuotesResponse.Quote>, Quote.Value> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Secondary constructor
|
|
||||||
*
|
|
||||||
* @param isCached flag that determines whether the quote is a cache
|
|
||||||
*/
|
|
||||||
constructor(isCached: Boolean) : this(
|
|
||||||
source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun convert(value: Map.Entry<String, QuotesResponse.Quote>): Quote.Value {
|
|
||||||
val (currencyId, quote) = value
|
|
||||||
|
|
||||||
return Quote.Value(
|
|
||||||
rawCurrencyId = CryptoCurrency.RawID(currencyId),
|
|
||||||
fiatRate = quote.price.orZero(),
|
|
||||||
priceChange = quote.priceChange24h.orZero().movePointLeft(2),
|
|
||||||
source = source,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -37,7 +37,7 @@ private val API_CONFIGS = setOf(
|
||||||
Express(configManager, expressAuthProvider, appVersionProvider, appInfoProvider),
|
Express(configManager, expressAuthProvider, appVersionProvider, appInfoProvider),
|
||||||
TangemTech(appVersionProvider, appAuthProvider, appInfoProvider),
|
TangemTech(appVersionProvider, appAuthProvider, appInfoProvider),
|
||||||
StakeKit(stakeKitAuthProvider),
|
StakeKit(stakeKitAuthProvider),
|
||||||
TangemVisa(appVersionProvider),
|
TangemPay(appVersionProvider),
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -88,7 +88,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
||||||
is Express -> createExpressModel()
|
is Express -> createExpressModel()
|
||||||
is TangemTech -> createTangemTechModel()
|
is TangemTech -> createTangemTechModel()
|
||||||
is StakeKit -> createStakeKitModel()
|
is StakeKit -> createStakeKitModel()
|
||||||
is TangemVisa -> createVisaModel()
|
is TangemPay -> createVisaModel()
|
||||||
is Attestation -> createAttestationModel()
|
is Attestation -> createAttestationModel()
|
||||||
is BlockAid -> createBlockAidSdkModel()
|
is BlockAid -> createBlockAidSdkModel()
|
||||||
}
|
}
|
||||||
|
|
@ -182,7 +182,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
||||||
|
|
||||||
private fun createVisaModel(): Model {
|
private fun createVisaModel(): Model {
|
||||||
return Model(
|
return Model(
|
||||||
id = ApiConfig.ID.TangemVisa,
|
id = ApiConfig.ID.TangemPay,
|
||||||
expected = ApiEnvironmentConfig(
|
expected = ApiEnvironmentConfig(
|
||||||
environment = ApiEnvironment.DEV,
|
environment = ApiEnvironment.DEV,
|
||||||
baseUrl = "[REDACTED_ENV_URL]",
|
baseUrl = "[REDACTED_ENV_URL]",
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
plugins {
|
plugins {
|
||||||
alias(deps.plugins.kotlin.jvm)
|
alias(deps.plugins.android.library)
|
||||||
|
alias(deps.plugins.kotlin.android)
|
||||||
alias(deps.plugins.kotlin.kapt)
|
alias(deps.plugins.kotlin.kapt)
|
||||||
id("configuration")
|
id("configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.tangem.core.decompose"
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.core.utils)
|
api(projects.core.utils)
|
||||||
|
|
||||||
api(deps.decompose)
|
api(deps.decompose)
|
||||||
|
api(deps.androidx.appCompat)
|
||||||
implementation(deps.kotlin.coroutines)
|
implementation(deps.kotlin.coroutines)
|
||||||
|
|
||||||
implementation(deps.hilt.core)
|
implementation(deps.hilt.core)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.arkivanov.decompose.ComponentContext
|
||||||
import com.tangem.core.decompose.di.HiltComponentBuilderOwner
|
import com.tangem.core.decompose.di.HiltComponentBuilderOwner
|
||||||
import com.tangem.core.decompose.navigation.NavigationOwner
|
import com.tangem.core.decompose.navigation.NavigationOwner
|
||||||
import com.tangem.core.decompose.ui.UiMessageSenderOwner
|
import com.tangem.core.decompose.ui.UiMessageSenderOwner
|
||||||
|
import com.tangem.core.decompose.utils.ActivityHolder
|
||||||
import com.tangem.core.decompose.utils.ComponentScopeOwner
|
import com.tangem.core.decompose.utils.ComponentScopeOwner
|
||||||
import com.tangem.core.decompose.utils.DispatchersOwner
|
import com.tangem.core.decompose.utils.DispatchersOwner
|
||||||
import com.tangem.core.decompose.utils.TagsOwner
|
import com.tangem.core.decompose.utils.TagsOwner
|
||||||
|
|
@ -20,4 +21,5 @@ interface AppComponentContext :
|
||||||
DispatchersOwner,
|
DispatchersOwner,
|
||||||
UiMessageSenderOwner,
|
UiMessageSenderOwner,
|
||||||
HiltComponentBuilderOwner,
|
HiltComponentBuilderOwner,
|
||||||
TagsOwner
|
TagsOwner,
|
||||||
|
ActivityHolder
|
||||||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.decompose.ui.DefaultUiMessageSender
|
||||||
import com.tangem.core.decompose.ui.UiMessageHandler
|
import com.tangem.core.decompose.ui.UiMessageHandler
|
||||||
import com.tangem.core.decompose.ui.UiMessageSender
|
import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
import com.tangem.core.decompose.ui.UiMessageSenderOwner
|
import com.tangem.core.decompose.ui.UiMessageSenderOwner
|
||||||
|
import com.tangem.core.decompose.utils.ActivityHolder
|
||||||
import com.tangem.core.decompose.utils.ComponentCoroutineScope
|
import com.tangem.core.decompose.utils.ComponentCoroutineScope
|
||||||
import com.tangem.core.decompose.utils.DispatchersOwner
|
import com.tangem.core.decompose.utils.DispatchersOwner
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -58,7 +59,8 @@ fun AppComponentContext.childByContext(
|
||||||
NavigationOwner by this@childByContext,
|
NavigationOwner by this@childByContext,
|
||||||
UiMessageSenderOwner by this@childByContext,
|
UiMessageSenderOwner by this@childByContext,
|
||||||
DispatchersOwner by this@childByContext,
|
DispatchersOwner by this@childByContext,
|
||||||
HiltComponentBuilderOwner by this@childByContext {
|
HiltComponentBuilderOwner by this@childByContext,
|
||||||
|
ActivityHolder by this@childByContext {
|
||||||
|
|
||||||
override val tags: HashMap<String, Any> = HashMap()
|
override val tags: HashMap<String, Any> = HashMap()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.core.decompose.context
|
package com.tangem.core.decompose.context
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.arkivanov.decompose.ComponentContext
|
import com.arkivanov.decompose.ComponentContext
|
||||||
import com.arkivanov.essenty.instancekeeper.getOrCreate
|
import com.arkivanov.essenty.instancekeeper.getOrCreate
|
||||||
import com.tangem.core.decompose.di.ModelComponent
|
import com.tangem.core.decompose.di.ModelComponent
|
||||||
|
|
@ -17,6 +18,7 @@ class DefaultAppComponentContext(
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
override val hiltComponentBuilder: ModelComponent.Builder,
|
override val hiltComponentBuilder: ModelComponent.Builder,
|
||||||
override val messageSender: UiMessageSender,
|
override val messageSender: UiMessageSender,
|
||||||
|
override val activity: AppCompatActivity,
|
||||||
private val replaceRouter: Router? = null,
|
private val replaceRouter: Router? = null,
|
||||||
) : AppComponentContext, ComponentContext by componentContext {
|
) : AppComponentContext, ComponentContext by componentContext {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.tangem.core.decompose.utils
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for holding an [AppCompatActivity] instance.
|
||||||
|
*
|
||||||
|
* This interface is used to provide access to the activity in which the component is running.
|
||||||
|
*/
|
||||||
|
interface ActivityHolder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [AppCompatActivity] instance associated with this context.
|
||||||
|
*/
|
||||||
|
val activity: AppCompatActivity
|
||||||
|
}
|
||||||
|
|
@ -1299,8 +1299,8 @@
|
||||||
<string name="warning_token_balance_not_updated">Der Kontostand ist möglicherweise veraltet. Aktualisiere bittre die Seite.</string>
|
<string name="warning_token_balance_not_updated">Der Kontostand ist möglicherweise veraltet. Aktualisiere bittre die Seite.</string>
|
||||||
<string name="warning_token_required_min_coin_reserve">Nicht genug %1$s. Lade Dein XLM-Konto auf, um dieses Token zu verknüpfen</string>
|
<string name="warning_token_required_min_coin_reserve">Nicht genug %1$s. Lade Dein XLM-Konto auf, um dieses Token zu verknüpfen</string>
|
||||||
<string name="warning_token_trustline_button_title">Trustline aktivieren</string>
|
<string name="warning_token_trustline_button_title">Trustline aktivieren</string>
|
||||||
<string name="warning_token_trustline_subtiile">Um dieses Token zu erhalten, muss eine Trustline aktiviert sein. Das Netzwerk benötigt eine Reserve von %1$s %2$s.</string>
|
<string name="warning_token_trustline_subtitle">Um dieses Token zu erhalten, muss eine Trustline aktiviert sein. Das Netzwerk benötigt eine Reserve von %1$s %2$s.</string>
|
||||||
<string name="warning_token_trustline_tiile">Trustline erforderlich</string>
|
<string name="warning_token_trustline_title">Trustline erforderlich</string>
|
||||||
<string name="wc_alert_audit_unknown_domain">Unbekannte Domäne</string>
|
<string name="wc_alert_audit_unknown_domain">Unbekannte Domäne</string>
|
||||||
<string name="wc_alert_connect_anyway">Trotzdem verbinden</string>
|
<string name="wc_alert_connect_anyway">Trotzdem verbinden</string>
|
||||||
<string name="wc_alert_connection_timeout_description">Zeitüberschreitungsfehler. Bitte versuche es später erneut.</string>
|
<string name="wc_alert_connection_timeout_description">Zeitüberschreitungsfehler. Bitte versuche es später erneut.</string>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue