Updated on 2026-08-14
|
|
@ -273,6 +273,8 @@ dependencies {
|
||||||
implementation(projects.features.welcome.impl)
|
implementation(projects.features.welcome.impl)
|
||||||
implementation(projects.features.createWalletSelection.api)
|
implementation(projects.features.createWalletSelection.api)
|
||||||
implementation(projects.features.createWalletSelection.impl)
|
implementation(projects.features.createWalletSelection.impl)
|
||||||
|
implementation(projects.features.createWalletStart.api)
|
||||||
|
implementation(projects.features.createWalletStart.impl)
|
||||||
implementation(projects.features.home.api)
|
implementation(projects.features.home.api)
|
||||||
implementation(projects.features.home.impl)
|
implementation(projects.features.home.impl)
|
||||||
implementation(projects.features.account.api)
|
implementation(projects.features.account.api)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package com.tangem.common
|
package com.tangem.common
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import androidx.compose.ui.test.isRoot
|
||||||
import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
||||||
import androidx.compose.ui.test.onRoot
|
|
||||||
import androidx.compose.ui.test.printToLog
|
import androidx.compose.ui.test.printToLog
|
||||||
import androidx.test.core.app.ActivityScenario
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.espresso.intent.Intents
|
import androidx.test.espresso.intent.Intents
|
||||||
|
|
@ -121,6 +121,8 @@ abstract class BaseTestCase : TestCase(
|
||||||
/**
|
/**
|
||||||
* Prints the Compose semantics tree to logcat for debugging UI tests.
|
* Prints the Compose semantics tree to logcat for debugging UI tests.
|
||||||
*
|
*
|
||||||
|
* @param rootIndex Use rootIndex > 0, if you need to print semantics tree for bottom sheet.
|
||||||
|
* Default: 0.
|
||||||
* @param useUnmergedTree When true, shows unmerged tree with all individual nodes.
|
* @param useUnmergedTree When true, shows unmerged tree with all individual nodes.
|
||||||
* Use for accessing inner elements of compound components.
|
* Use for accessing inner elements of compound components.
|
||||||
* Default: false (merged tree - accessibility view).
|
* Default: false (merged tree - accessibility view).
|
||||||
|
|
@ -129,11 +131,13 @@ abstract class BaseTestCase : TestCase(
|
||||||
* Default: Int.MAX_VALUE (unlimited depth).
|
* Default: Int.MAX_VALUE (unlimited depth).
|
||||||
*/
|
*/
|
||||||
fun printSemanticTree(
|
fun printSemanticTree(
|
||||||
|
rootIndex: Int = 0,
|
||||||
useUnmergedTree: Boolean = false,
|
useUnmergedTree: Boolean = false,
|
||||||
tag: String = "SEMANTIC_TREE",
|
tag: String = "SEMANTIC_TREE",
|
||||||
maxDepth: Int = Int.MAX_VALUE)
|
maxDepth: Int = Int.MAX_VALUE
|
||||||
{
|
) {
|
||||||
composeTestRule.onRoot(useUnmergedTree = useUnmergedTree).printToLog(tag, maxDepth)
|
composeTestRule.onAllNodes(isRoot(), useUnmergedTree = useUnmergedTree)[rootIndex]
|
||||||
|
.printToLog(tag, maxDepth)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun waitForIdle() = composeTestRule.waitForIdle()
|
fun waitForIdle() = composeTestRule.waitForIdle()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ object TestConstants {
|
||||||
const val TOTAL_BALANCE = "$3,299.18"
|
const val TOTAL_BALANCE = "$3,299.18"
|
||||||
|
|
||||||
const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"
|
const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"
|
||||||
|
const val BITCOIN_ADDRESS = "bc1qtg9aa6jcpqtvun0pe0uct7sxm8nq2nsxfmfxm3"
|
||||||
const val CARDANO_ADDRESS = "addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p"
|
const val CARDANO_ADDRESS = "addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p"
|
||||||
|
|
||||||
const val WAIT_UNTIL_TIMEOUT = 20_000L
|
const val WAIT_UNTIL_TIMEOUT = 20_000L
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.tangem.common.utils
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
|
||||||
|
fun getClipboardText(context: Context): String? {
|
||||||
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
return if (clipboard.hasPrimaryClip()) {
|
||||||
|
clipboard.primaryClip?.getItemAt(0)?.text?.toString()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setClipboardText(context: Context, text: String?) {
|
||||||
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip = ClipData.newPlainText("label", text)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearClipboard(
|
||||||
|
context: Context = ApplicationProvider.getApplicationContext()
|
||||||
|
) {
|
||||||
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
clipboard.clearPrimaryClip()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertClipboardTextEquals(
|
||||||
|
expected: String,
|
||||||
|
context: Context = ApplicationProvider.getApplicationContext()
|
||||||
|
) {
|
||||||
|
val actual = getClipboardText(context)
|
||||||
|
assert(actual == expected) {
|
||||||
|
"Clipboard text mismatch.\nExpected: '$expected'\nActual: '$actual'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -85,4 +85,13 @@ fun BaseTestCase.openDeviceSettingsScreen() {
|
||||||
step("Click on 'Device settings' button") {
|
step("Click on 'Device settings' button") {
|
||||||
onWalletSettingsScreen { deviceSettingsButton.clickWithAssertion() }
|
onWalletSettingsScreen { deviceSettingsButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseTestCase.openWalletConnectScreen() {
|
||||||
|
step("Click 'More' button on TopBar") {
|
||||||
|
onTopBar { moreButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Click on 'Wallet Connect' button") {
|
||||||
|
onDetailsScreen { walletConnectButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
package com.tangem.scenarios
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.screens.onStakingConfirmScreen
|
||||||
|
import com.tangem.screens.onStakingDetailsScreen
|
||||||
|
import io.qameta.allure.kotlin.Allure.step
|
||||||
|
|
||||||
|
fun BaseTestCase.checkStakingDetailsScreen(withStaking: Boolean) {
|
||||||
|
step("Assert 'Title' is displayed") {
|
||||||
|
onStakingDetailsScreen { stakingTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Annual percentage rate' is displayed") {
|
||||||
|
onStakingDetailsScreen { annualPercentageRate.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Available' block is displayed") {
|
||||||
|
onStakingDetailsScreen { availableBlock.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Unbonding Period' block is displayed") {
|
||||||
|
onStakingDetailsScreen { unbondingPeriodBlock.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Reward claiming' block is displayed") {
|
||||||
|
onStakingDetailsScreen { rewardClaimingBlock.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Reward schedule' block is displayed") {
|
||||||
|
onStakingDetailsScreen { rewardScheduleBlock.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'ToS' text is displayed") {
|
||||||
|
onStakingDetailsScreen { toSText.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
if (withStaking) {
|
||||||
|
step("Assert 'Rewards block' is displayed") {
|
||||||
|
onStakingDetailsScreen { rewardsBlock.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Rewards block' title is displayed") {
|
||||||
|
onStakingDetailsScreen { rewardsBlockTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Rewards block' text is displayed") {
|
||||||
|
onStakingDetailsScreen { rewardsBlockText.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Active staking block' is displayed") {
|
||||||
|
onStakingDetailsScreen { activeStakingBlock.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Your stakes' title is displayed") {
|
||||||
|
onStakingDetailsScreen { yourStakesTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Stake more' button is displayed") {
|
||||||
|
onStakingDetailsScreen { stakeMoreButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
step("Assert banner image is displayed") {
|
||||||
|
onStakingDetailsScreen { bannerImage.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert banner text is displayed") {
|
||||||
|
onStakingDetailsScreen { bannerText.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Stake' button is displayed") {
|
||||||
|
onStakingDetailsScreen { stakeButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
fun BaseTestCase.checkStakingScreen(stakingAmount: String) {
|
||||||
|
step("Assert 'Staking' screen is displayed") {
|
||||||
|
onSendScreen { screenContainer.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert top app bar 'Close' button is displayed") {
|
||||||
|
onSendScreen { closeButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Send' screen title is displayed") {
|
||||||
|
onSendScreen { title.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert amount container title is displayed") {
|
||||||
|
onSendScreen { amountContainerTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert input text field is displayed") {
|
||||||
|
onSendScreen { amountInputTextField.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert token name is displayed") {
|
||||||
|
onSendScreen { tokenName.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert primary amount is displayed") {
|
||||||
|
onSendScreen { primaryAmount.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert secondary amount is displayed") {
|
||||||
|
onSendScreen { secondaryAmount.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Type '$stakingAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(stakingAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert input text field has value: '$stakingAmount'") {
|
||||||
|
onSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
|
||||||
|
}
|
||||||
|
step("Assert 'Max' button is displayed") {
|
||||||
|
onSendScreen { maxButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is displayed") {
|
||||||
|
onSendScreen { nextButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseTestCase.checkStakingConfirmScreen() {
|
||||||
|
step("Assert 'Staking confirm' screen title is displayed") {
|
||||||
|
onStakingConfirmScreen { title.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert primary amount is displayed") {
|
||||||
|
onStakingConfirmScreen { primaryAmount.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert secondary amount is displayed") {
|
||||||
|
onStakingConfirmScreen { secondaryAmount.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Validator' block is displayed") {
|
||||||
|
onStakingConfirmScreen { validatorBlock.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Network Fee' block is displayed") {
|
||||||
|
onStakingConfirmScreen { networkFeeBlock.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Stake' button is displayed") {
|
||||||
|
onStakingConfirmScreen { stakeButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import com.tangem.screens.onWalletConnectScreen
|
||||||
import io.qameta.allure.kotlin.Allure.step
|
import io.qameta.allure.kotlin.Allure.step
|
||||||
|
|
||||||
fun BaseTestCase.checkWalletConnectBottomSheet() {
|
fun BaseTestCase.checkWalletConnectBottomSheet() {
|
||||||
|
waitForIdle()
|
||||||
step("Assert 'Wallet Connect' bottom sheet title is displayed") {
|
step("Assert 'Wallet Connect' bottom sheet title is displayed") {
|
||||||
onWalletConnectBottomSheet { title.assertIsDisplayed() }
|
onWalletConnectBottomSheet { title.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
|
|
@ -60,34 +61,64 @@ fun BaseTestCase.checkWalletConnectBottomSheet() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BaseTestCase.checkWalletConnectScreen() {
|
fun BaseTestCase.checkWalletConnectScreen(withConnections: Boolean) {
|
||||||
|
waitForIdle()
|
||||||
step("Assert 'Wallet Connect' title is displayed") {
|
step("Assert 'Wallet Connect' title is displayed") {
|
||||||
onWalletConnectScreen { title.assertIsDisplayed() }
|
onWalletConnectScreen { title.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
step("Assert 'More' button is displayed") {
|
|
||||||
onWalletConnectScreen { moreButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert wallet name is displayed") {
|
|
||||||
onWalletConnectScreen { walletName.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert app icon is displayed") {
|
|
||||||
onWalletConnectScreen { appIcon.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert app name is displayed") {
|
|
||||||
onWalletConnectScreen { appName.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert approve icon is displayed") {
|
|
||||||
onWalletConnectScreen { approveIcon.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert app URL is displayed") {
|
|
||||||
onWalletConnectScreen { appUrl.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'New Connection' button is displayed") {
|
step("Assert 'New Connection' button is displayed") {
|
||||||
onWalletConnectScreen { newConnectionButton.assertIsDisplayed() }
|
onWalletConnectScreen { newConnectionButton.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
|
if (withConnections) {
|
||||||
|
step("Assert 'More' button is displayed") {
|
||||||
|
onWalletConnectScreen { moreButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert wallet name is displayed") {
|
||||||
|
onWalletConnectScreen { walletName.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert app icon is displayed") {
|
||||||
|
onWalletConnectScreen { appIcon.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert app name is displayed") {
|
||||||
|
onWalletConnectScreen { appName.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert approve icon is displayed") {
|
||||||
|
onWalletConnectScreen { approveIcon.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert app URL is displayed") {
|
||||||
|
onWalletConnectScreen { appUrl.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
step("Assert wallet name is not displayed") {
|
||||||
|
onWalletConnectScreen { walletName.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert app icon is not displayed") {
|
||||||
|
onWalletConnectScreen { appIcon.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert app name is not displayed") {
|
||||||
|
onWalletConnectScreen { appName.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert approve icon is not displayed") {
|
||||||
|
onWalletConnectScreen { approveIcon.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert app URL is not displayed") {
|
||||||
|
onWalletConnectScreen { appUrl.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Wallet Connect' image is displayed") {
|
||||||
|
onWalletConnectScreen { walletConnectImage.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'No session' title is displayed") {
|
||||||
|
onWalletConnectScreen { noSessionTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'No session' text is displayed") {
|
||||||
|
onWalletConnectScreen { noSessionText.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BaseTestCase.checkWalletConnectDetailsBottomSheet(dAppName: String) {
|
fun BaseTestCase.checkWalletConnectDetailsBottomSheet(dAppName: String) {
|
||||||
|
waitForIdle()
|
||||||
step("Assert connection details title is displayed") {
|
step("Assert connection details title is displayed") {
|
||||||
onWalletConnectDetailsBottomSheet { title.assertIsDisplayed() }
|
onWalletConnectDetailsBottomSheet { title.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +159,7 @@ fun BaseTestCase.checkWalletConnectDetailsBottomSheet(dAppName: String) {
|
||||||
onWalletConnectDetailsBottomSheet { connectedNetworkIcon.assertIsDisplayed() }
|
onWalletConnectDetailsBottomSheet { connectedNetworkIcon.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
step("Assert connected dApp name: '$dAppName'") {
|
step("Assert connected dApp name: '$dAppName'") {
|
||||||
onWalletConnectDetailsBottomSheet { connectedNetworkName.assertTextContains(dAppName) }
|
onWalletConnectDetailsBottomSheet { appName.assertTextContains(dAppName) }
|
||||||
}
|
}
|
||||||
step("Assert connected network symbol is displayed") {
|
step("Assert connected network symbol is displayed") {
|
||||||
onWalletConnectDetailsBottomSheet { connectedNetworkSymbol.assertIsDisplayed() }
|
onWalletConnectDetailsBottomSheet { connectedNetworkSymbol.assertIsDisplayed() }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.uiautomator.By
|
||||||
|
import androidx.test.uiautomator.UiDevice
|
||||||
|
import androidx.test.uiautomator.UiObject2
|
||||||
|
import androidx.test.uiautomator.Until
|
||||||
|
import com.kaspersky.kaspresso.screens.KScreen
|
||||||
|
|
||||||
|
object ChromeBrowserPageObject : KScreen<ChromeBrowserPageObject>() {
|
||||||
|
|
||||||
|
override val layoutId: Int? = null
|
||||||
|
override val viewClass: Class<*>? = null
|
||||||
|
|
||||||
|
private val device: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||||
|
private const val TIMEOUT = 10000L
|
||||||
|
|
||||||
|
fun assertChromeIsOpened() {
|
||||||
|
val chromePackage = "com.android.chrome"
|
||||||
|
device.wait(Until.hasObject(By.pkg(chromePackage).depth(0)), TIMEOUT)
|
||||||
|
assert(device.hasObject(By.pkg(chromePackage))) {
|
||||||
|
"Chrome browser is not opened"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertUrlContains(expectedUrl: String) {
|
||||||
|
val urlBar = device.findObject(
|
||||||
|
By.res("com.android.chrome:id/url_bar")
|
||||||
|
)
|
||||||
|
urlBar?.let {
|
||||||
|
assert(it.text.contains(expectedUrl)) {
|
||||||
|
"URL doesn't contain expected: $expectedUrl, actual: ${it.text}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findElementByText(text: String): UiObject2? {
|
||||||
|
device.wait(Until.hasObject(By.text(text)), TIMEOUT)
|
||||||
|
return device.findObject(By.text(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertElementWithTextExists(text: String) {
|
||||||
|
val element = findElementByText(text)
|
||||||
|
assert(element != null) { "Element with text '$text' not found" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isElementWithTextExists(text: String): Boolean {
|
||||||
|
return device.wait(Until.hasObject(By.text(text)), TIMEOUT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clickOnElementWithText(text: String) {
|
||||||
|
findElementByText(text)?.click()
|
||||||
|
?: throw AssertionError("Cannot click - element with text '$text' not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ import io.github.kakaocup.compose.node.element.lazylist.KLazyListNode
|
||||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||||
import androidx.compose.ui.test.hasText as withText
|
import androidx.compose.ui.test.hasText as withText
|
||||||
|
import com.tangem.core.ui.R as CoreUiR
|
||||||
|
|
||||||
class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
ComposeScreen<MainScreenPageObject>(semanticsProvider = semanticsProvider) {
|
ComposeScreen<MainScreenPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
@ -200,6 +201,10 @@ class MainScreenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val snackbarCopiedAddressMessage: KNode = child {
|
||||||
|
hasText(getResourceString(CoreUiR.string.wallet_notification_address_copied))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find token list item with title and address
|
* Find token list item with title and address
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,19 @@ package com.tangem.screens
|
||||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.test.BaseBottomSheetTestTags
|
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.KNode
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
|
||||||
class BaseBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
class ReceiveAssetsBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
ComposeScreen<BaseBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
|
ComposeScreen<ReceiveAssetsBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
val hideButton: KNode = child {
|
val showQrCodeButton: KNode = child {
|
||||||
hasText(getResourceString(R.string.token_details_hide_token))
|
hasText(getResourceString(R.string.token_receive_show_qr_code_title))
|
||||||
hasTestTag(BaseBottomSheetTestTags.ACTION_TITLE)
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun BaseTestCase.onBottomSheet(function: BaseBottomSheetPageObject.() -> Unit) =
|
internal fun BaseTestCase.onReceiveAssetsBottomSheet(function: ReceiveAssetsBottomSheetPageObject.() -> Unit) =
|
||||||
onComposeScreen(composeTestRule, function)
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -10,7 +10,6 @@ import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onCompose
|
||||||
import io.github.kakaocup.compose.node.element.KNode
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
import com.tangem.features.send.v2.impl.R as SendR
|
import com.tangem.features.send.v2.impl.R as SendR
|
||||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
|
||||||
|
|
||||||
class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
ComposeScreen<SendPageObject>(semanticsProvider = semanticsProvider) {
|
ComposeScreen<SendPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
@ -19,6 +18,11 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
hasTestTag(SendScreenTestTags.SCREEN_CONTAINER)
|
hasTestTag(SendScreenTestTags.SCREEN_CONTAINER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val closeButton: KNode = child {
|
||||||
|
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
val title: KNode = child {
|
val title: KNode = child {
|
||||||
hasTestTag(TopAppBarTestTags.TITLE)
|
hasTestTag(TopAppBarTestTags.TITLE)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
|
|
@ -29,13 +33,18 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val amountContainerText: KNode = child {
|
val amountInputTextField: KNode = child {
|
||||||
hasTestTag(SendScreenTestTags.AMOUNT_CONTAINER_TEXT)
|
hasTestTag(SendScreenTestTags.INPUT_TEXT_FIELD)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val amountInputTextField: KNode = child {
|
val tokenName: KNode = child {
|
||||||
hasTestTag(SendScreenTestTags.INPUT_TEXT_FIELD)
|
hasTestTag(SendScreenTestTags.TOKEN_NAME)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val primaryAmount: KNode = child {
|
||||||
|
hasTestTag(SendScreenTestTags.PRIMARY_AMOUNT)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,28 +53,11 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val currencyButton: KNode = child {
|
|
||||||
hasTestTag(SendScreenTestTags.CURRENCY_BUTTON)
|
|
||||||
hasAnyChild(withTestTag(SendScreenTestTags.CURRENCY_ICON))
|
|
||||||
useUnmergedTree = true
|
|
||||||
}
|
|
||||||
|
|
||||||
val fiatButton: KNode = child {
|
|
||||||
hasTestTag(SendScreenTestTags.CURRENCY_BUTTON)
|
|
||||||
hasAnyChild(withTestTag(SendScreenTestTags.FIAT_ICON))
|
|
||||||
useUnmergedTree = true
|
|
||||||
}
|
|
||||||
|
|
||||||
val maxButton: KNode = child {
|
val maxButton: KNode = child {
|
||||||
hasTestTag(SendScreenTestTags.MAX_BUTTON)
|
hasTestTag(SendScreenTestTags.MAX_BUTTON)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val previousButton: KNode = child {
|
|
||||||
hasTestTag(SendScreenTestTags.PREVIOUS_BUTTON)
|
|
||||||
useUnmergedTree = true
|
|
||||||
}
|
|
||||||
|
|
||||||
val nextButton: KNode = child {
|
val nextButton: KNode = child {
|
||||||
hasTestTag(BaseButtonTestTags.TEXT)
|
hasTestTag(BaseButtonTestTags.TEXT)
|
||||||
hasText(getResourceString(SendR.string.common_next))
|
hasText(getResourceString(SendR.string.common_next))
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,10 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
|
||||||
hasText(getResourceString(R.string.common_swap))
|
hasText(getResourceString(R.string.common_swap))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun tokenSymbol(symbol: String): KNode = child {
|
||||||
|
hasTestTag(SwapTokenScreenTestTags.TOKEN_SYMBOL)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun BaseTestCase.onSwapTokenScreen(function: SwapTokenPageObject.() -> Unit) =
|
internal fun BaseTestCase.onSwapTokenScreen(function: SwapTokenPageObject.() -> Unit) =
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.test.BaseBottomSheetTestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||||
|
import androidx.compose.ui.test.hasText as withText
|
||||||
|
|
||||||
|
class TokenActionsBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<TokenActionsBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
|
val analyticsButton: KNode = child {
|
||||||
|
hasAnyChild(withText(getResourceString(R.string.common_analytics)))
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
|
||||||
|
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val copyAddressButton: KNode = child {
|
||||||
|
hasAnyChild(withText(getResourceString(R.string.common_copy_address)))
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
|
||||||
|
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val receiveButton: KNode = child {
|
||||||
|
hasAnyChild(withText(getResourceString(R.string.common_receive)))
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
|
||||||
|
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val sendButton: KNode = child {
|
||||||
|
hasAnyChild(withText(getResourceString(R.string.common_send)))
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
|
||||||
|
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val swapButton: KNode = child {
|
||||||
|
hasAnyChild(withText(getResourceString(R.string.common_swap)))
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
|
||||||
|
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val buyButton: KNode = child {
|
||||||
|
hasAnyChild(withText(getResourceString(R.string.common_buy)))
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
|
||||||
|
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val sellButton: KNode = child {
|
||||||
|
hasAnyChild(withText(getResourceString(R.string.common_sell)))
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
|
||||||
|
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val hideTokenButton: KNode = child {
|
||||||
|
hasAnyChild(withText(getResourceString(R.string.token_details_hide_token)))
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
|
||||||
|
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun BaseTestCase.onTokenActionsBottomSheet(function: TokenActionsBottomSheetPageObject.() -> Unit) =
|
||||||
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.core.ui.test.BaseBottomSheetTestTags
|
||||||
|
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||||
|
import com.tangem.core.ui.test.TokenReceiveQrCodeBottomSheetTestTags
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
|
||||||
|
class TokenReceiveQrCodeBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<TokenReceiveQrCodeBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
|
val closeButton: KNode = child {
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.CLOSE_BUTTON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val title: KNode = child {
|
||||||
|
hasTestTag(TokenReceiveQrCodeBottomSheetTestTags.TITLE)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val qrCode: KNode = child {
|
||||||
|
hasTestTag(TokenReceiveQrCodeBottomSheetTestTags.QR_CODE)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val addressTitle: KNode = child {
|
||||||
|
hasText(getResourceString(R.string.wc_common_address))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val address: KNode = child {
|
||||||
|
hasTestTag(TokenReceiveQrCodeBottomSheetTestTags.ADDRESS)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val copyButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.TEXT)
|
||||||
|
hasText(getResourceString(R.string.common_copy))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val shareButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.TEXT)
|
||||||
|
hasText(getResourceString(R.string.common_share))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun BaseTestCase.onTokenReceiveQrCodeBottomSheet(function: TokenReceiveQrCodeBottomSheetPageObject.() -> Unit) =
|
||||||
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||||
|
import com.tangem.core.ui.test.TokenReceiveWarningBottomSheetTestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
|
||||||
|
class TokenReceiveWarningBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<TokenReceiveWarningBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
|
val bottomSheet: KNode = child {
|
||||||
|
hasTestTag(TokenReceiveWarningBottomSheetTestTags.BOTTOM_SHEET)
|
||||||
|
}
|
||||||
|
|
||||||
|
val gotItButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.TEXT)
|
||||||
|
hasText(getResourceString(R.string.common_got_it))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun BaseTestCase.onTokenReceiveWarningBottomSheet(function: TokenReceiveWarningBottomSheetPageObject.() -> Unit) =
|
||||||
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -9,13 +9,11 @@ import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.KNode
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
import com.tangem.features.walletconnect.impl.R as WalletConnectImplR
|
|
||||||
|
|
||||||
class WalletConnectBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
class WalletConnectBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
ComposeScreen<WalletConnectBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
|
ComposeScreen<WalletConnectBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
val title: KNode = child {
|
val title: KNode = child {
|
||||||
hasText(getResourceString(WalletConnectImplR.string.wc_wallet_connect))
|
|
||||||
hasTestTag(WalletConnectBottomSheetTestTags.TITLE)
|
hasTestTag(WalletConnectBottomSheetTestTags.TITLE)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,21 @@ class WalletConnectPageObject(semanticsProvider: SemanticsNodeInteractionsProvid
|
||||||
hasText(getResourceString(R.string.wc_new_connection))
|
hasText(getResourceString(R.string.wc_new_connection))
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val walletConnectImage: KNode = child {
|
||||||
|
hasTestTag(WalletConnectScreenTestTags.WALLET_CONNECT_IMAGE)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val noSessionTitle: KNode = child {
|
||||||
|
hasText(getResourceString(R.string.wc_no_sessions_title))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val noSessionText: KNode = child {
|
||||||
|
hasText(getResourceString(R.string.wc_no_sessions_desc))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun BaseTestCase.onWalletConnectScreen(function: WalletConnectPageObject.() -> Unit) =
|
internal fun BaseTestCase.onWalletConnectScreen(function: WalletConnectPageObject.() -> Unit) =
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
|
||||||
|
class WalletConnectScanQrPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<WalletConnectScanQrPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
|
val pasteFromClipboardButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.TEXT)
|
||||||
|
hasText(getResourceString(R.string.wallet_connect_paste_from_clipboard))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun BaseTestCase.onWalletConnectScanQrScreen(function: WalletConnectScanQrPageObject.() -> Unit) =
|
||||||
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
package com.tangem.tests
|
package com.tangem.tests
|
||||||
|
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.annotations.ApiEnv
|
||||||
|
import com.tangem.common.annotations.ApiEnvConfig
|
||||||
|
import com.tangem.datasource.api.common.config.ApiConfig
|
||||||
|
import com.tangem.datasource.api.common.config.ApiEnvironment
|
||||||
import com.tangem.domain.models.scan.ProductType
|
import com.tangem.domain.models.scan.ProductType
|
||||||
import com.tangem.scenarios.checkMultiCurrencyMainScreen
|
import com.tangem.scenarios.checkMultiCurrencyMainScreen
|
||||||
import com.tangem.scenarios.checkSingleCurrencyMainScreen
|
import com.tangem.scenarios.checkSingleCurrencyMainScreen
|
||||||
|
|
@ -15,6 +19,10 @@ import org.junit.Test
|
||||||
@HiltAndroidTest
|
@HiltAndroidTest
|
||||||
class ScanCardTest : BaseTestCase() {
|
class ScanCardTest : BaseTestCase() {
|
||||||
|
|
||||||
|
@ApiEnv(
|
||||||
|
ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD),
|
||||||
|
ApiEnvConfig(ApiConfig.ID.Express, ApiEnvironment.PROD)
|
||||||
|
)
|
||||||
@AllureId("868")
|
@AllureId("868")
|
||||||
@DisplayName("Scan: Scanning single-currency cards")
|
@DisplayName("Scan: Scanning single-currency cards")
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,8 @@ import com.tangem.common.extensions.clickWithAssertion
|
||||||
import com.tangem.common.extensions.swipeVertical
|
import com.tangem.common.extensions.swipeVertical
|
||||||
import com.tangem.common.utils.resetWireMockScenarioState
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
import com.tangem.common.utils.setWireMockScenarioState
|
import com.tangem.common.utils.setWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.*
|
||||||
import com.tangem.screens.*
|
import com.tangem.screens.*
|
||||||
import com.tangem.scenarios.openMainScreen
|
|
||||||
import com.tangem.scenarios.synchronizeAddresses
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import io.qameta.allure.kotlin.AllureId
|
import io.qameta.allure.kotlin.AllureId
|
||||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
|
@ -117,116 +116,20 @@ class StakingTest : BaseTestCase() {
|
||||||
step("Click on 'Staking block'") {
|
step("Click on 'Staking block'") {
|
||||||
onTokenDetailsScreen { stakingBlock.clickWithAssertion() }
|
onTokenDetailsScreen { stakingBlock.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Assert 'Title' is displayed") {
|
step("Check 'Staking details' screen") {
|
||||||
onStakingDetailsScreen { stakingTitle.assertIsDisplayed() }
|
checkStakingDetailsScreen(withStaking = true)
|
||||||
}
|
|
||||||
step("Assert 'Annual percentage rate' is displayed") {
|
|
||||||
onStakingDetailsScreen { annualPercentageRate.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Available' block is displayed") {
|
|
||||||
onStakingDetailsScreen { availableBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Unbonding Period' block is displayed") {
|
|
||||||
onStakingDetailsScreen { unbondingPeriodBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Reward claiming' block is displayed") {
|
|
||||||
onStakingDetailsScreen { rewardClaimingBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Reward schedule' block is displayed") {
|
|
||||||
onStakingDetailsScreen { rewardScheduleBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Rewards block' is displayed") {
|
|
||||||
onStakingDetailsScreen { rewardsBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Rewards block' title is displayed") {
|
|
||||||
onStakingDetailsScreen { rewardsBlockTitle.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Rewards block' text is displayed") {
|
|
||||||
onStakingDetailsScreen { rewardsBlockText.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Active staking block' is displayed") {
|
|
||||||
onStakingDetailsScreen { activeStakingBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Your stakes' title is displayed") {
|
|
||||||
onStakingDetailsScreen { yourStakesTitle.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'ToS' text is displayed") {
|
|
||||||
onStakingDetailsScreen { toSText.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Stake more' button is displayed") {
|
|
||||||
onStakingDetailsScreen { stakeMoreButton.assertIsDisplayed() }
|
|
||||||
}
|
}
|
||||||
step("Click 'Stake more' button") {
|
step("Click 'Stake more' button") {
|
||||||
onStakingDetailsScreen { stakeMoreButton.performClick() }
|
onStakingDetailsScreen { stakeMoreButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Assert 'Send' screen is displayed") {
|
step("Check 'Staking' screen") {
|
||||||
onSendScreen { screenContainer.assertIsDisplayed() }
|
checkStakingScreen(stakingAmount)
|
||||||
}
|
|
||||||
step("Assert 'Send' screen title is displayed") {
|
|
||||||
onSendScreen { title.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert amount container title is displayed") {
|
|
||||||
onSendScreen { amountContainerTitle.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert amount container text is displayed") {
|
|
||||||
onSendScreen { amountContainerText.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert input text field is displayed") {
|
|
||||||
onSendScreen { amountInputTextField.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert secondary amount is displayed") {
|
|
||||||
onSendScreen { secondaryAmount.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Type '$stakingAmount' in input text field") {
|
|
||||||
onSendScreen {
|
|
||||||
amountInputTextField.performClick()
|
|
||||||
amountInputTextField.performTextReplacement(stakingAmount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
step("Assert input text field has value: '$stakingAmount'") {
|
|
||||||
onSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
|
|
||||||
}
|
|
||||||
step("Assert currency button is displayed") {
|
|
||||||
onSendScreen { currencyButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert fiat button is displayed") {
|
|
||||||
onSendScreen { fiatButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert currency button is displayed") {
|
|
||||||
onSendScreen { currencyButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert fiat button is displayed") {
|
|
||||||
onSendScreen { fiatButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Max' button is displayed") {
|
|
||||||
onSendScreen { maxButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert previous button is displayed") {
|
|
||||||
onSendScreen { previousButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Next' button is displayed") {
|
|
||||||
onSendScreen { nextButton.assertIsDisplayed() }
|
|
||||||
}
|
}
|
||||||
step("Click on 'Next' button") {
|
step("Click on 'Next' button") {
|
||||||
onSendScreen { nextButton.performClick() }
|
onSendScreen { nextButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Assert 'Send details' screen title is displayed") {
|
step("Check 'Staking confirm' screen") {
|
||||||
onStakingConfirmScreen { title.assertIsDisplayed() }
|
checkStakingConfirmScreen()
|
||||||
}
|
|
||||||
step("Assert primary amount is displayed") {
|
|
||||||
onStakingConfirmScreen { primaryAmount.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert secondary amount is displayed") {
|
|
||||||
onStakingConfirmScreen { secondaryAmount.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Validator' block is displayed") {
|
|
||||||
onStakingConfirmScreen { validatorBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Network Fee' block is displayed") {
|
|
||||||
onStakingConfirmScreen { networkFeeBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Stake' button is displayed") {
|
|
||||||
onStakingConfirmScreen { stakeButton.assertIsDisplayed() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -284,107 +187,20 @@ class StakingTest : BaseTestCase() {
|
||||||
step("Click on 'Stake' button") {
|
step("Click on 'Stake' button") {
|
||||||
onTokenDetailsScreen { stakeButton.clickWithAssertion() }
|
onTokenDetailsScreen { stakeButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Assert 'Title' is displayed") {
|
step("Check 'Staking details' screen") {
|
||||||
onStakingDetailsScreen { stakingTitle.assertIsDisplayed() }
|
checkStakingDetailsScreen(withStaking = false)
|
||||||
}
|
|
||||||
step("Assert banner image is displayed") {
|
|
||||||
onStakingDetailsScreen { bannerImage.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert banner text is displayed") {
|
|
||||||
onStakingDetailsScreen { bannerText.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Annual percentage rate' is displayed") {
|
|
||||||
onStakingDetailsScreen { annualPercentageRate.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Available' block is displayed") {
|
|
||||||
onStakingDetailsScreen { availableBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Unbonding Period' block is displayed") {
|
|
||||||
onStakingDetailsScreen { unbondingPeriodBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Reward claiming' block is displayed") {
|
|
||||||
onStakingDetailsScreen { rewardClaimingBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Reward schedule' block is displayed") {
|
|
||||||
onStakingDetailsScreen { rewardScheduleBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'ToS' text is displayed") {
|
|
||||||
onStakingDetailsScreen { toSText.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Stake' button is displayed") {
|
|
||||||
onStakingDetailsScreen { stakeButton.assertIsDisplayed() }
|
|
||||||
}
|
}
|
||||||
step("Click 'Stake' button") {
|
step("Click 'Stake' button") {
|
||||||
onStakingDetailsScreen { stakeButton.performClick() }
|
onStakingDetailsScreen { stakeButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Assert 'Send' screen is displayed") {
|
step("Check 'Staking' screen") {
|
||||||
onSendScreen { screenContainer.assertIsDisplayed() }
|
checkStakingScreen(stakingAmount)
|
||||||
}
|
|
||||||
step("Assert 'Send' screen title is displayed") {
|
|
||||||
onSendScreen { title.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert amount container title is displayed") {
|
|
||||||
onSendScreen { amountContainerTitle.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert amount container text is displayed") {
|
|
||||||
onSendScreen { amountContainerText.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert input text field is displayed") {
|
|
||||||
onSendScreen { amountInputTextField.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert secondary amount is displayed") {
|
|
||||||
onSendScreen { secondaryAmount.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Type '$stakingAmount' in input text field") {
|
|
||||||
onSendScreen {
|
|
||||||
amountInputTextField.performClick()
|
|
||||||
amountInputTextField.performTextReplacement(stakingAmount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
step("Assert input text field has value: '$stakingAmount'") {
|
|
||||||
onSendScreen { amountInputTextField.assertTextContains(value = stakingAmount, substring = true) }
|
|
||||||
}
|
|
||||||
step("Assert currency button is displayed") {
|
|
||||||
onSendScreen { currencyButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert fiat button is displayed") {
|
|
||||||
onSendScreen { fiatButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert currency button is displayed") {
|
|
||||||
onSendScreen { currencyButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert fiat button is displayed") {
|
|
||||||
onSendScreen { fiatButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Max' button is displayed") {
|
|
||||||
onSendScreen { maxButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert previous button is displayed") {
|
|
||||||
onSendScreen { previousButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Next' button is displayed") {
|
|
||||||
onSendScreen { nextButton.assertIsDisplayed() }
|
|
||||||
}
|
}
|
||||||
step("Click on 'Next' button") {
|
step("Click on 'Next' button") {
|
||||||
onSendScreen { nextButton.performClick() }
|
onSendScreen { nextButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Assert 'Send details' screen title is displayed") {
|
step("Check 'Staking confirm' screen") {
|
||||||
onStakingConfirmScreen { title.assertIsDisplayed() }
|
checkStakingConfirmScreen()
|
||||||
}
|
|
||||||
step("Assert primary amount is displayed") {
|
|
||||||
onStakingConfirmScreen { primaryAmount.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert secondary amount is displayed") {
|
|
||||||
onStakingConfirmScreen { secondaryAmount.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Validator' block is displayed") {
|
|
||||||
onStakingConfirmScreen { validatorBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Network Fee' block is displayed") {
|
|
||||||
onStakingConfirmScreen { networkFeeBlock.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Stake' button is displayed") {
|
|
||||||
onStakingConfirmScreen { stakeButton.assertIsDisplayed() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ package com.tangem.tests
|
||||||
|
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||||
import com.tangem.common.extensions.SwipeDirection
|
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||||
import com.tangem.common.extensions.clickWithAssertion
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
import com.tangem.common.extensions.swipeVertical
|
|
||||||
import com.tangem.common.utils.getWcUri
|
import com.tangem.common.utils.getWcUri
|
||||||
|
import com.tangem.common.utils.setClipboardText
|
||||||
import com.tangem.scenarios.*
|
import com.tangem.scenarios.*
|
||||||
import com.tangem.screens.*
|
import com.tangem.screens.*
|
||||||
|
import com.tangem.wallet.BuildConfig
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import io.qameta.allure.kotlin.AllureId
|
import io.qameta.allure.kotlin.AllureId
|
||||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
|
@ -21,7 +22,7 @@ class WalletConnectTest : BaseTestCase() {
|
||||||
@DisplayName("WC (React App): open session from deeplink on main screen")
|
@DisplayName("WC (React App): open session from deeplink on main screen")
|
||||||
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
||||||
@Test
|
@Test
|
||||||
fun openWalletConnectSessionOnMainScreen() {
|
fun openWalletConnectSessionOnMainScreenTest() {
|
||||||
val balance = TOTAL_BALANCE
|
val balance = TOTAL_BALANCE
|
||||||
val dAppName = "React App"
|
val dAppName = "React App"
|
||||||
val deepLinkUri = getWcUri()
|
val deepLinkUri = getWcUri()
|
||||||
|
|
@ -37,19 +38,28 @@ class WalletConnectTest : BaseTestCase() {
|
||||||
openAppByDeepLink(deepLinkUri)
|
openAppByDeepLink(deepLinkUri)
|
||||||
}
|
}
|
||||||
step("Check 'Wallet Connect' bottom sheet") {
|
step("Check 'Wallet Connect' bottom sheet") {
|
||||||
checkWalletConnectBottomSheet()
|
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||||
|
checkWalletConnectBottomSheet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert 'Connect' button is enabled") {
|
||||||
|
onWalletConnectBottomSheet { connectButton.assertIsEnabled() }
|
||||||
}
|
}
|
||||||
step("Click on 'Connect' button") {
|
step("Click on 'Connect' button") {
|
||||||
|
waitForIdle()
|
||||||
onWalletConnectBottomSheet { connectButton.performClick() }
|
onWalletConnectBottomSheet { connectButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Click 'More' button on TopBar") {
|
step("Assert 'Connect' button is not displayed") {
|
||||||
onTopBar { moreButton.clickWithAssertion() }
|
waitForIdle()
|
||||||
|
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
|
||||||
}
|
}
|
||||||
step("Click on 'Wallet Connect' button") {
|
step("Open 'Wallet Connect' screen") {
|
||||||
onDetailsScreen { walletConnectButton.clickWithAssertion() }
|
openWalletConnectScreen()
|
||||||
}
|
}
|
||||||
step("Check 'Wallet Connect' screen") {
|
step("Check 'Wallet Connect' screen with connections") {
|
||||||
checkWalletConnectScreen()
|
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||||
|
checkWalletConnectScreen(withConnections = true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
step("Click on app icon") {
|
step("Click on app icon") {
|
||||||
onWalletConnectScreen { appIcon.performClick() }
|
onWalletConnectScreen { appIcon.performClick() }
|
||||||
|
|
@ -57,11 +67,11 @@ class WalletConnectTest : BaseTestCase() {
|
||||||
step("Check 'Wallet Connect' details bottom sheet") {
|
step("Check 'Wallet Connect' details bottom sheet") {
|
||||||
checkWalletConnectDetailsBottomSheet(dAppName)
|
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||||
}
|
}
|
||||||
step("Click on 'Disconnect button' is displayed") {
|
step("Click on 'Disconnect' button") {
|
||||||
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Assert connection is not displayed") {
|
step("Check 'Wallet Connect' screen without connections") {
|
||||||
onWalletConnectScreen { appName.assertIsNotDisplayed() }
|
checkWalletConnectScreen(withConnections = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +80,7 @@ class WalletConnectTest : BaseTestCase() {
|
||||||
@DisplayName("WC (React App): open session from deeplink not on main screen")
|
@DisplayName("WC (React App): open session from deeplink not on main screen")
|
||||||
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
||||||
@Test
|
@Test
|
||||||
fun openWalletConnectSessionNotOnMainScreen() {
|
fun openWalletConnectSessionNotOnMainScreenTest() {
|
||||||
val balance = TOTAL_BALANCE
|
val balance = TOTAL_BALANCE
|
||||||
val dAppName = "React App"
|
val dAppName = "React App"
|
||||||
val deepLinkUri = getWcUri()
|
val deepLinkUri = getWcUri()
|
||||||
|
|
@ -82,41 +92,44 @@ class WalletConnectTest : BaseTestCase() {
|
||||||
step("Synchronize addresses") {
|
step("Synchronize addresses") {
|
||||||
synchronizeAddresses(balance)
|
synchronizeAddresses(balance)
|
||||||
}
|
}
|
||||||
step("Click on 'Buy' button") {
|
step("Open 'Wallet Connect' screen") {
|
||||||
onMainScreen { buyButton.clickWithAssertion() }
|
openWalletConnectScreen()
|
||||||
|
checkWalletConnectScreen(false)
|
||||||
}
|
}
|
||||||
step("Create WC session buy deeplink") {
|
step("Create WC session buy deeplink") {
|
||||||
openAppByDeepLink(deepLinkUri)
|
openAppByDeepLink(deepLinkUri)
|
||||||
}
|
}
|
||||||
step("Check 'Wallet Connect' bottom sheet") {
|
step("Check 'Wallet Connect' bottom sheet") {
|
||||||
checkWalletConnectBottomSheet()
|
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||||
|
checkWalletConnectBottomSheet()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
step("Click on 'Connect' button") {
|
step("Click on 'Connect' button") {
|
||||||
|
waitForIdle()
|
||||||
onWalletConnectBottomSheet { connectButton.performClick() }
|
onWalletConnectBottomSheet { connectButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Click 'More' button on TopBar") {
|
step("Assert 'Connect' button is not displayed") {
|
||||||
onTopBar { moreButton.clickWithAssertion() }
|
waitForIdle()
|
||||||
|
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
|
||||||
}
|
}
|
||||||
step("Click on 'Wallet Connect' button") {
|
step("Check 'Wallet Connect' screen with connections") {
|
||||||
onDetailsScreen { walletConnectButton.clickWithAssertion() }
|
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||||
}
|
checkWalletConnectScreen(withConnections = true)
|
||||||
step("Assert 'Wallet Connect' bottom sheet is displayed") {
|
}
|
||||||
onWalletConnectBottomSheet { connectButton.clickWithAssertion() }
|
|
||||||
}
|
|
||||||
step("Check 'Wallet Connect' screen") {
|
|
||||||
checkWalletConnectScreen()
|
|
||||||
}
|
}
|
||||||
step("Click on app icon") {
|
step("Click on app icon") {
|
||||||
onWalletConnectScreen { appIcon.performClick() }
|
onWalletConnectScreen { appIcon.performClick() }
|
||||||
}
|
}
|
||||||
step("Check 'Wallet Connect' details bottom sheet") {
|
step("Check 'Wallet Connect' details bottom sheet") {
|
||||||
checkWalletConnectDetailsBottomSheet(dAppName)
|
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||||
|
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
step("Click on 'Disconnect button' is displayed") {
|
step("Click on 'Disconnect' button") {
|
||||||
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Assert connection is not displayed") {
|
step("Check 'Wallet Connect' screen without connections") {
|
||||||
onWalletConnectScreen { appName.assertIsNotDisplayed() }
|
checkWalletConnectScreen(withConnections = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,9 +138,10 @@ class WalletConnectTest : BaseTestCase() {
|
||||||
@DisplayName("WC (React App): open session from deeplink ")
|
@DisplayName("WC (React App): open session from deeplink ")
|
||||||
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
||||||
@Test
|
@Test
|
||||||
fun openWalletConnectSession() {
|
fun openWalletConnectSessionTest() {
|
||||||
val balance = TOTAL_BALANCE
|
val balance = TOTAL_BALANCE
|
||||||
val dAppName = "React App"
|
val dAppName = "React App"
|
||||||
|
val packageName = BuildConfig.APPLICATION_ID
|
||||||
val deepLinkUri = getWcUri()
|
val deepLinkUri = getWcUri()
|
||||||
|
|
||||||
setupHooks().run {
|
setupHooks().run {
|
||||||
|
|
@ -137,32 +151,28 @@ class WalletConnectTest : BaseTestCase() {
|
||||||
step("Synchronize addresses") {
|
step("Synchronize addresses") {
|
||||||
synchronizeAddresses(balance)
|
synchronizeAddresses(balance)
|
||||||
}
|
}
|
||||||
step("Open recent apps") {
|
step("Kill app") {
|
||||||
device.uiDevice.pressRecentApps()
|
device.apps.kill(packageName)
|
||||||
}
|
|
||||||
step("Stop app by swipe") {
|
|
||||||
swipeVertical(SwipeDirection.UP, startHeightRatio = 0.8f)
|
|
||||||
}
|
}
|
||||||
step("Create WC session buy deeplink") {
|
step("Create WC session buy deeplink") {
|
||||||
openAppByDeepLink(deepLinkUri)
|
openAppByDeepLink(deepLinkUri)
|
||||||
}
|
}
|
||||||
step("Open 'Main Screen'") {
|
|
||||||
openMainScreen()
|
|
||||||
}
|
|
||||||
step("Check 'Wallet Connect' bottom sheet") {
|
step("Check 'Wallet Connect' bottom sheet") {
|
||||||
checkWalletConnectBottomSheet()
|
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||||
|
checkWalletConnectBottomSheet()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
step("Click on 'Connect' button") {
|
step("Click on 'Connect' button") {
|
||||||
onWalletConnectBottomSheet { connectButton.performClick() }
|
onWalletConnectBottomSheet { connectButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Click 'More' button on TopBar") {
|
step("Assert 'Connect' button is not displayed") {
|
||||||
onTopBar { moreButton.clickWithAssertion() }
|
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
|
||||||
}
|
}
|
||||||
step("Click on 'Wallet Connect' button") {
|
step("Open 'Wallet Connect' screen") {
|
||||||
onDetailsScreen { walletConnectButton.clickWithAssertion() }
|
openWalletConnectScreen()
|
||||||
}
|
}
|
||||||
step("Check 'Wallet Connect' screen") {
|
step("Check 'Wallet Connect' screen with connections") {
|
||||||
checkWalletConnectScreen()
|
checkWalletConnectScreen(withConnections = true)
|
||||||
}
|
}
|
||||||
step("Click on app icon") {
|
step("Click on app icon") {
|
||||||
onWalletConnectScreen { appIcon.performClick() }
|
onWalletConnectScreen { appIcon.performClick() }
|
||||||
|
|
@ -170,11 +180,72 @@ class WalletConnectTest : BaseTestCase() {
|
||||||
step("Check 'Wallet Connect' details bottom sheet") {
|
step("Check 'Wallet Connect' details bottom sheet") {
|
||||||
checkWalletConnectDetailsBottomSheet(dAppName)
|
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||||
}
|
}
|
||||||
step("Click on 'Disconnect button' is displayed") {
|
step("Click on 'Disconnect' button") {
|
||||||
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Assert connection is not displayed") {
|
step("Check 'Wallet Connect' screen without connections") {
|
||||||
onWalletConnectScreen { appName.assertIsNotDisplayed() }
|
checkWalletConnectScreen(withConnections = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("887")
|
||||||
|
@DisplayName("WC: open session by 'Paste from clipboard' button")
|
||||||
|
@Ignore("TODO [REDACTED_JIRA] React app deeplink doesn't work")
|
||||||
|
@Test
|
||||||
|
fun openWalletConnectSessionByClipboardLinkTest() {
|
||||||
|
val balance = TOTAL_BALANCE
|
||||||
|
val dAppName = "React App"
|
||||||
|
val context = device.context
|
||||||
|
val deepLinkUri = getWcUri()
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Set URI to clipboard") {
|
||||||
|
setClipboardText(context, deepLinkUri)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses(balance)
|
||||||
|
}
|
||||||
|
step("Open 'Wallet Connect' screen") {
|
||||||
|
openWalletConnectScreen()
|
||||||
|
}
|
||||||
|
step("Click 'New connection' button") {
|
||||||
|
onWalletConnectScreen { newConnectionButton.performClick() }
|
||||||
|
}
|
||||||
|
step("CLick 'Paste from clipboard' button") {
|
||||||
|
onWalletConnectScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Check 'Wallet Connect' bottom sheet") {
|
||||||
|
waitForIdle()
|
||||||
|
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||||
|
checkWalletConnectBottomSheet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Connect' button") {
|
||||||
|
waitForIdle()
|
||||||
|
onWalletConnectBottomSheet { connectButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Connect' button is not displayed") {
|
||||||
|
waitForIdle()
|
||||||
|
onWalletConnectBottomSheet { connectButton.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
step("Check 'Wallet Connect' screen with connections") {
|
||||||
|
checkWalletConnectScreen(withConnections = true)
|
||||||
|
}
|
||||||
|
step("Click on app icon") {
|
||||||
|
onWalletConnectScreen { appIcon.performClick() }
|
||||||
|
}
|
||||||
|
step("Check 'Wallet Connect' details bottom sheet") {
|
||||||
|
checkWalletConnectDetailsBottomSheet(dAppName)
|
||||||
|
}
|
||||||
|
step("Click on 'Disconnect' button") {
|
||||||
|
onWalletConnectDetailsBottomSheet { disconnectButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Check 'Wallet Connect' screen without connections") {
|
||||||
|
checkWalletConnectScreen(withConnections = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,405 @@
|
||||||
|
package com.tangem.tests.actionButtons
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.longClick
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.BITCOIN_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.TOTAL_BALANCE
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.assertClipboardTextEquals
|
||||||
|
import com.tangem.common.utils.clearClipboard
|
||||||
|
import com.tangem.scenarios.openMainScreen
|
||||||
|
import com.tangem.scenarios.synchronizeAddresses
|
||||||
|
import com.tangem.screens.*
|
||||||
|
import com.tangem.screens.onTokenActionsBottomSheet
|
||||||
|
import com.tangem.screens.onBuyTokenDetailsScreen
|
||||||
|
import com.tangem.screens.onDialog
|
||||||
|
import com.tangem.screens.onMainScreen
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class MainScreenActionButtonsTest : BaseTestCase() {
|
||||||
|
|
||||||
|
@AllureId("79")
|
||||||
|
@DisplayName("Action buttons (long tap): validate UI")
|
||||||
|
@Test
|
||||||
|
fun actionButtonsValidateLongTapUiTest() {
|
||||||
|
val tokenTitle = "Ethereum"
|
||||||
|
val balance = TOTAL_BALANCE
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses(balance)
|
||||||
|
}
|
||||||
|
step("Long click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen {
|
||||||
|
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
|
||||||
|
longClick(
|
||||||
|
position = center,
|
||||||
|
durationMillis = 1000L
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert 'Analytics' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { analyticsButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Copy address' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { copyAddressButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Receive' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { receiveButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Send' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { sendButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { swapButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Buy' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { buyButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Sell' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { sellButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Hide token' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { hideTokenButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("84")
|
||||||
|
@DisplayName("Action buttons (long tap): check 'Copy address' button")
|
||||||
|
@Test
|
||||||
|
fun clickOnCopyAddressButtonTest() {
|
||||||
|
val tokenTitle = "Bitcoin"
|
||||||
|
val balance = TOTAL_BALANCE
|
||||||
|
val bitcoinAddress = BITCOIN_ADDRESS
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalBeforeSection = {
|
||||||
|
clearClipboard()
|
||||||
|
},
|
||||||
|
additionalAfterSection = {
|
||||||
|
clearClipboard()
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses(balance)
|
||||||
|
}
|
||||||
|
step("Long click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen {
|
||||||
|
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
|
||||||
|
longClick(
|
||||||
|
position = center,
|
||||||
|
durationMillis = 1000L
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert 'Copy address' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { copyAddressButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Copy address' button") {
|
||||||
|
onTokenActionsBottomSheet { copyAddressButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert snack bar message is displayed") {
|
||||||
|
onMainScreen { snackbarCopiedAddressMessage.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Check clipboard has '$tokenTitle' address '$bitcoinAddress'") {
|
||||||
|
waitForIdle()
|
||||||
|
assertClipboardTextEquals(expected = bitcoinAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("82")
|
||||||
|
@DisplayName("Action buttons (long tap): check 'Buy' button")
|
||||||
|
@Test
|
||||||
|
fun clickOnBuyButtonTest() {
|
||||||
|
val tokenTitle = "Bitcoin"
|
||||||
|
val tokenSymbol = "BTC"
|
||||||
|
val balance = TOTAL_BALANCE
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses(balance)
|
||||||
|
}
|
||||||
|
step("Long click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen {
|
||||||
|
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
|
||||||
|
longClick(
|
||||||
|
position = center,
|
||||||
|
durationMillis = 1000L
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert 'Buy' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { buyButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Buy' button") {
|
||||||
|
onTokenActionsBottomSheet { buyButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Click on 'Confirm' button in 'Dialog'") {
|
||||||
|
waitForIdle()
|
||||||
|
onDialog { confirmButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert top app bar title contains '$tokenTitle'") {
|
||||||
|
onBuyTokenDetailsScreen { topBarTitle.assertTextContains("Buy $tokenTitle") }
|
||||||
|
}
|
||||||
|
step("Assert fiat currency text field is displayed") {
|
||||||
|
onBuyTokenDetailsScreen { fiatAmountTextField.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert fiat currency icon is displayed") {
|
||||||
|
onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert token amount field is displayed") {
|
||||||
|
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenSymbol, substring = true) }
|
||||||
|
}
|
||||||
|
step("Assert 'Continue' button") {
|
||||||
|
onBuyTokenDetailsScreen { continueButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("87")
|
||||||
|
@DisplayName("Action buttons (long tap): check 'Swap' button")
|
||||||
|
@Test
|
||||||
|
fun clickOnSwapButtonTest() {
|
||||||
|
val tokenTitle = "Ethereum"
|
||||||
|
val tokenSymbol = "ETH"
|
||||||
|
val balance = TOTAL_BALANCE
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses(balance)
|
||||||
|
}
|
||||||
|
step("Long click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen {
|
||||||
|
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
|
||||||
|
longClick(
|
||||||
|
position = center,
|
||||||
|
durationMillis = 1000L
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { swapButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Swap' button") {
|
||||||
|
onTokenActionsBottomSheet { swapButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Close 'Stories' screen") {
|
||||||
|
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' screen title is displayed") {
|
||||||
|
onSwapTokenScreen { title.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert token symbol: '$tokenSymbol' is displayed") {
|
||||||
|
onSwapTokenScreen { tokenSymbol(tokenSymbol).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("83")
|
||||||
|
@DisplayName("Action buttons (long tap): check 'Send' button")
|
||||||
|
@Test
|
||||||
|
fun clickOnSendButtonTest() {
|
||||||
|
val tokenTitle = "Ethereum"
|
||||||
|
val tokenSymbol = "ETH"
|
||||||
|
val balance = TOTAL_BALANCE
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses(balance)
|
||||||
|
}
|
||||||
|
step("Long click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen {
|
||||||
|
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
|
||||||
|
longClick(
|
||||||
|
position = center,
|
||||||
|
durationMillis = 1000L
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert 'Send' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { sendButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Send' button") {
|
||||||
|
onTokenActionsBottomSheet { sendButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert amount input text field contains token symbol: '$tokenSymbol'") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.assertTextContains(value = tokenSymbol, substring = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("86")
|
||||||
|
@DisplayName("Action buttons (long tap): check 'Receive' button")
|
||||||
|
@Test
|
||||||
|
fun clickOnReceiveButtonTest() {
|
||||||
|
val tokenTitle = "Bitcoin"
|
||||||
|
val balance = TOTAL_BALANCE
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses(balance)
|
||||||
|
}
|
||||||
|
step("Long click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen {
|
||||||
|
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
|
||||||
|
longClick(
|
||||||
|
position = center,
|
||||||
|
durationMillis = 1000L
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert 'Receive' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { receiveButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Receive' button") {
|
||||||
|
onTokenActionsBottomSheet { receiveButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Token receive warning' bottom sheet is displayed") {
|
||||||
|
onTokenReceiveWarningBottomSheet { bottomSheet.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Got it' button") {
|
||||||
|
onTokenReceiveWarningBottomSheet { gotItButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Click on 'Show QR code' button") {
|
||||||
|
onReceiveAssetsBottomSheet { showQrCodeButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert bottom sheet with QR code title is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { title.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert QR code is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { qrCode.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert address title is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { addressTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert address is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { address.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Copy' button is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { copyButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Share' button is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { shareButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("85")
|
||||||
|
@DisplayName("Action buttons (long tap): check 'Sell' button")
|
||||||
|
@Test
|
||||||
|
fun clickOnSellButtonTest() {
|
||||||
|
val tokenTitle = "Ethereum"
|
||||||
|
val tokenSymbol = "ETH"
|
||||||
|
val balance = TOTAL_BALANCE
|
||||||
|
val url = "sell.moonpay.com"
|
||||||
|
val useWithoutAccount = "Use without an account"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses(balance)
|
||||||
|
}
|
||||||
|
step("Long click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen {
|
||||||
|
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
|
||||||
|
longClick(
|
||||||
|
position = center,
|
||||||
|
durationMillis = 1000L
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert 'Receive' button is displayed") {
|
||||||
|
onTokenActionsBottomSheet { sellButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Receive' button") {
|
||||||
|
onTokenActionsBottomSheet { sellButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert Chrome Browser is opened") {
|
||||||
|
ChromeBrowserPageObject { assertChromeIsOpened() }
|
||||||
|
}
|
||||||
|
if (ChromeBrowserPageObject.isElementWithTextExists(useWithoutAccount)) {
|
||||||
|
step("Click on '$useWithoutAccount' button on Chrome browser") {
|
||||||
|
ChromeBrowserPageObject { clickOnElementWithText(useWithoutAccount) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert url contains: '$url'") {
|
||||||
|
ChromeBrowserPageObject { assertUrlContains(url) }
|
||||||
|
}
|
||||||
|
step("Assert token symbol '$tokenSymbol' is displayed") {
|
||||||
|
ChromeBrowserPageObject { assertElementWithTextExists(tokenSymbol) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("77")
|
||||||
|
@DisplayName("Action buttons (long tap): assert 'Sell' button is not displayed if token doesn't support it")
|
||||||
|
@Test
|
||||||
|
fun assertSellButtonIsNotDisplayedTest() {
|
||||||
|
val tokenTitle = "Bitcoin"
|
||||||
|
val balance = TOTAL_BALANCE
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses(balance)
|
||||||
|
}
|
||||||
|
step("Long click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen {
|
||||||
|
tokenWithTitleAndAddress(tokenTitle).performTouchInput {
|
||||||
|
longClick(
|
||||||
|
position = center,
|
||||||
|
durationMillis = 1000L
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert 'Sell' button is not displayed") {
|
||||||
|
onTokenActionsBottomSheet { sellButton.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -162,7 +162,7 @@ class TotalBalanceUpdateTest : BaseTestCase() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
step("Click 'Hide token' button") {
|
step("Click 'Hide token' button") {
|
||||||
onBottomSheet { hideButton.clickWithAssertion() }
|
onTokenActionsBottomSheet { hideTokenButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Click 'Hide' button in dialog") {
|
step("Click 'Hide' button in dialog") {
|
||||||
onDialog {
|
onDialog {
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,11 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
||||||
secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true)
|
secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clear(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
override suspend fun clearOrderId(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
||||||
|
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearAll(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
||||||
secureStorage.delete(createKey(customerWalletAddress))
|
secureStorage.delete(createKey(customerWalletAddress))
|
||||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,12 @@ internal object AccountDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideRecoverCryptoPortfolioUseCase(
|
fun provideRecoverCryptoPortfolioUseCase(
|
||||||
accountsCRUDRepository: AccountsCRUDRepository,
|
accountsCRUDRepository: AccountsCRUDRepository,
|
||||||
|
mainAccountTokensMigration: MainAccountTokensMigration,
|
||||||
): RecoverCryptoPortfolioUseCase {
|
): RecoverCryptoPortfolioUseCase {
|
||||||
return RecoverCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
|
return RecoverCryptoPortfolioUseCase(
|
||||||
|
crudRepository = accountsCRUDRepository,
|
||||||
|
mainAccountTokensMigration = mainAccountTokensMigration,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||||
import com.tangem.domain.wallets.usecase.*
|
import com.tangem.domain.wallets.usecase.*
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyMarketRepository
|
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase
|
||||||
import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase
|
import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase
|
||||||
|
|
@ -454,21 +454,17 @@ internal object WalletsDomainModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideYieldSupplyApyFlowUseCase(
|
fun provideYieldSupplyApyFlowUseCase(yieldSupplyRepository: YieldSupplyRepository): YieldSupplyApyFlowUseCase {
|
||||||
yieldSupplyMarketRepository: YieldSupplyMarketRepository,
|
|
||||||
): YieldSupplyApyFlowUseCase {
|
|
||||||
return YieldSupplyApyFlowUseCase(
|
return YieldSupplyApyFlowUseCase(
|
||||||
yieldSupplyMarketRepository = yieldSupplyMarketRepository,
|
yieldSupplyRepository = yieldSupplyRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideYieldSupplyApyUpdateUseCase(
|
fun provideYieldSupplyApyUpdateUseCase(yieldSupplyRepository: YieldSupplyRepository): YieldSupplyApyUpdateUseCase {
|
||||||
yieldSupplyMarketRepository: YieldSupplyMarketRepository,
|
|
||||||
): YieldSupplyApyUpdateUseCase {
|
|
||||||
return YieldSupplyApyUpdateUseCase(
|
return YieldSupplyApyUpdateUseCase(
|
||||||
yieldSupplyMarketRepository = yieldSupplyMarketRepository,
|
yieldSupplyRepository = yieldSupplyRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import com.tangem.domain.blockaid.BlockAidGasEstimate
|
||||||
import com.tangem.domain.transaction.FeeRepository
|
import com.tangem.domain.transaction.FeeRepository
|
||||||
import com.tangem.domain.transaction.error.FeeErrorResolver
|
import com.tangem.domain.transaction.error.FeeErrorResolver
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyErrorResolver
|
import com.tangem.domain.yield.supply.YieldSupplyErrorResolver
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyMarketRepository
|
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository
|
import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository
|
||||||
import com.tangem.domain.yield.supply.usecase.*
|
import com.tangem.domain.yield.supply.usecase.*
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -82,30 +82,54 @@ internal object YieldSupplyDomainModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideYieldSupplyGetTokenStatusUseCase(
|
fun provideYieldSupplyGetTokenStatusUseCase(
|
||||||
yieldSupplyMarketRepository: YieldSupplyMarketRepository,
|
yieldSupplyRepository: YieldSupplyRepository,
|
||||||
): YieldSupplyGetTokenStatusUseCase {
|
): YieldSupplyGetTokenStatusUseCase {
|
||||||
return YieldSupplyGetTokenStatusUseCase(
|
return YieldSupplyGetTokenStatusUseCase(
|
||||||
yieldSupplyMarketRepository = yieldSupplyMarketRepository,
|
yieldSupplyRepository = yieldSupplyRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideYieldSupplyGetApyUseCase(
|
fun provideYieldSupplyGetApyUseCase(yieldSupplyRepository: YieldSupplyRepository): YieldSupplyGetApyUseCase {
|
||||||
yieldSupplyMarketRepository: YieldSupplyMarketRepository,
|
|
||||||
): YieldSupplyGetApyUseCase {
|
|
||||||
return YieldSupplyGetApyUseCase(
|
return YieldSupplyGetApyUseCase(
|
||||||
yieldSupplyMarketRepository = yieldSupplyMarketRepository,
|
yieldSupplyRepository = yieldSupplyRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideYieldSupplyGetChartUseCase(
|
fun provideYieldSupplyGetChartUseCase(yieldSupplyRepository: YieldSupplyRepository): YieldSupplyGetChartUseCase {
|
||||||
yieldSupplyMarketRepository: YieldSupplyMarketRepository,
|
|
||||||
): YieldSupplyGetChartUseCase {
|
|
||||||
return YieldSupplyGetChartUseCase(
|
return YieldSupplyGetChartUseCase(
|
||||||
yieldSupplyMarketRepository = yieldSupplyMarketRepository,
|
yieldSupplyRepository = yieldSupplyRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideYieldSupplyIsAvailableUseCase(
|
||||||
|
yieldSupplyRepository: YieldSupplyRepository,
|
||||||
|
): YieldSupplyIsAvailableUseCase {
|
||||||
|
return YieldSupplyIsAvailableUseCase(
|
||||||
|
yieldSupplyRepository = yieldSupplyRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideYieldSupplyActivateUseCase(yieldSupplyRepository: YieldSupplyRepository): YieldSupplyActivateUseCase {
|
||||||
|
return YieldSupplyActivateUseCase(
|
||||||
|
yieldSupplyRepository = yieldSupplyRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideYieldSupplyDeactivateUseCase(
|
||||||
|
yieldSupplyRepository: YieldSupplyRepository,
|
||||||
|
): YieldSupplyDeactivateUseCase {
|
||||||
|
return YieldSupplyDeactivateUseCase(
|
||||||
|
yieldSupplyRepository = yieldSupplyRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -160,4 +160,6 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
|
||||||
Pepecoin, PepecoinTestnet -> null
|
Pepecoin, PepecoinTestnet -> null
|
||||||
Hyperliquid, HyperliquidTestnet -> null
|
Hyperliquid, HyperliquidTestnet -> null
|
||||||
Quai, QuaiTestnet -> null
|
Quai, QuaiTestnet -> null
|
||||||
|
Linea, LineaTestnet -> null
|
||||||
|
ArbitrumNova -> null
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.routing.utils
|
||||||
|
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.core.decompose.context.AppComponentContext
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
|
import com.tangem.domain.models.PortfolioId
|
||||||
import com.tangem.domain.qrscanning.models.SourceType
|
import com.tangem.domain.qrscanning.models.SourceType
|
||||||
import com.tangem.feature.qrscanning.QrScanningComponent
|
import com.tangem.feature.qrscanning.QrScanningComponent
|
||||||
import com.tangem.feature.referral.api.ReferralComponent
|
import com.tangem.feature.referral.api.ReferralComponent
|
||||||
|
|
@ -12,6 +13,7 @@ import com.tangem.features.account.AccountCreateEditComponent
|
||||||
import com.tangem.features.account.AccountDetailsComponent
|
import com.tangem.features.account.AccountDetailsComponent
|
||||||
import com.tangem.features.account.ArchivedAccountListComponent
|
import com.tangem.features.account.ArchivedAccountListComponent
|
||||||
import com.tangem.features.createwalletselection.CreateWalletSelectionComponent
|
import com.tangem.features.createwalletselection.CreateWalletSelectionComponent
|
||||||
|
import com.tangem.features.createwalletstart.CreateWalletStartComponent
|
||||||
import com.tangem.features.details.component.DetailsComponent
|
import com.tangem.features.details.component.DetailsComponent
|
||||||
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
||||||
import com.tangem.features.home.api.HomeComponent
|
import com.tangem.features.home.api.HomeComponent
|
||||||
|
|
@ -19,6 +21,7 @@ import com.tangem.features.hotwallet.*
|
||||||
import com.tangem.features.kyc.KycComponent
|
import com.tangem.features.kyc.KycComponent
|
||||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||||
|
import com.tangem.features.managetokens.component.ManageTokensMode
|
||||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||||
import com.tangem.features.markets.tokenlist.MarketsTokenListComponent
|
import com.tangem.features.markets.tokenlist.MarketsTokenListComponent
|
||||||
|
|
@ -97,6 +100,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
private val usedeskComponentFactory: UsedeskComponent.Factory,
|
private val usedeskComponentFactory: UsedeskComponent.Factory,
|
||||||
private val chooseManagedTokensComponentFactory: ChooseManagedTokensComponent.Factory,
|
private val chooseManagedTokensComponentFactory: ChooseManagedTokensComponent.Factory,
|
||||||
private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory,
|
private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory,
|
||||||
|
private val createWalletStartComponentFactory: CreateWalletStartComponent.Factory,
|
||||||
private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory,
|
private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory,
|
||||||
private val upgradeWalletComponentFactory: UpgradeWalletComponent.Factory,
|
private val upgradeWalletComponentFactory: UpgradeWalletComponent.Factory,
|
||||||
private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory,
|
private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory,
|
||||||
|
|
@ -140,9 +144,15 @@ internal class ChildFactory @Inject constructor(
|
||||||
AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES
|
AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val mode = when (val portfolio = route.portfolioId) {
|
||||||
|
is PortfolioId.Account -> ManageTokensMode.Account(portfolio.accountId)
|
||||||
|
is PortfolioId.Wallet -> ManageTokensMode.Wallet(portfolio.userWalletId)
|
||||||
|
null -> ManageTokensMode.None
|
||||||
|
}
|
||||||
|
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = ManageTokensComponent.Params(route.userWalletId, source),
|
params = ManageTokensComponent.Params(mode, source),
|
||||||
componentFactory = manageTokensComponentFactory,
|
componentFactory = manageTokensComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +210,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = OnrampComponent.Params(
|
params = OnrampComponent.Params(
|
||||||
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param,
|
userWalletId = route.userWalletId,
|
||||||
cryptoCurrency = route.currency,
|
cryptoCurrency = route.currency,
|
||||||
source = route.source,
|
source = route.source,
|
||||||
shouldLaunchSepa = route.shouldLaunchSepa,
|
shouldLaunchSepa = route.shouldLaunchSepa,
|
||||||
|
|
@ -274,7 +284,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = TokenDetailsComponent.Params(
|
params = TokenDetailsComponent.Params(
|
||||||
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param
|
userWalletId = route.userWalletId,
|
||||||
currency = route.currency,
|
currency = route.currency,
|
||||||
),
|
),
|
||||||
componentFactory = tokenDetailsComponentFactory,
|
componentFactory = tokenDetailsComponentFactory,
|
||||||
|
|
@ -284,7 +294,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = StakingComponent.Params(
|
params = StakingComponent.Params(
|
||||||
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param,
|
userWalletId = route.userWalletId,
|
||||||
cryptoCurrencyId = route.cryptoCurrencyId,
|
cryptoCurrencyId = route.cryptoCurrencyId,
|
||||||
yieldId = route.yieldId,
|
yieldId = route.yieldId,
|
||||||
),
|
),
|
||||||
|
|
@ -297,7 +307,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
params = SwapComponent.Params(
|
params = SwapComponent.Params(
|
||||||
currencyFrom = route.currencyFrom,
|
currencyFrom = route.currencyFrom,
|
||||||
currencyTo = route.currencyTo,
|
currencyTo = route.currencyTo,
|
||||||
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param,
|
userWalletId = route.userWalletId,
|
||||||
isInitialReverseOrder = route.isInitialReverseOrder,
|
isInitialReverseOrder = route.isInitialReverseOrder,
|
||||||
screenSource = route.screenSource,
|
screenSource = route.screenSource,
|
||||||
),
|
),
|
||||||
|
|
@ -308,7 +318,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = SendComponent.Params(
|
params = SendComponent.Params(
|
||||||
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param,
|
userWalletId = route.userWalletId,
|
||||||
currency = route.currency,
|
currency = route.currency,
|
||||||
transactionId = route.transactionId,
|
transactionId = route.transactionId,
|
||||||
amount = route.amount,
|
amount = route.amount,
|
||||||
|
|
@ -468,6 +478,19 @@ internal class ChildFactory @Inject constructor(
|
||||||
componentFactory = chooseManagedTokensComponentFactory,
|
componentFactory = chooseManagedTokensComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is AppRoute.CreateWalletStart -> {
|
||||||
|
val mode = when (route.mode) {
|
||||||
|
AppRoute.CreateWalletStart.Mode.ColdWallet -> CreateWalletStartComponent.Mode.ColdWallet
|
||||||
|
AppRoute.CreateWalletStart.Mode.HotWallet -> CreateWalletStartComponent.Mode.HotWallet
|
||||||
|
}
|
||||||
|
createComponentChild(
|
||||||
|
context = context,
|
||||||
|
params = CreateWalletStartComponent.Params(
|
||||||
|
mode = mode,
|
||||||
|
),
|
||||||
|
componentFactory = createWalletStartComponentFactory,
|
||||||
|
)
|
||||||
|
}
|
||||||
is AppRoute.CreateWalletSelection -> {
|
is AppRoute.CreateWalletSelection -> {
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
|
|
|
||||||
|
|
@ -51,50 +51,25 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class CurrencyDetails(
|
data class CurrencyDetails(
|
||||||
val portfolioId: PortfolioId,
|
val userWalletId: UserWalletId,
|
||||||
val currency: CryptoCurrency,
|
val currency: CryptoCurrency,
|
||||||
) : AppRoute(path = "/currency_details/${portfolioId.stringValue}/${currency.id.value}") {
|
) : AppRoute(path = "/currency_details/${userWalletId.stringValue}/${currency.id.value}")
|
||||||
companion object {
|
|
||||||
operator fun invoke(userWalletId: UserWalletId, currency: CryptoCurrency) = CurrencyDetails(
|
|
||||||
portfolioId = PortfolioId(userWalletId),
|
|
||||||
currency = currency,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Send(
|
data class Send(
|
||||||
val portfolioId: PortfolioId,
|
val userWalletId: UserWalletId,
|
||||||
val currency: CryptoCurrency,
|
val currency: CryptoCurrency,
|
||||||
val transactionId: String? = null,
|
val transactionId: String? = null,
|
||||||
val amount: String? = null,
|
val amount: String? = null,
|
||||||
val tag: String? = null,
|
val tag: String? = null,
|
||||||
val destinationAddress: String? = null,
|
val destinationAddress: String? = null,
|
||||||
) : AppRoute(
|
) : AppRoute(
|
||||||
path = "/send/${portfolioId.stringValue}/${currency.id.value}?" +
|
path = "/send/${userWalletId.stringValue}/${currency.id.value}?" +
|
||||||
"&$transactionId" +
|
"&$transactionId" +
|
||||||
"&$amount" +
|
"&$amount" +
|
||||||
"&$tag" +
|
"&$tag" +
|
||||||
"&$destinationAddress",
|
"&$destinationAddress",
|
||||||
) {
|
)
|
||||||
companion object {
|
|
||||||
operator fun invoke(
|
|
||||||
userWalletId: UserWalletId,
|
|
||||||
currency: CryptoCurrency,
|
|
||||||
transactionId: String? = null,
|
|
||||||
amount: String? = null,
|
|
||||||
tag: String? = null,
|
|
||||||
destinationAddress: String? = null,
|
|
||||||
) = Send(
|
|
||||||
portfolioId = PortfolioId(userWalletId),
|
|
||||||
currency = currency,
|
|
||||||
transactionId = transactionId,
|
|
||||||
amount = amount,
|
|
||||||
tag = tag,
|
|
||||||
destinationAddress = destinationAddress,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Details(
|
data class Details(
|
||||||
|
|
@ -147,8 +122,8 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ManageTokens(
|
data class ManageTokens(
|
||||||
val source: Source,
|
val source: Source,
|
||||||
val userWalletId: UserWalletId? = null,
|
val portfolioId: PortfolioId? = null,
|
||||||
) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/$userWalletId") {
|
) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/${portfolioId?.stringValue}") {
|
||||||
|
|
||||||
enum class Source {
|
enum class Source {
|
||||||
STORIES,
|
STORIES,
|
||||||
|
|
@ -199,51 +174,26 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
data class Swap(
|
data class Swap(
|
||||||
val currencyFrom: CryptoCurrency,
|
val currencyFrom: CryptoCurrency,
|
||||||
val currencyTo: CryptoCurrency? = null,
|
val currencyTo: CryptoCurrency? = null,
|
||||||
val portfolioId: PortfolioId,
|
val userWalletId: UserWalletId,
|
||||||
val isInitialReverseOrder: Boolean = false,
|
val isInitialReverseOrder: Boolean = false,
|
||||||
val screenSource: String,
|
val screenSource: String,
|
||||||
) : AppRoute(
|
) : AppRoute(
|
||||||
path = "/swap" +
|
path = "/swap" +
|
||||||
"/${currencyFrom.id.value}" +
|
"/${currencyFrom.id.value}" +
|
||||||
"/${currencyTo?.id?.value}" +
|
"/${currencyTo?.id?.value}" +
|
||||||
"/${portfolioId.stringValue}" +
|
"/${userWalletId.stringValue}" +
|
||||||
"/$isInitialReverseOrder",
|
"/$isInitialReverseOrder",
|
||||||
) {
|
)
|
||||||
companion object {
|
|
||||||
operator fun invoke(
|
|
||||||
userWalletId: UserWalletId,
|
|
||||||
currencyFrom: CryptoCurrency,
|
|
||||||
currencyTo: CryptoCurrency? = null,
|
|
||||||
isInitialReverseOrder: Boolean = false,
|
|
||||||
screenSource: String,
|
|
||||||
) = Swap(
|
|
||||||
portfolioId = PortfolioId(userWalletId),
|
|
||||||
currencyFrom = currencyFrom,
|
|
||||||
currencyTo = currencyTo,
|
|
||||||
isInitialReverseOrder = isInitialReverseOrder,
|
|
||||||
screenSource = screenSource,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object AppCurrencySelector : AppRoute(path = "/app_currency_selector")
|
data object AppCurrencySelector : AppRoute(path = "/app_currency_selector")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Staking(
|
data class Staking(
|
||||||
val portfolioId: PortfolioId,
|
val userWalletId: UserWalletId,
|
||||||
val cryptoCurrencyId: CryptoCurrency.ID,
|
val cryptoCurrencyId: CryptoCurrency.ID,
|
||||||
val yieldId: String,
|
val yieldId: String,
|
||||||
) : AppRoute(path = "/staking/${portfolioId.stringValue}/${cryptoCurrencyId.value}/$yieldId") {
|
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/$yieldId")
|
||||||
companion object {
|
|
||||||
operator fun invoke(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, yieldId: String) =
|
|
||||||
Staking(
|
|
||||||
portfolioId = PortfolioId(userWalletId),
|
|
||||||
cryptoCurrencyId = cryptoCurrencyId,
|
|
||||||
yieldId = yieldId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PushNotification(
|
data class PushNotification(
|
||||||
|
|
@ -287,25 +237,11 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Onramp(
|
data class Onramp(
|
||||||
val source: OnrampSource,
|
val source: OnrampSource,
|
||||||
val portfolioId: PortfolioId,
|
val userWalletId: UserWalletId,
|
||||||
val currency: CryptoCurrency,
|
val currency: CryptoCurrency,
|
||||||
val shouldLaunchSepa: Boolean = false,
|
val shouldLaunchSepa: Boolean = false,
|
||||||
) : AppRoute(path = "/onramp/${portfolioId.stringValue}/${currency.symbol}"), RouteBundleParams {
|
) : AppRoute(path = "/onramp/${userWalletId.stringValue}/${currency.symbol}"), RouteBundleParams {
|
||||||
override fun getBundle(): Bundle = bundle(serializer())
|
override fun getBundle(): Bundle = bundle(serializer())
|
||||||
|
|
||||||
companion object {
|
|
||||||
operator fun invoke(
|
|
||||||
source: OnrampSource,
|
|
||||||
userWalletId: UserWalletId,
|
|
||||||
currency: CryptoCurrency,
|
|
||||||
launchSepa: Boolean = false,
|
|
||||||
) = Onramp(
|
|
||||||
source = source,
|
|
||||||
portfolioId = PortfolioId(userWalletId),
|
|
||||||
currency = currency,
|
|
||||||
shouldLaunchSepa = launchSepa,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -375,6 +311,16 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
@Serializable
|
@Serializable
|
||||||
object CreateWalletSelection : AppRoute(path = "/create_wallet_selection")
|
object CreateWalletSelection : AppRoute(path = "/create_wallet_selection")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CreateWalletStart(
|
||||||
|
val mode: Mode,
|
||||||
|
) : AppRoute(path = "/create_wallet_start") {
|
||||||
|
enum class Mode {
|
||||||
|
ColdWallet,
|
||||||
|
HotWallet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object CreateMobileWallet : AppRoute(path = "/create_mobile_wallet")
|
object CreateMobileWallet : AppRoute(path = "/create_mobile_wallet")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.tangem.common.ui.account
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.core.ui.components.account.AccountIconSize
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays account name with icon
|
||||||
|
*
|
||||||
|
* @param name account name
|
||||||
|
* @param icon portfolio account icon model
|
||||||
|
* @param iconSize portfolio account icon size
|
||||||
|
* @param nameStyle account name style
|
||||||
|
* @param nameColor account name color
|
||||||
|
* @see AccountIcon
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun AccountLabel(
|
||||||
|
name: TextReference,
|
||||||
|
icon: CryptoPortfolioIconUM,
|
||||||
|
iconSize: AccountIconSize,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
nameStyle: TextStyle = TangemTheme.typography.subtitle2,
|
||||||
|
nameColor: Color = TangemTheme.colors.text.tertiary,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
AccountIcon(
|
||||||
|
name = name,
|
||||||
|
icon = icon,
|
||||||
|
size = iconSize,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = name.resolveReference(),
|
||||||
|
style = nameStyle,
|
||||||
|
color = nameColor,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,6 @@ class AccountPortfolioItemUMConverter(
|
||||||
endIcon = endIcon,
|
endIcon = endIcon,
|
||||||
onClick = { onClick(value.accountId) },
|
onClick = { onClick(value.accountId) },
|
||||||
imageState = getImageState(value),
|
imageState = getImageState(value),
|
||||||
label = null,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,21 +13,17 @@ import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
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.amountFieldV2
|
import com.tangem.common.ui.amountScreen.ui.amountFieldV2
|
||||||
import com.tangem.common.ui.amountScreen.ui.buttons
|
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Amount screen with field
|
* Amount screen with field
|
||||||
* @param amountState amount state
|
* @param amountState amount state
|
||||||
* @param isBalanceHidden flag hidden balances
|
|
||||||
* @param clickIntents amount screen clicks
|
* @param clickIntents amount screen clicks
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AmountScreenContent(
|
fun AmountScreenContent(
|
||||||
amountState: AmountState,
|
amountState: AmountState,
|
||||||
isBalanceHidden: Boolean,
|
|
||||||
clickIntents: AmountScreenClickIntents,
|
clickIntents: AmountScreenClickIntents,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
extraContent: (@Composable () -> Unit)? = null,
|
extraContent: (@Composable () -> Unit)? = null,
|
||||||
|
|
@ -38,32 +34,17 @@ fun AmountScreenContent(
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
if (amountState.isRedesignEnabled) {
|
amountFieldV2(
|
||||||
amountFieldV2(
|
amountState = amountState,
|
||||||
amountState = amountState,
|
onValueChange = clickIntents::onAmountValueChange,
|
||||||
onValueChange = clickIntents::onAmountValueChange,
|
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
||||||
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
onCurrencyChange = clickIntents::onCurrencyChangeClick,
|
||||||
onCurrencyChange = clickIntents::onCurrencyChangeClick,
|
onMaxAmountClick = clickIntents::onMaxValueClick,
|
||||||
onMaxAmountClick = clickIntents::onMaxValueClick,
|
)
|
||||||
)
|
if (extraContent != null) {
|
||||||
if (extraContent != null) {
|
item("EXTRA_CONTENT_KEY") {
|
||||||
item("EXTRA_CONTENT_KEY") {
|
extraContent()
|
||||||
extraContent()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (amountState is AmountState.Data) {
|
|
||||||
amountField(
|
|
||||||
amountState = amountState,
|
|
||||||
isBalanceHidden = isBalanceHidden,
|
|
||||||
onValueChange = clickIntents::onAmountValueChange,
|
|
||||||
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
|
||||||
)
|
|
||||||
buttons(
|
|
||||||
segmentedButtonConfig = amountState.segmentedButtonConfig,
|
|
||||||
clickIntents = clickIntents,
|
|
||||||
isSegmentedButtonsEnabled = amountState.isSegmentedButtonsEnabled,
|
|
||||||
selectedButton = amountState.selectedButton,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +59,6 @@ private fun SendAmountContentPreview(
|
||||||
TangemThemePreview {
|
TangemThemePreview {
|
||||||
AmountScreenContent(
|
AmountScreenContent(
|
||||||
amountState = amountState,
|
amountState = amountState,
|
||||||
isBalanceHidden = false,
|
|
||||||
clickIntents = AmountScreenClickIntentsStub,
|
clickIntents = AmountScreenClickIntentsStub,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ class AmountCurrencyTransformer(
|
||||||
keyboardType = KeyboardType.Number,
|
keyboardType = KeyboardType.Number,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
selectedButton = prevState.segmentedButtonConfig.indexOfFirst { it.isFiat == value },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,10 +70,9 @@ class AmountReduceByTransformer(
|
||||||
error = when {
|
error = when {
|
||||||
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
|
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
|
||||||
isLessThanMinimumIfProvided -> {
|
isLessThanMinimumIfProvided -> {
|
||||||
val minimumAmount = minimumTransactionAmount
|
val minimumAmount = minimumTransactionAmount.amount.format {
|
||||||
?.amount
|
crypto(cryptoCurrencyStatus.currency)
|
||||||
?.format { crypto(cryptoCurrencyStatus.currency) }
|
}
|
||||||
.orEmpty()
|
|
||||||
resourceReference(
|
resourceReference(
|
||||||
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
|
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
|
||||||
wrappedList(minimumAmount, minimumAmount),
|
wrappedList(minimumAmount, minimumAmount),
|
||||||
|
|
|
||||||
|
|
@ -64,10 +64,9 @@ class AmountReduceToTransformer(
|
||||||
error = when {
|
error = when {
|
||||||
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
|
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
|
||||||
isLessThanMinimumIfProvided -> {
|
isLessThanMinimumIfProvided -> {
|
||||||
val minimumAmount = minimumTransactionAmount
|
val minimumAmount = minimumTransactionAmount.amount.format {
|
||||||
?.amount
|
crypto(cryptoCurrencyStatus.currency)
|
||||||
?.format { crypto(cryptoCurrencyStatus.currency) }
|
}
|
||||||
.orEmpty()
|
|
||||||
resourceReference(
|
resourceReference(
|
||||||
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
|
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
|
||||||
wrappedList(minimumAmount, minimumAmount),
|
wrappedList(minimumAmount, minimumAmount),
|
||||||
|
|
|
||||||
|
|
@ -1,104 +1,35 @@
|
||||||
package com.tangem.common.ui.amountScreen.converters
|
package com.tangem.common.ui.amountScreen.converters
|
||||||
|
|
||||||
import com.tangem.common.ui.R
|
|
||||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldConverter
|
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldConverter
|
||||||
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldConverterV2
|
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountParameters
|
import com.tangem.common.ui.amountScreen.models.AmountParameters
|
||||||
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.common.ui.amountScreen.models.EnterAmountBoundary
|
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||||
import com.tangem.core.ui.extensions.*
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.combinedReference
|
||||||
|
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.utils.Provider
|
|
||||||
import com.tangem.utils.StringsSigns.DOT
|
import com.tangem.utils.StringsSigns.DOT
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
import com.tangem.utils.isNullOrZero
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts initial [String] to [AmountState]
|
|
||||||
*
|
|
||||||
* @property clickIntents amount screen clicks
|
|
||||||
* @property appCurrencyProvider selected app currency provider
|
|
||||||
* @property maxEnterAmount max enter amount data
|
|
||||||
* @property cryptoCurrencyStatusProvider current cryptocurrency status provider
|
|
||||||
* @property iconStateConverter currency icon converter
|
|
||||||
*/
|
|
||||||
@Deprecated("Use AmountStateConverterV2")
|
|
||||||
class AmountStateConverter(
|
|
||||||
private val clickIntents: AmountScreenClickIntents,
|
|
||||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
|
||||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
|
||||||
private val maxEnterAmount: EnterAmountBoundary,
|
|
||||||
private val iconStateConverter: CryptoCurrencyToIconStateConverter,
|
|
||||||
) : Converter<AmountParameters, AmountState> {
|
|
||||||
|
|
||||||
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
|
|
||||||
AmountFieldConverter(
|
|
||||||
clickIntents = clickIntents,
|
|
||||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
|
||||||
appCurrencyProvider = appCurrencyProvider,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun convert(value: AmountParameters): AmountState {
|
|
||||||
val appCurrency = appCurrencyProvider()
|
|
||||||
val status = cryptoCurrencyStatusProvider()
|
|
||||||
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
|
||||||
val crypto = maxEnterAmount.amount.format { crypto(status.currency) }
|
|
||||||
val hasNoFeeRate = status.value.fiatRate.isNullOrZero()
|
|
||||||
|
|
||||||
return AmountState.Data(
|
|
||||||
title = value.title,
|
|
||||||
availableBalance = resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat)),
|
|
||||||
availableBalanceCrypto = stringReference(crypto),
|
|
||||||
availableBalanceFiat = stringReference(fiat),
|
|
||||||
tokenName = stringReference(status.currency.name),
|
|
||||||
tokenIconState = iconStateConverter.convert(status),
|
|
||||||
amountTextField = amountFieldConverter.convert(value.value),
|
|
||||||
isPrimaryButtonEnabled = false,
|
|
||||||
appCurrency = appCurrency,
|
|
||||||
segmentedButtonConfig = persistentListOf(
|
|
||||||
AmountSegmentedButtonsConfig(
|
|
||||||
title = stringReference(status.currency.symbol),
|
|
||||||
iconState = iconStateConverter.convertCustom(
|
|
||||||
value = status,
|
|
||||||
forceGrayscale = hasNoFeeRate,
|
|
||||||
showCustomTokenBadge = false,
|
|
||||||
),
|
|
||||||
isFiat = false,
|
|
||||||
),
|
|
||||||
AmountSegmentedButtonsConfig(
|
|
||||||
title = stringReference(appCurrency.code),
|
|
||||||
iconUrl = appCurrency.iconSmallUrl,
|
|
||||||
isFiat = true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
isSegmentedButtonsEnabled = !hasNoFeeRate,
|
|
||||||
selectedButton = 0,
|
|
||||||
isRedesignEnabled = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts initial [String] to [AmountState]
|
* Converts initial [String] to [AmountState]
|
||||||
*
|
*
|
||||||
* @property clickIntents amount screen clicks
|
* @property clickIntents amount screen clicks
|
||||||
* @property appCurrency selected app currency
|
* @property appCurrency selected app currency
|
||||||
* @property maxEnterAmount max enter amount data
|
|
||||||
* @property cryptoCurrencyStatus current cryptocurrency status
|
* @property cryptoCurrencyStatus current cryptocurrency status
|
||||||
|
* @property maxEnterAmount max enter amount data
|
||||||
* @property iconStateConverter currency icon converter
|
* @property iconStateConverter currency icon converter
|
||||||
* @property isBalanceHidden is balance hidden status
|
* @property isBalanceHidden is balance hidden status
|
||||||
*/
|
*/
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
class AmountStateConverterV2(
|
class AmountStateConverter(
|
||||||
private val clickIntents: AmountScreenClickIntents,
|
private val clickIntents: AmountScreenClickIntents,
|
||||||
private val appCurrency: AppCurrency,
|
private val appCurrency: AppCurrency,
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
|
|
@ -108,7 +39,7 @@ class AmountStateConverterV2(
|
||||||
) : Converter<AmountParameters, AmountState> {
|
) : Converter<AmountParameters, AmountState> {
|
||||||
|
|
||||||
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
|
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
AmountFieldConverterV2(
|
AmountFieldConverter(
|
||||||
clickIntents = clickIntents,
|
clickIntents = clickIntents,
|
||||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
|
|
@ -118,19 +49,13 @@ class AmountStateConverterV2(
|
||||||
override fun convert(value: AmountParameters): AmountState {
|
override fun convert(value: AmountParameters): AmountState {
|
||||||
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||||
val crypto = maxEnterAmount.amount.format { crypto(cryptoCurrencyStatus.currency) }
|
val crypto = maxEnterAmount.amount.format { crypto(cryptoCurrencyStatus.currency) }
|
||||||
val noFeeRate = cryptoCurrencyStatus.value.fiatRate.isNullOrZero()
|
|
||||||
|
|
||||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
|
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
|
||||||
return AmountState.Empty(isRedesignEnabled = true)
|
return AmountState.Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
return AmountState.Data(
|
return AmountState.Data(
|
||||||
title = value.title,
|
title = value.title,
|
||||||
availableBalance = combinedReference(
|
|
||||||
stringReference(crypto),
|
|
||||||
stringReference(" $DOT "),
|
|
||||||
stringReference(fiat),
|
|
||||||
).orMaskWithStars(isBalanceHidden),
|
|
||||||
availableBalanceCrypto = stringReference(crypto).orMaskWithStars(isBalanceHidden),
|
availableBalanceCrypto = stringReference(crypto).orMaskWithStars(isBalanceHidden),
|
||||||
availableBalanceFiat = if (isBalanceHidden) {
|
availableBalanceFiat = if (isBalanceHidden) {
|
||||||
TextReference.EMPTY
|
TextReference.EMPTY
|
||||||
|
|
@ -145,25 +70,6 @@ class AmountStateConverterV2(
|
||||||
amountTextField = amountFieldConverter.convert(value.value),
|
amountTextField = amountFieldConverter.convert(value.value),
|
||||||
isPrimaryButtonEnabled = false,
|
isPrimaryButtonEnabled = false,
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
segmentedButtonConfig = persistentListOf(
|
|
||||||
AmountSegmentedButtonsConfig(
|
|
||||||
title = stringReference(cryptoCurrencyStatus.currency.symbol),
|
|
||||||
iconState = iconStateConverter.convertCustom(
|
|
||||||
value = cryptoCurrencyStatus,
|
|
||||||
forceGrayscale = noFeeRate,
|
|
||||||
showCustomTokenBadge = false,
|
|
||||||
),
|
|
||||||
isFiat = false,
|
|
||||||
),
|
|
||||||
AmountSegmentedButtonsConfig(
|
|
||||||
title = stringReference(appCurrency.code),
|
|
||||||
iconUrl = appCurrency.iconSmallUrl,
|
|
||||||
isFiat = true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
isSegmentedButtonsEnabled = !noFeeRate,
|
|
||||||
selectedButton = 0,
|
|
||||||
isRedesignEnabled = true,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,10 @@ package com.tangem.common.ui.amountScreen.converters.field
|
||||||
|
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||||
import com.tangem.core.ui.extensions.*
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.combinedReference
|
||||||
|
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
|
|
@ -32,14 +35,7 @@ class AmountBoundaryUpdateTransformer(
|
||||||
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||||
val crypto = maxEnterAmount.amount.format { crypto(cryptoCurrencyStatus.currency) }
|
val crypto = maxEnterAmount.amount.format { crypto(cryptoCurrencyStatus.currency) }
|
||||||
|
|
||||||
val availableBalance = combinedReference(
|
|
||||||
stringReference(crypto),
|
|
||||||
stringReference(" $DOT "),
|
|
||||||
stringReference(fiat),
|
|
||||||
)
|
|
||||||
|
|
||||||
return prevState.copy(
|
return prevState.copy(
|
||||||
availableBalance = availableBalance.orMaskWithStars(isBalanceHidden),
|
|
||||||
availableBalanceCrypto = stringReference(crypto).orMaskWithStars(isBalanceHidden),
|
availableBalanceCrypto = stringReference(crypto).orMaskWithStars(isBalanceHidden),
|
||||||
availableBalanceFiat = if (isBalanceHidden) {
|
availableBalanceFiat = if (isBalanceHidden) {
|
||||||
TextReference.EMPTY
|
TextReference.EMPTY
|
||||||
|
|
|
||||||
|
|
@ -13,75 +13,10 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
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 com.tangem.domain.tokens.model.convertToAmount
|
import com.tangem.domain.tokens.model.convertToAmount
|
||||||
import com.tangem.utils.Provider
|
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
import com.tangem.utils.isNullOrZero
|
import com.tangem.utils.isNullOrZero
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts initial [String] to [AmountFieldModel]
|
|
||||||
*
|
|
||||||
* @property clickIntents amount screen clicks
|
|
||||||
* @property appCurrencyProvider selected app currency provider
|
|
||||||
* @property cryptoCurrencyStatusProvider current cryptocurrency status provider
|
|
||||||
*/
|
|
||||||
@Deprecated("Use AmountFieldConverterV2")
|
|
||||||
class AmountFieldConverter(
|
|
||||||
private val clickIntents: AmountScreenClickIntents,
|
|
||||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
|
||||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
|
||||||
) : Converter<String, AmountFieldModel> {
|
|
||||||
|
|
||||||
override fun convert(value: String): AmountFieldModel {
|
|
||||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
|
||||||
val cryptoDecimal = value.toBigDecimalOrNull() ?: BigDecimal.ZERO
|
|
||||||
val cryptoAmount = cryptoDecimal.convertToAmount(cryptoCurrencyStatus.currency)
|
|
||||||
val fiatRate = cryptoCurrencyStatus.value.fiatRate
|
|
||||||
val (fiatValue, fiatDecimal) = when {
|
|
||||||
fiatRate.isNullOrZero() -> "" to null
|
|
||||||
value.isEmpty() -> "" to BigDecimal.ZERO
|
|
||||||
else -> {
|
|
||||||
val fiatDecimal = fiatRate?.multiply(cryptoDecimal)
|
|
||||||
val fiatValue = fiatDecimal?.parseBigDecimal(FIAT_DECIMALS).orEmpty()
|
|
||||||
fiatValue to fiatDecimal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val isDoneActionEnabled = !cryptoDecimal.isNullOrZero()
|
|
||||||
return AmountFieldModel(
|
|
||||||
value = value,
|
|
||||||
fiatValue = fiatValue,
|
|
||||||
onValueChange = clickIntents::onAmountValueChange,
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None,
|
|
||||||
keyboardType = KeyboardType.Number,
|
|
||||||
),
|
|
||||||
keyboardActions = KeyboardActions(
|
|
||||||
onDone = { clickIntents.onAmountNext() },
|
|
||||||
),
|
|
||||||
isFiatValue = false,
|
|
||||||
cryptoAmount = cryptoAmount,
|
|
||||||
fiatAmount = getAppCurrencyAmount(fiatDecimal, appCurrencyProvider()),
|
|
||||||
isError = false,
|
|
||||||
isWarning = false,
|
|
||||||
error = TextReference.EMPTY,
|
|
||||||
isFiatUnavailable = fiatRate == null,
|
|
||||||
isValuePasted = false,
|
|
||||||
onValuePastedTriggerDismiss = clickIntents::onAmountPasteTriggerDismiss,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAppCurrencyAmount(fiatValue: BigDecimal?, appCurrency: AppCurrency) = Amount(
|
|
||||||
currencySymbol = appCurrency.symbol,
|
|
||||||
value = fiatValue,
|
|
||||||
decimals = FIAT_DECIMALS,
|
|
||||||
type = AmountType.FiatType(appCurrency.code),
|
|
||||||
)
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
private const val FIAT_DECIMALS = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts initial [String] to [AmountFieldModel]
|
* Converts initial [String] to [AmountFieldModel]
|
||||||
*
|
*
|
||||||
|
|
@ -89,7 +24,7 @@ class AmountFieldConverter(
|
||||||
* @property appCurrency selected app currency
|
* @property appCurrency selected app currency
|
||||||
* @property cryptoCurrencyStatus current cryptocurrency status
|
* @property cryptoCurrencyStatus current cryptocurrency status
|
||||||
*/
|
*/
|
||||||
class AmountFieldConverterV2(
|
class AmountFieldConverter(
|
||||||
private val clickIntents: AmountScreenClickIntents,
|
private val clickIntents: AmountScreenClickIntents,
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
private val appCurrency: AppCurrency,
|
private val appCurrency: AppCurrency,
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class AmountFieldSetMaxAmountTransformer(
|
||||||
isError = isLessThanMinimumIfProvided,
|
isError = isLessThanMinimumIfProvided,
|
||||||
error = when {
|
error = when {
|
||||||
isLessThanMinimumIfProvided -> {
|
isLessThanMinimumIfProvided -> {
|
||||||
val minimumAmount = minAmount?.amount.format { crypto(cryptoCurrencyStatus.currency) }
|
val minimumAmount = minAmount.amount.format { crypto(cryptoCurrencyStatus.currency) }
|
||||||
resourceReference(
|
resourceReference(
|
||||||
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
|
R.string.transfer_notification_invalid_minimum_transaction_amount_text,
|
||||||
wrappedList(minimumAmount, minimumAmount),
|
wrappedList(minimumAmount, minimumAmount),
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package com.tangem.common.ui.amountScreen.models
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Segmented buttons config
|
|
||||||
*
|
|
||||||
* @param title button title
|
|
||||||
* @param iconState currency icon state
|
|
||||||
* @param iconUrl currency icon url
|
|
||||||
* @param isFiat is fiat currency
|
|
||||||
*/
|
|
||||||
@Immutable
|
|
||||||
data class AmountSegmentedButtonsConfig(
|
|
||||||
val title: TextReference,
|
|
||||||
val iconState: CurrencyIconState? = null,
|
|
||||||
val iconUrl: String? = null,
|
|
||||||
val isFiat: Boolean,
|
|
||||||
)
|
|
||||||
|
|
@ -4,7 +4,6 @@ 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 com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import kotlinx.collections.immutable.PersistentList
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
/** Model for amount state */
|
/** Model for amount state */
|
||||||
|
|
@ -12,18 +11,13 @@ import java.math.BigDecimal
|
||||||
sealed class AmountState {
|
sealed class AmountState {
|
||||||
|
|
||||||
abstract val isPrimaryButtonEnabled: Boolean
|
abstract val isPrimaryButtonEnabled: Boolean
|
||||||
abstract val isRedesignEnabled: Boolean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param isPrimaryButtonEnabled indicates if next state button enabled
|
* @param isPrimaryButtonEnabled indicates if next state button enabled
|
||||||
* @param title title
|
* @param title title
|
||||||
* @param availableBalance user crypto currency balance with fiat balance
|
|
||||||
* @param availableBalanceCrypto user crypto currency balance in crypto
|
* @param availableBalanceCrypto user crypto currency balance in crypto
|
||||||
* @param availableBalanceFiat user crypto currency balance in fiat
|
* @param availableBalanceFiat user crypto currency balance in fiat
|
||||||
* @param tokenIconState crypto currency icon state
|
* @param tokenIconState crypto currency icon state
|
||||||
* @param segmentedButtonConfig currency switcher config
|
|
||||||
* @param selectedButton selected currency index
|
|
||||||
* @param isSegmentedButtonsEnabled indicates if currency switches is enabled
|
|
||||||
* @param amountTextField amount field state
|
* @param amountTextField amount field state
|
||||||
* @param appCurrency app currency
|
* @param appCurrency app currency
|
||||||
* @param isEditingDisabled indicated whether amount is editable
|
* @param isEditingDisabled indicated whether amount is editable
|
||||||
|
|
@ -32,17 +26,11 @@ sealed class AmountState {
|
||||||
*/
|
*/
|
||||||
data class Data(
|
data class Data(
|
||||||
override val isPrimaryButtonEnabled: Boolean,
|
override val isPrimaryButtonEnabled: Boolean,
|
||||||
override val isRedesignEnabled: Boolean,
|
|
||||||
val title: TextReference,
|
val title: TextReference,
|
||||||
@Deprecated("Remove with SEND_REDESIGNED toggle")
|
|
||||||
val availableBalance: TextReference,
|
|
||||||
val availableBalanceCrypto: TextReference,
|
val availableBalanceCrypto: TextReference,
|
||||||
val availableBalanceFiat: TextReference,
|
val availableBalanceFiat: TextReference,
|
||||||
val tokenName: TextReference,
|
val tokenName: TextReference,
|
||||||
val tokenIconState: CurrencyIconState,
|
val tokenIconState: CurrencyIconState,
|
||||||
val segmentedButtonConfig: PersistentList<AmountSegmentedButtonsConfig>,
|
|
||||||
val selectedButton: Int,
|
|
||||||
val isSegmentedButtonsEnabled: Boolean,
|
|
||||||
val amountTextField: AmountFieldModel,
|
val amountTextField: AmountFieldModel,
|
||||||
val appCurrency: AppCurrency,
|
val appCurrency: AppCurrency,
|
||||||
val isEditingDisabled: Boolean = false,
|
val isEditingDisabled: Boolean = false,
|
||||||
|
|
@ -50,8 +38,7 @@ sealed class AmountState {
|
||||||
val isIgnoreReduce: Boolean = false,
|
val isIgnoreReduce: Boolean = false,
|
||||||
) : AmountState()
|
) : AmountState()
|
||||||
|
|
||||||
data class Empty(
|
data object Empty : AmountState() {
|
||||||
override val isPrimaryButtonEnabled: Boolean = false,
|
override val isPrimaryButtonEnabled: Boolean = false
|
||||||
override val isRedesignEnabled: Boolean,
|
}
|
||||||
) : AmountState()
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ 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.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.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
|
||||||
|
|
@ -12,31 +11,18 @@ 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 com.tangem.utils.StringsSigns
|
import com.tangem.utils.StringsSigns
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
object AmountStatePreviewData {
|
object AmountStatePreviewData {
|
||||||
|
|
||||||
val emptyState = AmountState.Empty(isRedesignEnabled = true)
|
val emptyState = AmountState.Empty
|
||||||
|
|
||||||
val amountState = AmountState.Data(
|
val amountState = AmountState.Data(
|
||||||
isPrimaryButtonEnabled = false,
|
isPrimaryButtonEnabled = false,
|
||||||
title = stringReference("Family Wallet"),
|
title = stringReference("Family Wallet"),
|
||||||
availableBalance = stringReference("2 130,81231238 USDT • 2 129,12 \$)"),
|
|
||||||
availableBalanceCrypto = stringReference("2 130,81231238 USDT"),
|
availableBalanceCrypto = stringReference("2 130,81231238 USDT"),
|
||||||
availableBalanceFiat = stringReference("1 232 129,12 \$"),
|
availableBalanceFiat = stringReference("1 232 129,12 \$"),
|
||||||
tokenIconState = CurrencyIconState.Loading,
|
tokenIconState = CurrencyIconState.Loading,
|
||||||
segmentedButtonConfig = persistentListOf(
|
|
||||||
AmountSegmentedButtonsConfig(
|
|
||||||
title = stringReference("USDT"),
|
|
||||||
iconState = CurrencyIconState.Locked,
|
|
||||||
isFiat = false,
|
|
||||||
),
|
|
||||||
AmountSegmentedButtonsConfig(
|
|
||||||
title = stringReference("USD"),
|
|
||||||
isFiat = true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
appCurrency = AppCurrency.Default,
|
appCurrency = AppCurrency.Default,
|
||||||
tokenName = stringReference("Tether"),
|
tokenName = stringReference("Tether"),
|
||||||
amountTextField = AmountFieldModel(
|
amountTextField = AmountFieldModel(
|
||||||
|
|
@ -65,12 +51,9 @@ object AmountStatePreviewData {
|
||||||
isValuePasted = false,
|
isValuePasted = false,
|
||||||
onValuePastedTriggerDismiss = {},
|
onValuePastedTriggerDismiss = {},
|
||||||
),
|
),
|
||||||
isSegmentedButtonsEnabled = true,
|
|
||||||
selectedButton = 0,
|
|
||||||
isRedesignEnabled = false,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val amountWithValueState = amountState.copy(
|
private val amountWithValueState = amountState.copy(
|
||||||
amountTextField = amountState.amountTextField.copy(
|
amountTextField = amountState.amountTextField.copy(
|
||||||
value = "100.00",
|
value = "100.00",
|
||||||
cryptoAmount = amountState.amountTextField.cryptoAmount.copy(
|
cryptoAmount = amountState.amountTextField.cryptoAmount.copy(
|
||||||
|
|
@ -84,16 +67,10 @@ object AmountStatePreviewData {
|
||||||
)
|
)
|
||||||
|
|
||||||
val amountStateV2 = amountState.copy(
|
val amountStateV2 = amountState.copy(
|
||||||
isRedesignEnabled = true,
|
|
||||||
availableBalance = stringReference("2 130,81231238 USDT • 2 129,12 \$)"),
|
|
||||||
availableBalanceCrypto = stringReference("2 130,81231238 USDT"),
|
availableBalanceCrypto = stringReference("2 130,81231238 USDT"),
|
||||||
availableBalanceFiat = stringReference(" ${StringsSigns.DOT} 1 232 129,12 $"),
|
availableBalanceFiat = stringReference(" ${StringsSigns.DOT} 1 232 129,12 $"),
|
||||||
)
|
)
|
||||||
|
|
||||||
val amountWithValueFiatState = amountWithValueState.copy(
|
|
||||||
amountTextField = amountWithValueState.amountTextField.copy(isFiatValue = false),
|
|
||||||
)
|
|
||||||
|
|
||||||
val amountStateV2WithoutRates = amountState.copy(
|
val amountStateV2WithoutRates = amountState.copy(
|
||||||
amountTextField = amountState.amountTextField.copy(
|
amountTextField = amountState.amountTextField.copy(
|
||||||
fiatAmount = amountState.amountTextField.fiatAmount.copy(
|
fiatAmount = amountState.amountTextField.fiatAmount.copy(
|
||||||
|
|
|
||||||
|
|
@ -11,21 +11,22 @@ import androidx.compose.runtime.Composable
|
||||||
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.platform.testTag
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||||
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
|
||||||
import com.tangem.core.ui.components.ResizableText
|
import com.tangem.core.ui.components.ResizableText
|
||||||
|
import com.tangem.core.ui.components.SpacerH
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.core.ui.test.BaseAmountBlockTestTags
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDisabled: Boolean, onClick: () -> Unit) {
|
fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDisabled: Boolean, onClick: () -> Unit) {
|
||||||
|
|
@ -59,7 +60,16 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
|
||||||
.clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick)
|
.clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick)
|
||||||
.padding(TangemTheme.dimens.spacing16),
|
.padding(TangemTheme.dimens.spacing16),
|
||||||
) {
|
) {
|
||||||
CurrencyIcon(state = amountState.tokenIconState)
|
Text(
|
||||||
|
text = amountState.title.resolveReference(),
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
)
|
||||||
|
SpacerH(20.dp)
|
||||||
|
CurrencyIcon(
|
||||||
|
state = amountState.tokenIconState,
|
||||||
|
iconSize = 40.dp,
|
||||||
|
)
|
||||||
ResizableText(
|
ResizableText(
|
||||||
text = firstAmount,
|
text = firstAmount,
|
||||||
style = TangemTheme.typography.h2,
|
style = TangemTheme.typography.h2,
|
||||||
|
|
@ -68,8 +78,7 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = TangemTheme.dimens.spacing24)
|
.padding(top = TangemTheme.dimens.spacing24),
|
||||||
.testTag(BaseAmountBlockTestTags.PRIMARY_AMOUNT),
|
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = secondAmount,
|
text = secondAmount,
|
||||||
|
|
@ -78,8 +87,7 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = TangemTheme.dimens.spacing8)
|
.padding(top = TangemTheme.dimens.spacing8),
|
||||||
.testTag(BaseAmountBlockTestTags.SECONDARY_AMOUNT),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
|
||||||
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.platform.testTag
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
|
@ -28,6 +29,7 @@ import com.tangem.core.ui.format.bigdecimal.format
|
||||||
import com.tangem.core.ui.format.bigdecimal.uncapped
|
import com.tangem.core.ui.format.bigdecimal.uncapped
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.core.ui.test.BaseAmountBlockTestTags
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AmountBlockV2(
|
fun AmountBlockV2(
|
||||||
|
|
@ -133,6 +135,7 @@ private fun AmountBlockV2(
|
||||||
style = TangemTheme.typography.h2,
|
style = TangemTheme.typography.h2,
|
||||||
color = TangemTheme.colors.text.primary1,
|
color = TangemTheme.colors.text.primary1,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
modifier = Modifier.testTag(BaseAmountBlockTestTags.PRIMARY_AMOUNT),
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
|
@ -142,6 +145,7 @@ private fun AmountBlockV2(
|
||||||
style = TangemTheme.typography.body2,
|
style = TangemTheme.typography.body2,
|
||||||
color = TangemTheme.colors.text.tertiary,
|
color = TangemTheme.colors.text.tertiary,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
modifier = Modifier.testTag(BaseAmountBlockTestTags.SECONDARY_AMOUNT),
|
||||||
)
|
)
|
||||||
extraContent()
|
extraContent()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
package com.tangem.common.ui.amountScreen.ui
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
|
||||||
import androidx.compose.ui.platform.testTag
|
|
||||||
import com.tangem.common.ui.R
|
|
||||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountSegmentedButtonsConfig
|
|
||||||
import com.tangem.core.ui.components.SpacerWMax
|
|
||||||
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
|
|
||||||
import com.tangem.core.ui.components.currency.fiaticon.FiatIcon
|
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
|
||||||
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.test.SendScreenTestTags
|
|
||||||
import kotlinx.collections.immutable.PersistentList
|
|
||||||
|
|
||||||
private const val AMOUNT_BUTTONS_KEY = "amountButtonsKey"
|
|
||||||
|
|
||||||
internal fun LazyListScope.buttons(
|
|
||||||
segmentedButtonConfig: PersistentList<AmountSegmentedButtonsConfig>,
|
|
||||||
clickIntents: AmountScreenClickIntents,
|
|
||||||
isSegmentedButtonsEnabled: Boolean,
|
|
||||||
selectedButton: Int,
|
|
||||||
) {
|
|
||||||
item(
|
|
||||||
key = AMOUNT_BUTTONS_KEY,
|
|
||||||
) {
|
|
||||||
val hapticFeedback = LocalHapticFeedback.current
|
|
||||||
Row {
|
|
||||||
if (segmentedButtonConfig.isNotEmpty()) {
|
|
||||||
SegmentedButtons(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.height(TangemTheme.dimens.size40),
|
|
||||||
config = segmentedButtonConfig,
|
|
||||||
showIndication = false,
|
|
||||||
onClick = {
|
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
|
||||||
clickIntents.onCurrencyChangeClick(it.isFiat)
|
|
||||||
},
|
|
||||||
initialSelectedItem = segmentedButtonConfig.getOrNull(selectedButton),
|
|
||||||
isEnabled = isSegmentedButtonsEnabled,
|
|
||||||
) {
|
|
||||||
AmountCurrencyButton(
|
|
||||||
button = it,
|
|
||||||
isSegmentedButtonsEnabled = isSegmentedButtonsEnabled,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SpacerWMax()
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = stringResourceSafe(R.string.send_max_amount),
|
|
||||||
style = TangemTheme.typography.button,
|
|
||||||
color = TangemTheme.colors.text.primary1,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = TangemTheme.dimens.spacing8)
|
|
||||||
.height(TangemTheme.dimens.size40)
|
|
||||||
.clip(shape = RoundedCornerShape(TangemTheme.dimens.radius26))
|
|
||||||
.background(TangemTheme.colors.button.secondary)
|
|
||||||
.clickable {
|
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
|
||||||
clickIntents.onMaxValueClick()
|
|
||||||
}
|
|
||||||
.padding(
|
|
||||||
vertical = TangemTheme.dimens.spacing10,
|
|
||||||
horizontal = TangemTheme.dimens.spacing34,
|
|
||||||
)
|
|
||||||
.testTag(SendScreenTestTags.MAX_BUTTON),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun AmountCurrencyButton(button: AmountSegmentedButtonsConfig, isSegmentedButtonsEnabled: Boolean) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(
|
|
||||||
horizontal = TangemTheme.dimens.spacing10,
|
|
||||||
)
|
|
||||||
.testTag(SendScreenTestTags.CURRENCY_BUTTON),
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
val iconModifier = Modifier
|
|
||||||
.size(TangemTheme.dimens.size18)
|
|
||||||
.padding(horizontal = TangemTheme.dimens.spacing1)
|
|
||||||
if (button.isFiat) {
|
|
||||||
FiatIcon(
|
|
||||||
url = button.iconUrl,
|
|
||||||
size = TangemTheme.dimens.size18,
|
|
||||||
isGrayscale = !isSegmentedButtonsEnabled,
|
|
||||||
modifier = iconModifier.testTag(SendScreenTestTags.FIAT_ICON),
|
|
||||||
)
|
|
||||||
} else if (button.iconState != null) {
|
|
||||||
CurrencyIcon(
|
|
||||||
state = button.iconState,
|
|
||||||
shouldDisplayNetwork = false,
|
|
||||||
modifier = iconModifier.testTag(SendScreenTestTags.CURRENCY_ICON),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = button.title.resolveReference(),
|
|
||||||
color = TangemTheme.colors.text.primary1,
|
|
||||||
style = TangemTheme.typography.button,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(
|
|
||||||
start = TangemTheme.dimens.spacing8,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
package com.tangem.common.ui.amountScreen.ui
|
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.animateContentSize
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.requiredHeightIn
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
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.platform.testTag
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
|
||||||
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.test.SendScreenTestTags
|
|
||||||
import com.tangem.core.ui.utils.rememberDecimalFormat
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun AmountField(
|
|
||||||
amountField: AmountFieldModel,
|
|
||||||
appCurrencyCode: String,
|
|
||||||
onValueChange: (String) -> Unit,
|
|
||||||
onValuePastedTriggerDismiss: () -> Unit,
|
|
||||||
) {
|
|
||||||
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() }
|
|
||||||
val symbolColor = if (primaryValue.isBlank()) TangemTheme.colors.text.disabled else TangemTheme.colors.text.primary1
|
|
||||||
AmountTextField(
|
|
||||||
value = primaryValue,
|
|
||||||
decimals = primaryAmount.decimals,
|
|
||||||
visualTransformation = AmountVisualTransformation(
|
|
||||||
decimals = primaryAmount.decimals,
|
|
||||||
symbol = primaryAmount.currencySymbol,
|
|
||||||
currencyCode = currencyCode,
|
|
||||||
decimalFormat = decimalFormat,
|
|
||||||
symbolColor = symbolColor,
|
|
||||||
),
|
|
||||||
onValueChange = 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 = 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
|
|
||||||
private fun AmountSecondary(amountField: AmountFieldModel, appCurrencyCode: String) {
|
|
||||||
val secondaryAmount = if (amountField.isFiatValue) amountField.cryptoAmount else amountField.fiatAmount
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.animateContentSize()
|
|
||||||
.padding(
|
|
||||||
top = TangemTheme.dimens.spacing8,
|
|
||||||
start = TangemTheme.dimens.spacing12,
|
|
||||||
end = TangemTheme.dimens.spacing12,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
val text = if (amountField.isFiatValue) {
|
|
||||||
secondaryAmount.value.format { crypto(secondaryAmount.currencySymbol, secondaryAmount.decimals) }
|
|
||||||
} else {
|
|
||||||
secondaryAmount.value.format {
|
|
||||||
fiat(
|
|
||||||
fiatCurrencySymbol = secondaryAmount.currencySymbol,
|
|
||||||
fiatCurrencyCode = appCurrencyCode,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
style = TangemTheme.typography.caption2.copy(textDirection = TextDirection.ContentOrLtr),
|
|
||||||
color = TangemTheme.colors.text.tertiary,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(TopCenter)
|
|
||||||
.padding(bottom = TangemTheme.dimens.spacing32)
|
|
||||||
.testTag(SendScreenTestTags.SECONDARY_AMOUNT),
|
|
||||||
)
|
|
||||||
AmountFieldError(
|
|
||||||
isError = amountField.isError,
|
|
||||||
isWarning = amountField.isWarning,
|
|
||||||
error = amountField.error,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(BottomCenter)
|
|
||||||
.padding(
|
|
||||||
top = TangemTheme.dimens.spacing20,
|
|
||||||
bottom = TangemTheme.dimens.spacing12,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun AmountFieldError(
|
|
||||||
isError: Boolean,
|
|
||||||
isWarning: Boolean,
|
|
||||||
error: TextReference,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = isError || isWarning,
|
|
||||||
enter = fadeIn(),
|
|
||||||
exit = fadeOut(),
|
|
||||||
modifier = modifier,
|
|
||||||
) {
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -16,7 +16,6 @@ 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.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.common.ui.R
|
import com.tangem.common.ui.R
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||||
|
|
@ -25,7 +24,6 @@ import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
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.components.currency.icon.CurrencyIconState
|
||||||
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.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
@ -33,60 +31,6 @@ import com.tangem.core.ui.test.SendScreenTestTags
|
||||||
|
|
||||||
private const val AMOUNT_FIELD_KEY = "amountFieldKey"
|
private const val AMOUNT_FIELD_KEY = "amountFieldKey"
|
||||||
|
|
||||||
internal fun LazyListScope.amountField(
|
|
||||||
amountState: AmountState.Data,
|
|
||||||
isBalanceHidden: Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onValueChange: (String) -> Unit,
|
|
||||||
onValuePastedTriggerDismiss: () -> Unit,
|
|
||||||
) {
|
|
||||||
item(key = AMOUNT_FIELD_KEY) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
|
||||||
.background(TangemTheme.colors.background.action),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = amountState.title.resolveReference(),
|
|
||||||
style = TangemTheme.typography.subtitle2,
|
|
||||||
color = TangemTheme.colors.text.tertiary,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = TangemTheme.dimens.spacing14)
|
|
||||||
.testTag(SendScreenTestTags.AMOUNT_CONTAINER_TITLE),
|
|
||||||
)
|
|
||||||
|
|
||||||
val balance = amountState.availableBalance.orMaskWithStars(isBalanceHidden).resolveReference()
|
|
||||||
AnimatedContent(
|
|
||||||
targetState = balance,
|
|
||||||
label = "Hide Balance Animation",
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
style = TangemTheme.typography.caption2,
|
|
||||||
color = TangemTheme.colors.text.tertiary,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = TangemTheme.dimens.spacing2)
|
|
||||||
.testTag(SendScreenTestTags.AMOUNT_CONTAINER_TEXT),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
CurrencyIcon(
|
|
||||||
state = amountState.tokenIconState,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = TangemTheme.dimens.spacing32),
|
|
||||||
)
|
|
||||||
AmountField(
|
|
||||||
amountField = amountState.amountTextField,
|
|
||||||
appCurrencyCode = amountState.appCurrency.code,
|
|
||||||
onValueChange = onValueChange,
|
|
||||||
onValuePastedTriggerDismiss = onValuePastedTriggerDismiss,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun LazyListScope.amountFieldV2(
|
internal fun LazyListScope.amountFieldV2(
|
||||||
amountState: AmountState,
|
amountState: AmountState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
|
@ -118,6 +62,7 @@ internal fun LazyListScope.amountFieldV2(
|
||||||
text = amountState.title.resolveReference(),
|
text = amountState.title.resolveReference(),
|
||||||
style = TangemTheme.typography.subtitle2,
|
style = TangemTheme.typography.subtitle2,
|
||||||
color = TangemTheme.colors.text.tertiary,
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
modifier = Modifier.testTag(SendScreenTestTags.AMOUNT_CONTAINER_TITLE),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AmountFieldV2(
|
AmountFieldV2(
|
||||||
|
|
@ -175,7 +120,8 @@ private fun AmountInfo(amountUM: AmountState, onMaxAmountClick: () -> Unit, modi
|
||||||
indication = ripple(),
|
indication = ripple(),
|
||||||
onClick = onMaxAmountClick,
|
onClick = onMaxAmountClick,
|
||||||
)
|
)
|
||||||
.padding(horizontal = 12.dp, vertical = 4.dp),
|
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
|
.testTag(SendScreenTestTags.MAX_BUTTON),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -183,46 +129,53 @@ private fun AmountInfo(amountUM: AmountState, onMaxAmountClick: () -> Unit, modi
|
||||||
@Composable
|
@Composable
|
||||||
private fun AmountInfoMain(amountUM: AmountState, modifier: Modifier = Modifier) {
|
private fun AmountInfoMain(amountUM: AmountState, modifier: Modifier = Modifier) {
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = amountUM !is AmountState.Data,
|
targetState = amountUM,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) { isContent ->
|
) { currentAmount ->
|
||||||
if (isContent) {
|
Column(
|
||||||
Column(
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
) {
|
||||||
) {
|
when (currentAmount) {
|
||||||
TextShimmer(
|
is AmountState.Data -> {
|
||||||
style = TangemTheme.typography.subtitle2,
|
val amountUM = amountUM as AmountState.Data
|
||||||
modifier = Modifier.width(56.dp),
|
Text(
|
||||||
)
|
text = amountUM.tokenName.resolveReference(),
|
||||||
TextShimmer(
|
style = TangemTheme.typography.subtitle2,
|
||||||
style = TangemTheme.typography.caption2,
|
color = TangemTheme.colors.text.primary1,
|
||||||
modifier = Modifier.width(72.dp),
|
maxLines = 1,
|
||||||
)
|
modifier = Modifier.testTag(SendScreenTestTags.TOKEN_NAME),
|
||||||
}
|
|
||||||
} 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,
|
|
||||||
)
|
|
||||||
Row {
|
|
||||||
EllipsisText(
|
|
||||||
text = amountUM.availableBalanceCrypto.resolveReference(),
|
|
||||||
style = TangemTheme.typography.caption2,
|
|
||||||
color = TangemTheme.colors.text.tertiary,
|
|
||||||
ellipsis = TextEllipsis.OffsetEnd(amountUM.amountTextField.cryptoAmount.currencySymbol.length),
|
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
|
||||||
)
|
)
|
||||||
EllipsisText(
|
Row {
|
||||||
text = amountUM.availableBalanceFiat.resolveReference(),
|
EllipsisText(
|
||||||
|
text = amountUM.availableBalanceCrypto.resolveReference(),
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
ellipsis = TextEllipsis.OffsetEnd(
|
||||||
|
amountUM.amountTextField.cryptoAmount.currencySymbol.length,
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f, fill = false)
|
||||||
|
.testTag(SendScreenTestTags.PRIMARY_AMOUNT),
|
||||||
|
)
|
||||||
|
EllipsisText(
|
||||||
|
text = amountUM.availableBalanceFiat.resolveReference(),
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
ellipsis = TextEllipsis.OffsetEnd(
|
||||||
|
amountUM.amountTextField.fiatAmount.currencySymbol.length,
|
||||||
|
),
|
||||||
|
modifier = Modifier.testTag(SendScreenTestTags.SECONDARY_AMOUNT),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AmountState.Empty -> {
|
||||||
|
TextShimmer(
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
modifier = Modifier.width(56.dp),
|
||||||
|
)
|
||||||
|
TextShimmer(
|
||||||
style = TangemTheme.typography.caption2,
|
style = TangemTheme.typography.caption2,
|
||||||
color = TangemTheme.colors.text.tertiary,
|
modifier = Modifier.width(72.dp),
|
||||||
ellipsis = TextEllipsis.OffsetEnd(amountUM.amountTextField.fiatAmount.currencySymbol.length),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ fun NavigationButtonsBlock(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||||
) {
|
) {
|
||||||
PreviousButton(state?.prevButton)
|
|
||||||
NavigationPrimaryButton(state?.primaryButton, modifier = Modifier.weight(1f))
|
NavigationPrimaryButton(state?.primaryButton, modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ sealed class NavigationButtonsState {
|
||||||
|
|
||||||
data class Data(
|
data class Data(
|
||||||
val primaryButton: NavigationButton?,
|
val primaryButton: NavigationButton?,
|
||||||
val prevButton: NavigationButton?,
|
|
||||||
val extraButtons: Pair<NavigationButton, NavigationButton>?,
|
val extraButtons: Pair<NavigationButton, NavigationButton>?,
|
||||||
val txUrl: String? = null,
|
val txUrl: String? = null,
|
||||||
val onTextClick: (String) -> Unit,
|
val onTextClick: (String) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package com.tangem.common.ui.navigationButtons.preview
|
||||||
import com.tangem.common.ui.R
|
import com.tangem.common.ui.R
|
||||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||||
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
|
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
|
||||||
internal object NavigationButtonsPreview {
|
internal object NavigationButtonsPreview {
|
||||||
|
|
@ -26,16 +25,6 @@ internal object NavigationButtonsPreview {
|
||||||
onClick = {},
|
onClick = {},
|
||||||
)
|
)
|
||||||
|
|
||||||
private val prev = NavigationButton(
|
|
||||||
textReference = TextReference.EMPTY,
|
|
||||||
iconRes = R.drawable.ic_back_24,
|
|
||||||
isSecondary = true,
|
|
||||||
isIconVisible = true,
|
|
||||||
shouldShowProgress = false,
|
|
||||||
isEnabled = true,
|
|
||||||
onClick = {},
|
|
||||||
)
|
|
||||||
|
|
||||||
private val finished = NavigationButton(
|
private val finished = NavigationButton(
|
||||||
textReference = resourceReference(R.string.common_close),
|
textReference = resourceReference(R.string.common_close),
|
||||||
isSecondary = false,
|
isSecondary = false,
|
||||||
|
|
@ -47,7 +36,6 @@ internal object NavigationButtonsPreview {
|
||||||
|
|
||||||
val allButtons = NavigationButtonsState.Data(
|
val allButtons = NavigationButtonsState.Data(
|
||||||
primaryButton = finished,
|
primaryButton = finished,
|
||||||
prevButton = prev,
|
|
||||||
extraButtons = extraButtons,
|
extraButtons = extraButtons,
|
||||||
txUrl = "https://tangem.com",
|
txUrl = "https://tangem.com",
|
||||||
onTextClick = {},
|
onTextClick = {},
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ import com.tangem.core.ui.components.icons.IconTint
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
|
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
|
||||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||||
|
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.core.ui.format.bigdecimal.crypto
|
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
|
|
@ -15,6 +17,8 @@ import com.tangem.core.ui.format.bigdecimal.percent
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.models.StatusSource
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||||
import com.tangem.domain.models.staking.YieldBalance
|
import com.tangem.domain.models.staking.YieldBalance
|
||||||
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
|
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
|
||||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||||
|
|
@ -35,10 +39,13 @@ import java.math.BigDecimal
|
||||||
*/
|
*/
|
||||||
class TokenItemStateConverter(
|
class TokenItemStateConverter(
|
||||||
private val appCurrency: AppCurrency,
|
private val appCurrency: AppCurrency,
|
||||||
|
private val apyMap: Map<String, String> = emptyMap(),
|
||||||
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
|
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
|
||||||
CryptoCurrencyToIconStateConverter().convert(it)
|
CryptoCurrencyToIconStateConverter().convert(it)
|
||||||
},
|
},
|
||||||
private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = Companion::createTitleState,
|
private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = {
|
||||||
|
createTitleState(it, apyMap)
|
||||||
|
},
|
||||||
private val subtitleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.SubtitleState? = {
|
private val subtitleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.SubtitleState? = {
|
||||||
createSubtitleState(it, appCurrency)
|
createSubtitleState(it, appCurrency)
|
||||||
},
|
},
|
||||||
|
|
@ -144,7 +151,10 @@ class TokenItemStateConverter(
|
||||||
private fun CryptoCurrencyStatus.getStakedBalance() = (value.yieldBalance as? YieldBalance.Data)
|
private fun CryptoCurrencyStatus.getStakedBalance() = (value.yieldBalance as? YieldBalance.Data)
|
||||||
?.getTotalWithRewardsStakingBalance(blockchainId = currency.network.rawId).orZero()
|
?.getTotalWithRewardsStakingBalance(blockchainId = currency.network.rawId).orZero()
|
||||||
|
|
||||||
private fun createTitleState(currencyStatus: CryptoCurrencyStatus): TokenItemState.TitleState {
|
private fun createTitleState(
|
||||||
|
currencyStatus: CryptoCurrencyStatus,
|
||||||
|
apyMap: Map<String, String>,
|
||||||
|
): TokenItemState.TitleState {
|
||||||
return when (val value = currencyStatus.value) {
|
return when (val value = currencyStatus.value) {
|
||||||
is CryptoCurrencyStatus.Loading,
|
is CryptoCurrencyStatus.Loading,
|
||||||
is CryptoCurrencyStatus.MissedDerivation,
|
is CryptoCurrencyStatus.MissedDerivation,
|
||||||
|
|
@ -158,14 +168,33 @@ class TokenItemStateConverter(
|
||||||
is CryptoCurrencyStatus.NoQuote,
|
is CryptoCurrencyStatus.NoQuote,
|
||||||
is CryptoCurrencyStatus.NoAccount,
|
is CryptoCurrencyStatus.NoAccount,
|
||||||
-> {
|
-> {
|
||||||
|
val earnApyText = resolveEarnApy(currencyStatus, apyMap)?.let { apy ->
|
||||||
|
resourceReference(
|
||||||
|
R.string.yield_module_earn_badge,
|
||||||
|
wrappedList(apy),
|
||||||
|
)
|
||||||
|
}
|
||||||
TokenItemState.TitleState.Content(
|
TokenItemState.TitleState.Content(
|
||||||
text = stringReference(currencyStatus.currency.name),
|
text = stringReference(currencyStatus.currency.name),
|
||||||
hasPending = value.hasCurrentNetworkTransactions,
|
hasPending = value.hasCurrentNetworkTransactions,
|
||||||
|
earnApy = earnApyText,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resolveEarnApy(cryptoCurrencyStatus: CryptoCurrencyStatus, apyMap: Map<String, String>): String? {
|
||||||
|
if (apyMap.isEmpty()) return null
|
||||||
|
|
||||||
|
val isYieldSupplyActive = (cryptoCurrencyStatus.value as? CryptoCurrencyStatus.Loaded)
|
||||||
|
?.yieldSupplyStatus?.isActive == true
|
||||||
|
if (isYieldSupplyActive) return null
|
||||||
|
|
||||||
|
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return null
|
||||||
|
|
||||||
|
return apyMap[token.yieldSupplyKey()]
|
||||||
|
}
|
||||||
|
|
||||||
private fun createSubtitleState(
|
private fun createSubtitleState(
|
||||||
currencyStatus: CryptoCurrencyStatus,
|
currencyStatus: CryptoCurrencyStatus,
|
||||||
appCurrency: AppCurrency,
|
appCurrency: AppCurrency,
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,6 @@ import com.tangem.core.ui.components.TextShimmer
|
||||||
import com.tangem.core.ui.components.account.AccountIconSize
|
import com.tangem.core.ui.components.account.AccountIconSize
|
||||||
import com.tangem.core.ui.components.block.BlockCard
|
import com.tangem.core.ui.components.block.BlockCard
|
||||||
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
||||||
import com.tangem.core.ui.components.label.Label
|
|
||||||
import com.tangem.core.ui.components.label.entity.LabelStyle
|
|
||||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
|
||||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||||
import com.tangem.core.ui.extensions.*
|
import com.tangem.core.ui.extensions.*
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
@ -76,8 +73,6 @@ fun UserWalletItem(
|
||||||
balance = state.balance,
|
balance = state.balance,
|
||||||
)
|
)
|
||||||
|
|
||||||
state.label?.let { Label(it) }
|
|
||||||
|
|
||||||
when (state.endIcon) {
|
when (state.endIcon) {
|
||||||
UserWalletItemUM.EndIcon.None -> Unit
|
UserWalletItemUM.EndIcon.None -> Unit
|
||||||
UserWalletItemUM.EndIcon.Arrow -> {
|
UserWalletItemUM.EndIcon.Arrow -> {
|
||||||
|
|
@ -94,6 +89,13 @@ fun UserWalletItem(
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
UserWalletItemUM.EndIcon.Warning -> {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(R.drawable.ic_alert_circle_24),
|
||||||
|
tint = TangemTheme.colors.icon.warning,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -316,10 +318,7 @@ private class UserWalletItemUMPreviewProvider : PreviewParameterProvider<UserWal
|
||||||
name = stringReference("Mobile Wallet"),
|
name = stringReference("Mobile Wallet"),
|
||||||
information = getInformation(cardCount = 1),
|
information = getInformation(cardCount = 1),
|
||||||
balance = UserWalletItemUM.Balance.Locked,
|
balance = UserWalletItemUM.Balance.Locked,
|
||||||
label = LabelUM(
|
endIcon = UserWalletItemUM.EndIcon.Warning,
|
||||||
text = resourceReference(R.string.hw_backup_no_backup),
|
|
||||||
style = LabelStyle.WARNING,
|
|
||||||
),
|
|
||||||
isEnabled = true,
|
isEnabled = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@ package com.tangem.common.ui.userwallet.converter
|
||||||
|
|
||||||
import com.tangem.common.ui.R
|
import com.tangem.common.ui.R
|
||||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||||
import com.tangem.core.ui.components.label.entity.LabelStyle
|
|
||||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.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.core.ui.extensions.wrappedList
|
||||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
|
|
@ -52,7 +49,6 @@ class UserWalletItemUMConverter(
|
||||||
endIcon = endIcon,
|
endIcon = endIcon,
|
||||||
onClick = { onClick(value.walletId) },
|
onClick = { onClick(value.walletId) },
|
||||||
imageState = artwork,
|
imageState = artwork,
|
||||||
label = getLabelOrNull(userWallet = this),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -61,17 +57,6 @@ class UserWalletItemUMConverter(
|
||||||
return isAuthMode || userWallet.isLocked.not()
|
return isAuthMode || userWallet.isLocked.not()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLabelOrNull(userWallet: UserWallet): LabelUM? {
|
|
||||||
return if (isAuthMode.not() && userWallet is UserWallet.Hot && !userWallet.backedUp) {
|
|
||||||
LabelUM(
|
|
||||||
text = resourceReference(R.string.hw_backup_no_backup),
|
|
||||||
style = LabelStyle.WARNING,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getInfo(userWallet: UserWallet): UserWalletItemUM.Information.Loaded {
|
private fun getInfo(userWallet: UserWallet): UserWalletItemUM.Information.Loaded {
|
||||||
val text = when (userWallet) {
|
val text = when (userWallet) {
|
||||||
is UserWallet.Cold -> {
|
is UserWallet.Cold -> {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.tangem.common.ui.userwallet.state
|
||||||
|
|
||||||
import com.tangem.common.ui.account.CryptoPortfolioIconUM
|
import com.tangem.common.ui.account.CryptoPortfolioIconUM
|
||||||
import com.tangem.core.ui.components.artwork.ArtworkUM
|
import com.tangem.core.ui.components.artwork.ArtworkUM
|
||||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import javax.annotation.concurrent.Immutable
|
import javax.annotation.concurrent.Immutable
|
||||||
|
|
@ -17,12 +16,13 @@ data class UserWalletItemUM(
|
||||||
val isEnabled: Boolean,
|
val isEnabled: Boolean,
|
||||||
val endIcon: EndIcon = EndIcon.None,
|
val endIcon: EndIcon = EndIcon.None,
|
||||||
val onClick: () -> Unit,
|
val onClick: () -> Unit,
|
||||||
val label: LabelUM? = null,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
enum class EndIcon {
|
enum class EndIcon {
|
||||||
None,
|
None,
|
||||||
Arrow,
|
Arrow,
|
||||||
Checkmark,
|
Checkmark,
|
||||||
|
Warning,
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Balance {
|
sealed class Balance {
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,6 @@
|
||||||
"name": "alephium",
|
"name": "alephium",
|
||||||
"version": "5.21.0"
|
"version": "5.21.0"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "scroll",
|
|
||||||
"version": "undefined"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "zklink",
|
"name": "zklink",
|
||||||
"version": "undefined"
|
"version": "undefined"
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ internal class TangemPay(
|
||||||
|
|
||||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||||
environment = ApiEnvironment.PROD,
|
environment = ApiEnvironment.PROD,
|
||||||
baseUrl = "https://api.paera.com/bff/",
|
baseUrl = "https://api.us.paera.com/bff/",
|
||||||
headers = createHeaders(),
|
headers = createHeaders(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ interface TangemTechApi {
|
||||||
@Path("walletId") walletId: String,
|
@Path("walletId") walletId: String,
|
||||||
@Header("If-Match") eTag: String,
|
@Header("If-Match") eTag: String,
|
||||||
@Body body: SaveWalletAccountsResponse,
|
@Body body: SaveWalletAccountsResponse,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<GetWalletAccountsResponse>
|
||||||
|
|
||||||
@GET("/v1/wallets/{walletId}/accounts/archived")
|
@GET("/v1/wallets/{walletId}/accounts/archived")
|
||||||
suspend fun getWalletArchivedAccounts(
|
suspend fun getWalletArchivedAccounts(
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import com.tangem.datasource.api.common.response.ApiResponse
|
||||||
import com.tangem.datasource.api.tangemTech.models.YieldMarketsResponse
|
import com.tangem.datasource.api.tangemTech.models.YieldMarketsResponse
|
||||||
import com.tangem.datasource.api.tangemTech.models.YieldModuleStatusResponse
|
import com.tangem.datasource.api.tangemTech.models.YieldModuleStatusResponse
|
||||||
import com.tangem.datasource.api.tangemTech.models.YieldSupplyChangeTokenStatusBody
|
import com.tangem.datasource.api.tangemTech.models.YieldSupplyChangeTokenStatusBody
|
||||||
import com.tangem.datasource.api.tangemTech.models.YieldTokenStatusResponse
|
import com.tangem.datasource.api.tangemTech.models.YieldSupplyMarketTokenDto
|
||||||
import com.tangem.datasource.api.tangemTech.models.YieldTokenChartResponse
|
import com.tangem.datasource.api.tangemTech.models.YieldTokenChartResponse
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
|
@ -15,13 +15,13 @@ import retrofit2.http.Query
|
||||||
interface YieldSupplyApi {
|
interface YieldSupplyApi {
|
||||||
|
|
||||||
@GET("api/v1/yield/markets")
|
@GET("api/v1/yield/markets")
|
||||||
suspend fun getYieldMarkets(@Query("chainId") chainId: Int? = null): ApiResponse<YieldMarketsResponse>
|
suspend fun getYieldMarkets(@Query("chainId") chainId: String? = null): ApiResponse<YieldMarketsResponse>
|
||||||
|
|
||||||
@GET("api/v1/yield/token/{chainId}/{tokenAddress}")
|
@GET("api/v1/yield/token/{chainId}/{tokenAddress}")
|
||||||
suspend fun getYieldTokenStatus(
|
suspend fun getYieldTokenStatus(
|
||||||
@Path("chainId") chainId: Int,
|
@Path("chainId") chainId: Int,
|
||||||
@Path("tokenAddress") tokenAddress: String,
|
@Path("tokenAddress") tokenAddress: String,
|
||||||
): ApiResponse<YieldTokenStatusResponse>
|
): ApiResponse<YieldSupplyMarketTokenDto>
|
||||||
|
|
||||||
@GET("api/v1/yield/token/{chainId}/{tokenAddress}/chart")
|
@GET("api/v1/yield/token/{chainId}/{tokenAddress}/chart")
|
||||||
suspend fun getYieldTokenChart(
|
suspend fun getYieldTokenChart(
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,9 @@ package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class YieldMarketsResponse(
|
data class YieldMarketsResponse(
|
||||||
@Json(name = "tokens") val marketDtos: List<MarketDto>,
|
@Json(name = "tokens") val marketDtos: List<YieldSupplyMarketTokenDto>,
|
||||||
@Json(name = "lastUpdatedAt") val lastUpdated: String,
|
@Json(name = "lastUpdatedAt") val lastUpdated: String,
|
||||||
) {
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
data class MarketDto(
|
|
||||||
@Json(name = "tokenAddress") val tokenAddress: String? = null,
|
|
||||||
@Json(name = "tokenSymbol") val tokenSymbol: String? = null,
|
|
||||||
@Json(name = "tokenName") val tokenName: String? = null,
|
|
||||||
@Json(name = "apy") val apy: BigDecimal,
|
|
||||||
@Json(name = "isActive") val isActive: Boolean,
|
|
||||||
@Json(name = "chainId") val chainId: Int? = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@ import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class YieldTokenStatusResponse(
|
data class YieldSupplyMarketTokenDto(
|
||||||
@Json(name = "tokenAddress") val tokenAddress: String? = null,
|
@Json(name = "tokenAddress") val tokenAddress: String? = null,
|
||||||
@Json(name = "tokenSymbol") val tokenSymbol: String? = null,
|
@Json(name = "tokenSymbol") val tokenSymbol: String? = null,
|
||||||
@Json(name = "tokenName") val tokenName: String? = null,
|
@Json(name = "tokenName") val tokenName: String? = null,
|
||||||
|
|
@ -2,8 +2,37 @@ package com.tangem.datasource.api.tangemTech.models.account
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import com.tangem.datasource.utils.SerializeNulls
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class SaveWalletAccountsResponse(
|
data class SaveWalletAccountsResponse(
|
||||||
@Json(name = "accounts") val accounts: List<WalletAccountDTO>,
|
@Json(name = "accounts") val accounts: List<AccountDTO>,
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
@SerializeNulls
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class AccountDTO(
|
||||||
|
@Json(name = "id") val id: String,
|
||||||
|
@Json(name = "name") val name: String?,
|
||||||
|
@Json(name = "derivation") val derivationIndex: Int,
|
||||||
|
@Json(name = "icon") val icon: String,
|
||||||
|
@Json(name = "iconColor") val iconColor: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
operator fun invoke(accounts: List<WalletAccountDTO>): SaveWalletAccountsResponse {
|
||||||
|
return SaveWalletAccountsResponse(
|
||||||
|
accounts = accounts.map { accountDto ->
|
||||||
|
AccountDTO(
|
||||||
|
id = accountDto.id,
|
||||||
|
name = accountDto.name,
|
||||||
|
derivationIndex = accountDto.derivationIndex,
|
||||||
|
icon = accountDto.icon,
|
||||||
|
iconColor = accountDto.iconColor,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import com.tangem.common.json.MoshiJsonConverter
|
||||||
import com.tangem.datasource.api.common.adapter.*
|
import com.tangem.datasource.api.common.adapter.*
|
||||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||||
|
import com.tangem.datasource.utils.SerializeNullsFactory
|
||||||
import com.tangem.domain.models.scan.serialization.*
|
import com.tangem.domain.models.scan.serialization.*
|
||||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||||
|
|
@ -28,6 +29,7 @@ class MoshiModule {
|
||||||
@NetworkMoshi
|
@NetworkMoshi
|
||||||
fun provideNetworkMoshi(): Moshi {
|
fun provideNetworkMoshi(): Moshi {
|
||||||
return Moshi.Builder()
|
return Moshi.Builder()
|
||||||
|
.add(SerializeNullsFactory)
|
||||||
.add(
|
.add(
|
||||||
PolymorphicJsonAdapterFactory.of(ProviderModel::class.java, "type")
|
PolymorphicJsonAdapterFactory.of(ProviderModel::class.java, "type")
|
||||||
.withSubtype(ProviderModel.Public::class.java, "public")
|
.withSubtype(ProviderModel.Public::class.java, "public")
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ import android.content.Context
|
||||||
import androidx.datastore.core.DataStoreFactory
|
import androidx.datastore.core.DataStoreFactory
|
||||||
import androidx.datastore.dataStoreFile
|
import androidx.datastore.dataStoreFile
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.tangem.datasource.api.tangemTech.models.YieldSupplyMarketTokenDto
|
||||||
import com.tangem.datasource.local.yieldsupply.DefaultYieldMarketsStore
|
import com.tangem.datasource.local.yieldsupply.DefaultYieldMarketsStore
|
||||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||||
import com.tangem.datasource.utils.listTypes
|
import com.tangem.datasource.utils.listTypes
|
||||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -34,7 +34,7 @@ object YieldSupplyModule {
|
||||||
persistenceStore = DataStoreFactory.create(
|
persistenceStore = DataStoreFactory.create(
|
||||||
serializer = MoshiDataStoreSerializer(
|
serializer = MoshiDataStoreSerializer(
|
||||||
moshi = moshi,
|
moshi = moshi,
|
||||||
types = listTypes<YieldMarketToken>(),
|
types = listTypes<YieldSupplyMarketTokenDto>(),
|
||||||
defaultValue = emptyList(),
|
defaultValue = emptyList(),
|
||||||
),
|
),
|
||||||
produceFile = { context.dataStoreFile(fileName = "yield_markets_cache") },
|
produceFile = { context.dataStoreFile(fileName = "yield_markets_cache") },
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,7 @@ interface TangemPayStorage {
|
||||||
|
|
||||||
suspend fun getOrderId(customerWalletAddress: String): String?
|
suspend fun getOrderId(customerWalletAddress: String): String?
|
||||||
|
|
||||||
suspend fun clear(customerWalletAddress: String)
|
suspend fun clearOrderId(customerWalletAddress: String)
|
||||||
|
|
||||||
|
suspend fun clearAll(customerWalletAddress: String)
|
||||||
}
|
}
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
package com.tangem.datasource.local.yieldsupply
|
package com.tangem.datasource.local.yieldsupply
|
||||||
|
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
import com.tangem.datasource.api.tangemTech.models.YieldSupplyMarketTokenDto
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
|
||||||
internal class DefaultYieldMarketsStore(
|
internal class DefaultYieldMarketsStore(
|
||||||
private val persistenceStore: DataStore<List<YieldMarketToken>>,
|
private val persistenceStore: DataStore<List<YieldSupplyMarketTokenDto>>,
|
||||||
) : YieldMarketsStore {
|
) : YieldMarketsStore {
|
||||||
|
|
||||||
override fun get(): Flow<List<YieldMarketToken>> = persistenceStore.data
|
override fun get(): Flow<List<YieldSupplyMarketTokenDto>> = persistenceStore.data
|
||||||
|
|
||||||
override suspend fun getSyncOrNull(): List<YieldMarketToken>? {
|
override suspend fun getSyncOrNull(): List<YieldSupplyMarketTokenDto>? {
|
||||||
return persistenceStore.data.firstOrNull()
|
return persistenceStore.data.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun store(items: List<YieldMarketToken>) {
|
override suspend fun store(items: List<YieldSupplyMarketTokenDto>) {
|
||||||
persistenceStore.updateData { _ -> items }
|
persistenceStore.updateData { _ -> items }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
package com.tangem.datasource.local.yieldsupply
|
package com.tangem.datasource.local.yieldsupply
|
||||||
|
|
||||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
import com.tangem.datasource.api.tangemTech.models.YieldSupplyMarketTokenDto
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface YieldMarketsStore {
|
interface YieldMarketsStore {
|
||||||
|
|
||||||
fun get(): Flow<List<YieldMarketToken>>
|
fun get(): Flow<List<YieldSupplyMarketTokenDto>>
|
||||||
|
|
||||||
suspend fun getSyncOrNull(): List<YieldMarketToken>?
|
suspend fun getSyncOrNull(): List<YieldSupplyMarketTokenDto>?
|
||||||
|
|
||||||
suspend fun store(items: List<YieldMarketToken>)
|
suspend fun store(items: List<YieldSupplyMarketTokenDto>)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.tangem.datasource.utils
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class SerializeNulls
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.tangem.datasource.utils
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.squareup.moshi.Types
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to serialize nulls in Moshi if the class is annotated with [SerializeNulls].
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
internal object SerializeNullsFactory : JsonAdapter.Factory {
|
||||||
|
|
||||||
|
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
|
||||||
|
val rawType = Types.getRawType(type)
|
||||||
|
if (!rawType.isAnnotationPresent(SerializeNulls::class.java)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val nextAdapter: JsonAdapter<Any> = moshi.nextAdapter(this, type, annotations)
|
||||||
|
|
||||||
|
return nextAdapter.serializeNulls()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.tangem.datasource.utils
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
|
||||||
|
// --- DTO ---
|
||||||
|
@SerializeNulls
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class UserWithNulls(val id: String?, val name: String?)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class UserWithoutNulls(val id: String?, val name: String?)
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
class SerializeNullsFactoryTest {
|
||||||
|
|
||||||
|
private val moshi = Moshi.Builder()
|
||||||
|
.add(SerializeNullsFactory)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should serialize nulls for annotated class`() {
|
||||||
|
val adapter = moshi.adapter(UserWithNulls::class.java)
|
||||||
|
|
||||||
|
val json = adapter.toJson(UserWithNulls(id = null, name = "John"))
|
||||||
|
|
||||||
|
assertThat(json).isEqualTo("""{"id":null,"name":"John"}""")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should skip nulls for non-annotated class`() {
|
||||||
|
val adapter = moshi.adapter(UserWithoutNulls::class.java)
|
||||||
|
|
||||||
|
val json = adapter.toJson(UserWithoutNulls(id = null, name = "John"))
|
||||||
|
|
||||||
|
assertThat(json).isEqualTo("""{"name":"John"}""")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize annotated class correctly`() {
|
||||||
|
val adapter = moshi.adapter(UserWithNulls::class.java)
|
||||||
|
|
||||||
|
val json = """{"id":null,"name":"Jane"}"""
|
||||||
|
val result = adapter.fromJson(json)
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(UserWithNulls(id = null, name = "Jane"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -40,7 +40,7 @@ fun SimpleSettingsRow(
|
||||||
onItemsClick()
|
onItemsClick()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
).testTag(BaseBottomSheetTestTags.ACTION_TITLE),
|
).testTag(BaseBottomSheetTestTags.ACTION_BUTTON),
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
|
@ -48,7 +48,8 @@ fun SimpleSettingsRow(
|
||||||
painter = painterResource(id = icon),
|
painter = painterResource(id = icon),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = if (redesign) TangemTheme.dimens.spacing12 else TangemTheme.dimens.spacing20),
|
.padding(horizontal = if (redesign) TangemTheme.dimens.spacing12 else TangemTheme.dimens.spacing20)
|
||||||
|
.testTag(BaseBottomSheetTestTags.ACTION_ICON),
|
||||||
tint = rowColors.iconColor(enabled = enabled).value,
|
tint = rowColors.iconColor(enabled = enabled).value,
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,54 @@
|
||||||
package com.tangem.core.ui.components
|
package com.tangem.core.ui.components
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import com.google.accompanist.systemuicontroller.SystemUiController
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import com.tangem.core.ui.res.LocalIsInDarkTheme
|
import com.tangem.core.ui.res.LocalIsInDarkTheme
|
||||||
|
|
||||||
|
val LocalSystemBarsIconsController = staticCompositionLocalOf<SystemBarsIconsController> {
|
||||||
|
error("No SystemBarsIconsController provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
class SystemBarsIconsController(private val systemUiController: SystemUiController) {
|
||||||
|
private var count by mutableIntStateOf(0)
|
||||||
|
|
||||||
|
fun setIcons(darkIcons: Boolean, isNavigationBarContrastEnforced: Boolean) {
|
||||||
|
if (count == 0) {
|
||||||
|
systemUiController.systemBarsDarkContentEnabled = darkIcons
|
||||||
|
systemUiController.isNavigationBarContrastEnforced = isNavigationBarContrastEnforced
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restoreIcons(isDarkTheme: Boolean) {
|
||||||
|
count--
|
||||||
|
if (count == 0) {
|
||||||
|
systemUiController.systemBarsDarkContentEnabled = !isDarkTheme
|
||||||
|
systemUiController.isNavigationBarContrastEnforced = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProvideSystemBarsIconsController(content: @Composable () -> Unit) {
|
||||||
|
val systemUiController = rememberSystemUiController()
|
||||||
|
val controller = remember(systemUiController) { SystemBarsIconsController(systemUiController) }
|
||||||
|
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalSystemBarsIconsController provides controller,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the ability to set a scrim for 3-button navigation
|
* Provides the ability to set a scrim for 3-button navigation
|
||||||
*
|
*
|
||||||
|
|
@ -43,19 +85,16 @@ fun NavigationBar3ButtonsScrim() {
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun SystemBarsIconsDisposable(darkIcons: Boolean, isNavigationBarContrastEnforced: Boolean = false) {
|
fun SystemBarsIconsDisposable(darkIcons: Boolean, isNavigationBarContrastEnforced: Boolean = false) {
|
||||||
val systemUiController = rememberSystemUiController()
|
val controller = LocalSystemBarsIconsController.current
|
||||||
|
val isDarkTheme = LocalIsInDarkTheme.current
|
||||||
|
|
||||||
SideEffect {
|
SideEffect {
|
||||||
systemUiController.systemBarsDarkContentEnabled = darkIcons
|
controller.setIcons(darkIcons, isNavigationBarContrastEnforced)
|
||||||
systemUiController.isNavigationBarContrastEnforced = isNavigationBarContrastEnforced
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDarkTheme = LocalIsInDarkTheme.current
|
|
||||||
|
|
||||||
DisposableEffect(isDarkTheme) {
|
DisposableEffect(isDarkTheme) {
|
||||||
onDispose {
|
onDispose {
|
||||||
systemUiController.systemBarsDarkContentEnabled = !isDarkTheme
|
controller.restoreIcons(isDarkTheme)
|
||||||
systemUiController.isNavigationBarContrastEnforced = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -51,7 +51,19 @@ fun BlockItem(model: BlockUM, modifier: Modifier = Modifier) {
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
|
|
||||||
model.label?.let { Label(it) }
|
when (val endContent = model.endContent) {
|
||||||
|
is BlockUM.EndContent.None -> Unit
|
||||||
|
is BlockUM.EndContent.Icon -> Icon(
|
||||||
|
painter = painterResource(id = endContent.resId),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = when (endContent.accentType) {
|
||||||
|
BlockUM.AccentType.NONE -> TangemTheme.colors.text.primary1
|
||||||
|
BlockUM.AccentType.ACCENT -> TangemTheme.colors.text.accent
|
||||||
|
BlockUM.AccentType.WARNING -> TangemTheme.colors.text.warning
|
||||||
|
},
|
||||||
|
)
|
||||||
|
is BlockUM.EndContent.Label -> Label(endContent.label)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,15 +3,29 @@ package com.tangem.core.ui.components.block.model
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import javax.annotation.concurrent.Immutable
|
||||||
|
|
||||||
data class BlockUM(
|
data class BlockUM(
|
||||||
val text: TextReference,
|
val text: TextReference,
|
||||||
@DrawableRes val iconRes: Int,
|
@DrawableRes val iconRes: Int,
|
||||||
val onClick: () -> Unit,
|
val onClick: () -> Unit,
|
||||||
val accentType: AccentType = AccentType.NONE,
|
val accentType: AccentType = AccentType.NONE,
|
||||||
val label: LabelUM? = null,
|
val endContent: EndContent = EndContent.None,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
sealed interface EndContent {
|
||||||
|
data object None : EndContent
|
||||||
|
data class Label(
|
||||||
|
val label: LabelUM,
|
||||||
|
) : EndContent
|
||||||
|
|
||||||
|
data class Icon(
|
||||||
|
val resId: Int,
|
||||||
|
val accentType: AccentType = AccentType.NONE,
|
||||||
|
) : EndContent
|
||||||
|
}
|
||||||
|
|
||||||
enum class AccentType {
|
enum class AccentType {
|
||||||
NONE, ACCENT, WARNING,
|
NONE, ACCENT, WARNING,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,20 @@ private const val DISABLED_ICON_ALPHA = 0.4f
|
||||||
* [Figma Component](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2737-2800&t=ewlXfWwbDnRhjw4B-4)
|
* [Figma Component](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2737-2800&t=ewlXfWwbDnRhjw4B-4)
|
||||||
* */
|
* */
|
||||||
@Composable
|
@Composable
|
||||||
fun BlockchainRow(model: BlockchainRowUM, modifier: Modifier = Modifier, action: @Composable BoxScope.() -> Unit) {
|
fun BlockchainRow(
|
||||||
|
model: BlockchainRowUM,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
itemPadding: PaddingValues = PaddingValues(
|
||||||
|
top = TangemTheme.dimens.spacing8,
|
||||||
|
bottom = TangemTheme.dimens.spacing8,
|
||||||
|
start = TangemTheme.dimens.spacing8,
|
||||||
|
),
|
||||||
|
action: @Composable BoxScope.() -> Unit,
|
||||||
|
) {
|
||||||
RowContentContainer(
|
RowContentContainer(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.heightIn(min = TangemTheme.dimens.size52)
|
.heightIn(min = TangemTheme.dimens.size52)
|
||||||
.padding(
|
.padding(itemPadding),
|
||||||
top = TangemTheme.dimens.spacing8,
|
|
||||||
bottom = TangemTheme.dimens.spacing8,
|
|
||||||
start = TangemTheme.dimens.spacing8,
|
|
||||||
),
|
|
||||||
icon = {
|
icon = {
|
||||||
RowIcon(
|
RowIcon(
|
||||||
resId = model.iconResId,
|
resId = model.iconResId,
|
||||||
|
|
@ -58,7 +63,7 @@ fun BlockchainRow(model: BlockchainRowUM, modifier: Modifier = Modifier, action:
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RowIcon(
|
fun RowIcon(
|
||||||
@DrawableRes resId: Int,
|
@DrawableRes resId: Int,
|
||||||
isColored: Boolean,
|
isColored: Boolean,
|
||||||
showAccentBadge: Boolean,
|
showAccentBadge: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,10 @@ fun TokenListItem(state: TokensListItemUM, isBalanceHidden: Boolean, modifier: M
|
||||||
@Composable
|
@Composable
|
||||||
fun PortfolioListItem(state: TokensListItemUM.Portfolio, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
fun PortfolioListItem(state: TokensListItemUM.Portfolio, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
||||||
if (state.isExpanded) {
|
if (state.isExpanded) {
|
||||||
ExpandedPortfolioHeader(state.state, modifier)
|
ExpandedPortfolioHeader(state = state.tokenItemUM, isCollapsable = state.isCollapsable, modifier = modifier)
|
||||||
} else {
|
} else {
|
||||||
TokenItem(
|
TokenItem(
|
||||||
state = state.state,
|
state = state.tokenItemUM,
|
||||||
isBalanceHidden = isBalanceHidden,
|
isBalanceHidden = isBalanceHidden,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
|
@ -83,7 +83,7 @@ fun PortfolioTokensListItem(state: PortfolioTokensListItemUM, isBalanceHidden: B
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExpandedPortfolioHeader(state: TokenItemState, modifier: Modifier = Modifier) {
|
private fun ExpandedPortfolioHeader(state: TokenItemState, isCollapsable: Boolean, modifier: Modifier = Modifier) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
@ -130,11 +130,13 @@ private fun ExpandedPortfolioHeader(state: TokenItemState, modifier: Modifier =
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Icon(
|
if (isCollapsable) {
|
||||||
modifier = Modifier.size(TangemTheme.dimens.size16),
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_minimize_24),
|
modifier = Modifier.size(TangemTheme.dimens.size16),
|
||||||
tint = TangemTheme.colors.icon.inactive,
|
painter = painterResource(id = R.drawable.ic_minimize_24),
|
||||||
contentDescription = null,
|
tint = TangemTheme.colors.icon.inactive,
|
||||||
)
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
|
||||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
/** Tokens list item state */
|
/** Tokens list item state */
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
@ -41,11 +42,12 @@ sealed interface TokensListItemUM {
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Portfolio(
|
data class Portfolio(
|
||||||
val state: TokenItemState,
|
val tokenItemUM: TokenItemState,
|
||||||
val isExpanded: Boolean,
|
val isExpanded: Boolean,
|
||||||
val tokens: List<PortfolioTokensListItemUM>,
|
val isCollapsable: Boolean,
|
||||||
|
val tokens: ImmutableList<PortfolioTokensListItemUM>,
|
||||||
) : TokensListItemUM {
|
) : TokensListItemUM {
|
||||||
override val id: String = state.id
|
override val id: String = tokenItemUM.id
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Text(override val id: Any, val text: TextReference) : TokensListItemUM
|
data class Text(override val id: Any, val text: TextReference) : TokensListItemUM
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.tangem.core.ui.decompose
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
interface ComposableListContentComponent<T> {
|
||||||
|
|
||||||
|
val uiState: StateFlow<T>
|
||||||
|
|
||||||
|
fun LazyListScope.content(uiState: T, modifier: Modifier)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EMPTY = EmptyComposableListContentComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object EmptyComposableListContentComponent : ComposableListContentComponent<Unit> {
|
||||||
|
override val uiState: StateFlow<Unit> = MutableStateFlow(Unit)
|
||||||
|
|
||||||
|
override fun LazyListScope.content(uiState: Unit, modifier: Modifier) { /* no-op */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -90,12 +90,14 @@ fun getActiveIconRes(blockchainId: String): Int {
|
||||||
"bitrock", "bitrock/test" -> R.drawable.img_bitrock_22
|
"bitrock", "bitrock/test" -> R.drawable.img_bitrock_22
|
||||||
"sonic", "sonic/test" -> R.drawable.img_sonic_22
|
"sonic", "sonic/test" -> R.drawable.img_sonic_22
|
||||||
"apechain", "apechain/test" -> R.drawable.img_apecoin_22
|
"apechain", "apechain/test" -> R.drawable.img_apecoin_22
|
||||||
"scroll", "scroll/test" -> R.drawable.ic_alert_24 // FIXME: add icon during full integration
|
"scroll", "scroll/test" -> R.drawable.img_scroll_22
|
||||||
"zklink", "zklink/test" -> R.drawable.img_zklink_22
|
"zklink", "zklink/test" -> R.drawable.img_zklink_22
|
||||||
"vanar-chain", "vanar-chain/test" -> R.drawable.img_vanar_22
|
"vanar-chain", "vanar-chain/test" -> R.drawable.img_vanar_22
|
||||||
"pepecoin", "pepecoin/test" -> R.drawable.img_pepecoin_22
|
"pepecoin", "pepecoin/test" -> R.drawable.img_pepecoin_22
|
||||||
"hyperliquid", "hyperliquid/test" -> R.drawable.img_hyperliquid_22
|
"hyperliquid", "hyperliquid/test" -> R.drawable.img_hyperliquid_22
|
||||||
"quai", "quai/test" -> R.drawable.img_quai_22
|
"quai", "quai/test" -> R.drawable.img_quai_22
|
||||||
|
"linea", "linea/test" -> R.drawable.img_linea_22
|
||||||
|
"arbitrum-nova" -> R.drawable.img_arbitrum_nova_22
|
||||||
else -> R.drawable.ic_alert_24
|
else -> R.drawable.ic_alert_24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,12 +186,14 @@ fun getActiveIconResByCoinId(coinId: String): Int {
|
||||||
"bitrock", "bitrock/test" -> R.drawable.img_bitrock_22
|
"bitrock", "bitrock/test" -> R.drawable.img_bitrock_22
|
||||||
"sonic", "sonic/test" -> R.drawable.img_sonic_22
|
"sonic", "sonic/test" -> R.drawable.img_sonic_22
|
||||||
"apechain", "apechain/test" -> R.drawable.img_apecoin_22
|
"apechain", "apechain/test" -> R.drawable.img_apecoin_22
|
||||||
"scroll", "scroll/test" -> R.drawable.ic_alert_24 // FIXME: add icon during full integration
|
"scroll", "scroll/test" -> R.drawable.img_scroll_22
|
||||||
"zklink", "zklink/test" -> R.drawable.img_zklink_22
|
"zklink", "zklink/test" -> R.drawable.img_zklink_22
|
||||||
"vanar-chain", "vanar-chain/test" -> R.drawable.img_vanar_22
|
"vanar-chain", "vanar-chain/test" -> R.drawable.img_vanar_22
|
||||||
"pepecoin-network", "pepecoin-network/test" -> R.drawable.img_pepecoin_22
|
"pepecoin-network", "pepecoin-network/test" -> R.drawable.img_pepecoin_22
|
||||||
"hyperliquid", "hyperliquid/test" -> R.drawable.img_hyperliquid_22
|
"hyperliquid", "hyperliquid/test" -> R.drawable.img_hyperliquid_22
|
||||||
"quai", "quai/test" -> R.drawable.img_quai_22
|
"quai", "quai/test" -> R.drawable.img_quai_22
|
||||||
|
"linea", "linea/test" -> R.drawable.img_linea_22
|
||||||
|
"arbitrum-nova" -> R.drawable.img_arbitrum_nova_22
|
||||||
else -> R.drawable.ic_alert_24
|
else -> R.drawable.ic_alert_24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -281,12 +285,14 @@ fun getGreyedOutIconRes(blockchainId: String): Int {
|
||||||
"bitrock", "bitrock/test" -> R.drawable.ic_bitrock_22
|
"bitrock", "bitrock/test" -> R.drawable.ic_bitrock_22
|
||||||
"sonic", "sonic/test" -> R.drawable.ic_sonic_22
|
"sonic", "sonic/test" -> R.drawable.ic_sonic_22
|
||||||
"apechain", "apechain/test" -> R.drawable.ic_apecoin_22
|
"apechain", "apechain/test" -> R.drawable.ic_apecoin_22
|
||||||
"scroll", "scroll/test" -> R.drawable.ic_alert_24 // FIXME: add icon during full integration
|
"scroll", "scroll/test" -> R.drawable.ic_scroll_22
|
||||||
"zklink", "zklink/test" -> R.drawable.ic_zklink_22
|
"zklink", "zklink/test" -> R.drawable.ic_zklink_22
|
||||||
"vanar-chain", "vanar-chain/test" -> R.drawable.ic_vanar_22
|
"vanar-chain", "vanar-chain/test" -> R.drawable.ic_vanar_22
|
||||||
"pepecoin", "pepecoin/test" -> R.drawable.ic_pepecoin_22
|
"pepecoin", "pepecoin/test" -> R.drawable.ic_pepecoin_22
|
||||||
"hyperliquid", "hyperliquid/test" -> R.drawable.ic_hyperliquid_22
|
"hyperliquid", "hyperliquid/test" -> R.drawable.ic_hyperliquid_22
|
||||||
"quai", "quai/test" -> R.drawable.ic_quai_22
|
"quai", "quai/test" -> R.drawable.ic_quai_22
|
||||||
|
"linea", "linea/test" -> R.drawable.ic_linea_22
|
||||||
|
"arbitrum-nova" -> R.drawable.ic_arbitrum_nova_22
|
||||||
else -> R.drawable.ic_alert_24
|
else -> R.drawable.ic_alert_24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,6 +13,8 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import com.tangem.core.ui.UiDependencies
|
import com.tangem.core.ui.UiDependencies
|
||||||
|
import com.tangem.core.ui.components.LocalSystemBarsIconsController
|
||||||
|
import com.tangem.core.ui.components.SystemBarsIconsController
|
||||||
import com.tangem.core.ui.components.TangemShimmer
|
import com.tangem.core.ui.components.TangemShimmer
|
||||||
import com.tangem.core.ui.components.text.BladeAnimation
|
import com.tangem.core.ui.components.text.BladeAnimation
|
||||||
import com.tangem.core.ui.components.text.rememberBladeAnimation
|
import com.tangem.core.ui.components.text.rememberBladeAnimation
|
||||||
|
|
@ -65,6 +67,8 @@ fun TangemTheme(
|
||||||
val themeColors = if (isDark) darkThemeColors() else lightThemeColors()
|
val themeColors = if (isDark) darkThemeColors() else lightThemeColors()
|
||||||
val rememberedColors = remember { themeColors }
|
val rememberedColors = remember { themeColors }
|
||||||
.also { it.update(themeColors) }
|
.also { it.update(themeColors) }
|
||||||
|
val systemUiController = rememberSystemUiController()
|
||||||
|
val systemBarsIconsController = remember(systemUiController) { SystemBarsIconsController(systemUiController) }
|
||||||
|
|
||||||
val shapes = remember { TangemShapes(dimens) }
|
val shapes = remember { TangemShapes(dimens) }
|
||||||
|
|
||||||
|
|
@ -103,6 +107,7 @@ fun TangemTheme(
|
||||||
LocalEventMessageHandler provides eventMessageHandler,
|
LocalEventMessageHandler provides eventMessageHandler,
|
||||||
LocalWindowSize provides windowSize,
|
LocalWindowSize provides windowSize,
|
||||||
LocalBladeAnimation provides rememberBladeAnimation(),
|
LocalBladeAnimation provides rememberBladeAnimation(),
|
||||||
|
LocalSystemBarsIconsController provides systemBarsIconsController,
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalTangemShimmer provides TangemShimmer,
|
LocalTangemShimmer provides TangemShimmer,
|
||||||
|
|
@ -119,6 +124,14 @@ fun TangemTheme(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ForceDarkTheme(content: @Composable () -> Unit) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalTangemColors provides darkThemeColors(),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
object TangemTheme {
|
object TangemTheme {
|
||||||
val colors: TangemColors
|
val colors: TangemColors
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
package com.tangem.core.ui.test
|
package com.tangem.core.ui.test
|
||||||
|
|
||||||
object BaseBottomSheetTestTags {
|
object BaseBottomSheetTestTags {
|
||||||
const val ACTION_TITLE = "BASE_BOTTOM_SHEET_ACTION_TITLE"
|
const val ACTION_BUTTON = "BASE_BOTTOM_SHEET_ACTION_BUTTON"
|
||||||
|
const val ACTION_ICON = "BASE_BOTTOM_SHEET_ACTION_ICON"
|
||||||
const val TITLE = "BASE_BOTTOM_SHEET_TITLE"
|
const val TITLE = "BASE_BOTTOM_SHEET_TITLE"
|
||||||
const val SUBTITLE = "BASE_BOTTOM_SHEET_SUBTITLE"
|
const val SUBTITLE = "BASE_BOTTOM_SHEET_SUBTITLE"
|
||||||
const val CLOSE_BUTTON = "BASE_BOTTOM_SHEET_CLOSE_BUTTON"
|
const val CLOSE_BUTTON = "BASE_BOTTOM_SHEET_CLOSE_BUTTON"
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,11 @@ object SendScreenTestTags {
|
||||||
const val SCREEN_CONTAINER = "SEND_SCREEN_CONTAINER"
|
const val SCREEN_CONTAINER = "SEND_SCREEN_CONTAINER"
|
||||||
|
|
||||||
const val AMOUNT_CONTAINER_TITLE = "SEND_SCREEN_AMOUNT_CONTAINER_TITLE"
|
const val AMOUNT_CONTAINER_TITLE = "SEND_SCREEN_AMOUNT_CONTAINER_TITLE"
|
||||||
const val AMOUNT_CONTAINER_TEXT = "SEND_SCREEN_AMOUNT_CONTAINER_TEXT"
|
|
||||||
const val INPUT_TEXT_FIELD = "SEND_SCREEN_INPUT_TEXT_FIELD"
|
const val INPUT_TEXT_FIELD = "SEND_SCREEN_INPUT_TEXT_FIELD"
|
||||||
|
const val TOKEN_NAME = "SEND_SCREEN_TOKEN_NAME"
|
||||||
|
const val PRIMARY_AMOUNT = "SEND_SCREEN_PRIMARY_AMOUNT"
|
||||||
const val SECONDARY_AMOUNT = "SEND_SCREEN_SECONDARY_AMOUNT"
|
const val SECONDARY_AMOUNT = "SEND_SCREEN_SECONDARY_AMOUNT"
|
||||||
|
|
||||||
const val CURRENCY_BUTTON = "SEND_SCREEN_CURRENCY_BUTTON"
|
const val MAX_BUTTON = "SEND_SCREEN_MAX_BUTTON"
|
||||||
const val FIAT_ICON = "SEND_SCREEN_FIAT_ICON"
|
|
||||||
const val CURRENCY_ICON = "SEND_SCREEN_CURRENCY_ICON"
|
|
||||||
const val MAX_BUTTON = "END_SCREEN_MAX_BUTTON"
|
|
||||||
const val PREVIOUS_BUTTON = "SEND_SCREEN_PREVIOUS_BUTTON"
|
const val PREVIOUS_BUTTON = "SEND_SCREEN_PREVIOUS_BUTTON"
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +9,6 @@ object SwapTokenScreenTestTags {
|
||||||
const val PROVIDERS_BLOCK = "SWAP_TOKEN_SCREEN_PROVIDERS_BLOCK"
|
const val PROVIDERS_BLOCK = "SWAP_TOKEN_SCREEN_PROVIDERS_BLOCK"
|
||||||
const val SWAP_BUTTON = "SWAP_TOKEN_SCREEN_SWAP_BUTTON"
|
const val SWAP_BUTTON = "SWAP_TOKEN_SCREEN_SWAP_BUTTON"
|
||||||
const val TOKEN = "SWAP_TOKEN_SCREEN_TOKEN"
|
const val TOKEN = "SWAP_TOKEN_SCREEN_TOKEN"
|
||||||
const val TOKEN_NAME = "SWAP_TOKEN_SCREEN_TOKEN_NAME"
|
const val TOKEN_SYMBOL = "SWAP_TOKEN_SCREEN_TOKEN_SYMBOL"
|
||||||
const val TOKEN_ICON = "SWAP_TOKEN_SCREEN_TOKEN_ICON"
|
const val TOKEN_ICON = "SWAP_TOKEN_SCREEN_TOKEN_ICON"
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.tangem.core.ui.test
|
||||||
|
|
||||||
|
object TokenReceiveQrCodeBottomSheetTestTags {
|
||||||
|
const val TITLE = "TOKEN_RECEIVE_QR_CODE_BOTTOM_SHEET_TITLE"
|
||||||
|
const val QR_CODE = "TOKEN_RECEIVE_QR_CODE_BOTTOM_SHEET_QR_CODE"
|
||||||
|
const val ADDRESS = "TOKEN_RECEIVE_QR_CODE_BOTTOM_SHEET_ADDRESS"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.tangem.core.ui.test
|
||||||
|
|
||||||
|
object TokenReceiveWarningBottomSheetTestTags {
|
||||||
|
const val BOTTOM_SHEET = "TOKEN_RECEIVE_WARNING_BOTTOM_SHEET"
|
||||||
|
}
|
||||||
|
|
@ -7,4 +7,5 @@ object WalletConnectScreenTestTags {
|
||||||
const val APP_NAME = "WALLET_CONNECT_SCREEN_APP_NAME"
|
const val APP_NAME = "WALLET_CONNECT_SCREEN_APP_NAME"
|
||||||
const val APPROVE_ICON = "WALLET_CONNECT_SCREEN_APPROVE_ICON"
|
const val APPROVE_ICON = "WALLET_CONNECT_SCREEN_APPROVE_ICON"
|
||||||
const val APP_URL = "WALLET_CONNECT_SCREEN_APP_URL"
|
const val APP_URL = "WALLET_CONNECT_SCREEN_APP_URL"
|
||||||
|
const val WALLET_CONNECT_IMAGE = "WALLET_CONNECT_SCREEN_WALLET_CONNECT_IMAGE"
|
||||||
}
|
}
|
||||||
BIN
core/ui/src/main/res/drawable-hdpi/img_hardware_wallet.webp
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
core/ui/src/main/res/drawable-hdpi/img_mobile_wallet.webp
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
core/ui/src/main/res/drawable-mdpi/img_hardware_wallet.webp
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
core/ui/src/main/res/drawable-mdpi/img_mobile_wallet.webp
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
core/ui/src/main/res/drawable-xhdpi/img_hardware_wallet.webp
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
core/ui/src/main/res/drawable-xhdpi/img_mobile_wallet.webp
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
core/ui/src/main/res/drawable-xxhdpi/img_hardware_wallet.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
core/ui/src/main/res/drawable-xxhdpi/img_mobile_wallet.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
core/ui/src/main/res/drawable-xxxhdpi/img_hardware_wallet.webp
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
core/ui/src/main/res/drawable-xxxhdpi/img_mobile_wallet.webp
Normal file
|
After Width: | Height: | Size: 128 KiB |
10
core/ui/src/main/res/drawable/ic_add_wallet_16.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportWidth="16"
|
||||||
|
android:viewportHeight="16">
|
||||||
|
<path
|
||||||
|
android:pathData="M11.413,2.426C12.757,2.426 13.43,2.426 13.943,2.688C14.395,2.918 14.762,3.285 14.992,3.736C15.254,4.25 15.253,4.922 15.253,6.266V9.736C15.253,11.079 15.254,11.752 14.992,12.265C14.762,12.716 14.395,13.084 13.943,13.314C13.43,13.575 12.757,13.575 11.413,13.575H4.587C3.243,13.575 2.57,13.575 2.057,13.314C1.605,13.084 1.238,12.716 1.008,12.265C0.746,11.752 0.746,11.079 0.746,9.736V6.266C0.746,4.922 0.746,4.25 1.008,3.736C1.238,3.285 1.605,2.918 2.057,2.688C2.57,2.426 3.243,2.426 4.587,2.426H11.413ZM8,5.063C7.876,5.063 7.814,5.063 7.763,5.073C7.551,5.115 7.386,5.281 7.344,5.492C7.334,5.544 7.333,5.606 7.333,5.729V7.334H5.729C5.606,7.334 5.544,7.334 5.492,7.344C5.281,7.386 5.115,7.551 5.073,7.763C5.063,7.815 5.063,7.877 5.063,8.001C5.063,8.124 5.063,8.186 5.073,8.238C5.115,8.449 5.281,8.615 5.492,8.657C5.544,8.667 5.606,8.667 5.729,8.667H7.333V10.271C7.333,10.394 7.334,10.456 7.344,10.508C7.386,10.719 7.551,10.885 7.763,10.927C7.814,10.937 7.876,10.938 8,10.938C8.124,10.938 8.186,10.937 8.238,10.927C8.449,10.885 8.615,10.719 8.657,10.508C8.667,10.456 8.667,10.394 8.667,10.271V8.667H10.27C10.394,8.667 10.456,8.667 10.508,8.657C10.719,8.615 10.885,8.449 10.927,8.238C10.937,8.186 10.937,8.124 10.937,8.001C10.937,7.877 10.937,7.815 10.927,7.763C10.885,7.551 10.719,7.386 10.508,7.344C10.456,7.333 10.394,7.334 10.27,7.334H8.667V5.729C8.667,5.606 8.667,5.544 8.657,5.492C8.615,5.281 8.449,5.115 8.238,5.073C8.186,5.063 8.124,5.063 8,5.063Z"
|
||||||
|
android:fillColor="#0099FF"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
9
core/ui/src/main/res/drawable/ic_arbitrum_nova_22.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="22dp"
|
||||||
|
android:height="22dp"
|
||||||
|
android:viewportWidth="22"
|
||||||
|
android:viewportHeight="22">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M8.857,6H7.575C7.486,6 7.407,6.058 7.377,6.146L4.006,15.851C3.981,15.924 4.032,16 4.105,16H5.388C5.476,16 5.555,15.942 5.586,15.855L8.956,6.149C8.981,6.076 8.93,6 8.857,6ZM10.416,10.067C10.383,9.97 10.252,9.97 10.218,10.067L9.554,11.981C9.537,12.03 9.537,12.084 9.554,12.133L10.846,15.854C10.876,15.942 10.955,16 11.044,16H12.326C12.399,16 12.45,15.924 12.425,15.851L10.416,10.067ZM13.685,11.941C13.719,12.038 13.849,12.038 13.883,11.941L15.894,6.149C15.919,6.076 15.868,6 15.795,6H14.513C14.424,6 14.345,6.058 14.315,6.146L13.02,9.874C13.003,9.923 13.003,9.977 13.02,10.026L13.685,11.941ZM11.157,6.144C11.127,6.058 11.048,6 10.96,6H9.674C9.585,6 9.506,6.058 9.476,6.145L6.106,15.851C6.081,15.924 6.132,16 6.205,16H7.487C7.576,16 7.655,15.942 7.685,15.855L10.265,8.423C10.282,8.374 10.348,8.374 10.365,8.423L12.945,15.855C12.976,15.942 13.055,16 13.143,16H14.425C14.499,16 14.55,15.924 14.524,15.851L11.157,6.144ZM17.895,6H16.612C16.524,6 16.445,6.058 16.414,6.145L14.07,12.898C14.053,12.946 14.053,13 14.07,13.049L14.735,14.964C14.768,15.061 14.899,15.061 14.933,14.964L17.253,8.28L17.993,6.149C18.019,6.076 17.968,6 17.895,6Z" />
|
||||||
|
</vector>
|
||||||
9
core/ui/src/main/res/drawable/ic_chevron_right_18x24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="19dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="19"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12.632,12.113C12.632,12.331 12.552,12.511 12.382,12.682L8.287,16.687C8.148,16.825 7.984,16.889 7.787,16.889C7.388,16.889 7.063,16.575 7.063,16.176C7.063,15.979 7.149,15.798 7.292,15.655L10.93,12.118L7.292,8.57C7.149,8.427 7.063,8.251 7.063,8.049C7.063,7.65 7.388,7.331 7.787,7.331C7.984,7.331 8.148,7.4 8.287,7.538L12.382,11.543C12.558,11.714 12.632,11.894 12.632,12.113Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
</vector>
|
||||||
9
core/ui/src/main/res/drawable/ic_flash_16.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="17dp"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportWidth="17"
|
||||||
|
android:viewportHeight="16">
|
||||||
|
<path
|
||||||
|
android:pathData="M10.49,1.932C10.866,2.174 11.072,2.631 10.988,3.121L10.323,7.004C10.312,7.067 10.355,7.127 10.418,7.138L12.198,7.443C12.706,7.53 12.997,7.933 13.079,8.318C13.162,8.702 13.063,9.161 12.713,9.481L7.814,13.946C7.422,14.304 6.898,14.317 6.51,14.067C6.135,13.825 5.928,13.368 6.012,12.878L6.677,8.995C6.688,8.932 6.646,8.872 6.582,8.861L4.803,8.556C4.295,8.469 4.003,8.066 3.921,7.681C3.839,7.297 3.937,6.838 4.288,6.519L9.186,2.053C9.579,1.695 10.102,1.682 10.49,1.932Z"
|
||||||
|
android:fillColor="#0099FF"/>
|
||||||
|
</vector>
|
||||||