Updated on 2026-08-14
This commit is contained in:
commit
9aacdb2ed3
695 changed files with 22022 additions and 5116 deletions
|
|
@ -113,12 +113,14 @@ dependencies {
|
|||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.core)
|
||||
api(projects.domain.common)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.demo.models)
|
||||
implementation(projects.domain.express)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.settings)
|
||||
|
|
|
|||
|
|
@ -4,8 +4,26 @@ object TestConstants {
|
|||
const val TOTAL_BALANCE = "$3,299.18"
|
||||
|
||||
const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"
|
||||
const val ETHEREUM_ADDRESS = "0x9FFd974772bDA94D288240C1B22F367Ce75CcD7f"
|
||||
const val ETHEREUM_RECIPIENT_ADDRESS = "0x5aa711F440Eb6d4361148bBD89d03464628ace84"
|
||||
const val ETHEREUM_RECIPIENT_SHORTENED_ADDRESS = "0x5aa711F440Eb6d43...89d03464628ace84"
|
||||
const val BITCOIN_ADDRESS = "bc1qtg9aa6jcpqtvun0pe0uct7sxm8nq2nsxfmfxm3"
|
||||
const val CARDANO_ADDRESS = "addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p"
|
||||
const val CARDANO_ADDRESS =
|
||||
"addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p"
|
||||
const val SOLANA_RECIPIENT_ADDRESS = "5fcy9woa8Di1QHcce65CsV3XKrxdB2pD4HJx5xx82ipM"
|
||||
const val POLKADOT_RECIPIENT_ADDRESS = "143TfgFYAFfM86LRzt4UcFNU3KosxCndBCVz2U5HCxpLidKZ"
|
||||
const val XRP_RECIPIENT_ADDRESS = "rNeY28BPda6jp5N5oESZzd2ZN7eMZy8jNf"
|
||||
const val ENS_NAME = "louded.eth"
|
||||
const val ENS_ETHEREUM_RECIPIENT_ADDRESS = "0x0211ff638298adcbdc99c177dc2e95ec69948865"
|
||||
const val ENS_ETHEREUM_RECIPIENT_SHORTENED_ADDRESS = "0x0211ff63829..."
|
||||
const val KUSAMA_RECIPIENT_ADDRESS = "CqNrR92Hh76vW69vDBL5iATrZoYkk9nj67iVSUbb2YHtktn"
|
||||
const val AZERO_RECIPIENT_ADDRESS = "5EA4p6DZdbt2vLZySML2dG3ZsnNrenEWZHnVCScQh4iq2KZo"
|
||||
const val TEZOS_RECIPIENT_ADDRESS = "tz1eBdC2JkU2bxgZssweLo6D3wCkWN12ioHW"
|
||||
const val KASPA_RECIPIENT_ADDRESS = "kaspa:qypc4aywf95rken57kclwdjvycugnacstrh5uzmu0a7pjtxas366ktgj5jzu8t6"
|
||||
const val XLM_NON_ACTIVATED_RECIPIENT_ADDRESS = "GAGMMENFDWASIHSVO4BPVIT3ZH3YNUIM4EJ6MUDJW46OVPNOJSQIJ22K"
|
||||
const val XLM_ACTIVATED_RECIPIENT_ADDRESS = "GDKGS3UQFUNQY34P4SIAOKKGEA3NKHH6BSAXK4HIN3BZQQRBTITJLKL6"
|
||||
const val XRP_NON_ACTIVATED_RECIPIENT_ADDRESS = "rvJfSnN6JzV3rhz1RRKDHnE6MYW28BaZG"
|
||||
const val XRP_ACTIVATED_RECIPIENT_ADDRESS = "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"
|
||||
|
||||
const val WAIT_UNTIL_TIMEOUT = 20_000L
|
||||
const val WAIT_UNTIL_TIMEOUT_LONG = 30_000L
|
||||
|
|
@ -14,4 +32,7 @@ object TestConstants {
|
|||
|
||||
const val ALLURE_LABEL_NAME = "Owner"
|
||||
const val ALLURE_LABEL_VALUE = "Kaspresso"
|
||||
|
||||
const val USER_TOKENS_API_SCENARIO = "user_tokens_api"
|
||||
const val QUOTES_API_SCENARIO = "quotes_api"
|
||||
}
|
||||
|
|
@ -20,12 +20,12 @@ fun BaseTestCase.swipeVertical(
|
|||
)
|
||||
}
|
||||
|
||||
fun BaseTestCase.pullToRefresh() {
|
||||
fun BaseTestCase.pullToRefresh(steps: Int = 1000) {
|
||||
swipeVertical(
|
||||
direction = SwipeDirection.DOWN,
|
||||
startHeightRatio = 0.2f,
|
||||
endHeightRatio = 0.8f,
|
||||
steps = 1000
|
||||
steps = steps
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,4 +115,28 @@ fun BaseTestCase.checkMultiCurrencyMainScreen(
|
|||
step("Assert 'Organize tokens' button is displayed") {
|
||||
onMainScreen { organizeTokensButton().assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.assertActionButtonsForMultiCurrencyWallet(isEnabled: Boolean = true) {
|
||||
if (isEnabled) {
|
||||
step("Assert 'Buy' button is enabled") {
|
||||
onMainScreen { buyButton.assertIsEnabled() }
|
||||
}
|
||||
step("Assert 'Swap' button is enabled") {
|
||||
onMainScreen { swapButton.assertIsEnabled() }
|
||||
}
|
||||
step("Assert 'Sell' button is enabled") {
|
||||
onMainScreen { sellButton.assertIsEnabled() }
|
||||
}
|
||||
} else {
|
||||
step("Assert 'Buy' button is not enabled") {
|
||||
onMainScreen { buyButton.assertIsNotEnabled() }
|
||||
}
|
||||
step("Assert 'Swap' button is not enabled") {
|
||||
onMainScreen { swapButton.assertIsNotEnabled() }
|
||||
}
|
||||
step("Assert 'Sell' button is not enabled") {
|
||||
onMainScreen { sellButton.assertIsNotEnabled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.screens.AlreadyUsedWalletDialogPageObject.requestSupportButton
|
|||
import com.tangem.screens.AlreadyUsedWalletDialogPageObject.thisIsMyWalletButton
|
||||
import com.tangem.screens.AlreadyUsedWalletDialogPageObject.title
|
||||
import com.tangem.screens.ScanWarningDialogPageObject
|
||||
import com.tangem.screens.onActionIsUnavailableDialog
|
||||
import com.tangem.screens.onFailedTransactionDialog
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
|
|
@ -63,4 +64,16 @@ fun checkAlreadyUsedWalletDialog() {
|
|||
step("Assert 'Request support' button is displayed") {
|
||||
AlreadyUsedWalletDialogPageObject { requestSupportButton.isDisplayed() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.checkActionIsUnavailableDialog() {
|
||||
step("Assert 'Action is unavailable' dialog title is displayed") {
|
||||
onActionIsUnavailableDialog { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Action is unavailable' dialog text is displayed") {
|
||||
onActionIsUnavailableDialog { text.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Action is unavailable' dialog 'Ok' button is displayed") {
|
||||
onActionIsUnavailableDialog { okButton.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.screens.onReceiveAssetsBottomSheet
|
||||
import com.tangem.screens.onTokenReceiveQrCodeBottomSheet
|
||||
import com.tangem.screens.onTokenReceiveWarningBottomSheet
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.goToQrCodeBottomSheet() {
|
||||
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() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.checkQrCodeBottomSheetScenario() {
|
||||
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() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onSendConfirmScreen
|
||||
import com.tangem.screens.onSendScreen
|
||||
import com.tangem.screens.onTokenDetailsScreen
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.checkNetworkFeeBlock(currentFeeAmount: String, withFeeSelector: Boolean) {
|
||||
step("Assert fee selector block is displayed") {
|
||||
onSendConfirmScreen { feeSelectorBlock.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fee selector icon is displayed") {
|
||||
onSendConfirmScreen { feeSelectorIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fee selector title is displayed") {
|
||||
onSendConfirmScreen { feeSelectorTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fee selector tooltip icon is displayed") {
|
||||
onSendConfirmScreen { feeSelectorTooltipIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert fee amount = '$currentFeeAmount'") {
|
||||
onSendConfirmScreen { feeAmount.assertTextContains(currentFeeAmount) }
|
||||
}
|
||||
if (withFeeSelector) {
|
||||
step("Assert select fee icon is displayed") {
|
||||
onSendConfirmScreen { selectFeeIcon.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.openSendConfirmScreen(
|
||||
tokenName: String,
|
||||
inputAmount: String,
|
||||
recipientAddress: String
|
||||
) {
|
||||
step("Click on token with name: '$tokenName'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton().performClick() }
|
||||
}
|
||||
step("Type '$inputAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(inputAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type recipient address") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(recipientAddress) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package com.tangem.scenarios
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onSendConfirmScreen
|
||||
import com.tangem.screens.onTokenDetailsScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
fun BaseTestCase.openSendScreen(tokenName: String, mockState: String = "") {
|
||||
val scenarioState = mockState.ifEmpty { tokenName }
|
||||
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = scenarioState)
|
||||
}
|
||||
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = scenarioState)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenName'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton().performClick() }
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseTestCase.checkSendWarning(
|
||||
title: String,
|
||||
message: String,
|
||||
isDisplayed: Boolean = true,
|
||||
sendButtonIsDisabled: Boolean = isDisplayed,
|
||||
) {
|
||||
val assertDisplay = if (isDisplayed) "displayed" else "not displayed"
|
||||
|
||||
step("Assert 'Send confirm screen' is displayed") {
|
||||
onSendConfirmScreen {
|
||||
appBarTitle.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
step("Assert warning title is $assertDisplay") {
|
||||
onSendConfirmScreen {
|
||||
warningTitle(title).assertVisibility(isDisplayed)
|
||||
}
|
||||
}
|
||||
step("Assert warning icon is $assertDisplay") {
|
||||
onSendConfirmScreen {
|
||||
sendWarningIcon(message).assertVisibility(isDisplayed)
|
||||
}
|
||||
}
|
||||
step("Assert warning message is $assertDisplay") {
|
||||
onSendConfirmScreen {
|
||||
sendWarningMessage(message).assertVisibility(isDisplayed)
|
||||
}
|
||||
}
|
||||
if (sendButtonIsDisabled)
|
||||
step("Assert 'Send' button is disabled") {
|
||||
onSendConfirmScreen {
|
||||
sendButton.assertIsNotEnabled()
|
||||
}
|
||||
}
|
||||
else
|
||||
step("Assert 'Send' button is enabled") {
|
||||
onSendConfirmScreen {
|
||||
sendButton.assertIsEnabled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KNode.assertVisibility(shouldBeDisplayed: Boolean) {
|
||||
if (shouldBeDisplayed) {
|
||||
assertIsDisplayed()
|
||||
} else {
|
||||
assertIsNotDisplayed()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
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.BaseDialogTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import com.tangem.common.ui.R as CommonUIR
|
||||
|
||||
class ActionIsUnavailableDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<ActionIsUnavailableDialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val dialogContainer: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.CONTAINER)
|
||||
}
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TITLE)
|
||||
hasText(getResourceString(CommonUIR.string.action_buttons_something_wrong_alert_title))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val text: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TEXT)
|
||||
hasText(getResourceString(CommonUIR.string.action_buttons_something_wrong_alert_message))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val okButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_ok))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onActionIsUnavailableDialog(function: ActionIsUnavailableDialogPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -17,6 +17,14 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
hasTestTag(BaseDialogTestTags.CONTAINER)
|
||||
}
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TITLE)
|
||||
}
|
||||
|
||||
val text: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TEXT)
|
||||
}
|
||||
|
||||
val cancelButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_cancel))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
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.BaseDialogTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import com.tangem.common.ui.R as CommonUIR
|
||||
|
||||
class OperationIsUnavailableDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<OperationIsUnavailableDialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val text: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TEXT)
|
||||
hasText(getResourceString(CommonUIR.string.token_button_unavailability_generic_description))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val okButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_ok))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onOperationIsUnavailableDialog(function: OperationIsUnavailableDialogPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
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.*
|
||||
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 SellPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SellPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.common_sell))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSellScreen(function: SellPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -9,18 +9,94 @@ 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
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
|
||||
class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SendAddressPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val addressTextFieldTitle: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val addressTextField: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val addressPasteButton: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.ADDRESS_PASTE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val clearTextFieldButton: KNode = child {
|
||||
hasContentDescription(getResourceString(CoreUiR.string.common_close))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val resolvedAddress: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.RESOLVED_ADDRESS)
|
||||
}
|
||||
|
||||
val recentAddressesTitle: KNode = child {
|
||||
hasText(getResourceString(CoreUiR.string.send_recent_transactions))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun destinationTagBlockTitle(isMemoCorrectOrEmpty: Boolean = true): KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.DESTINATION_TAG_TEXT_FIELD_TITLE)
|
||||
useUnmergedTree = true
|
||||
if(isMemoCorrectOrEmpty) {
|
||||
hasText(getResourceString(CoreUiR.string.send_destination_tag_field))
|
||||
} else {
|
||||
hasText(getResourceString(CoreUiR.string.send_memo_destination_tag_error))
|
||||
}
|
||||
}
|
||||
|
||||
val destinationTagTextField: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.DESTINATION_TAG_TEXT_FIELD)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val destinationTagBlockText: KNode = child {
|
||||
hasText(
|
||||
getResourceString(CoreUiR.string.send_recipient_memo_footer_v2) + "\n" +
|
||||
getResourceString(CoreUiR.string.send_recipient_memo_footer_v2_highlighted)
|
||||
)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val destinationTagBlockCaution: KNode = child {
|
||||
hasText(getResourceString(CoreUiR.string.send_recipient_memo_footer_v2_highlighted))
|
||||
}
|
||||
|
||||
val destinationTagPasteButton: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.DESTINATION_TAG_PASTE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val clearDestinationTagTextFieldButton: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.DESTINATION_TAG_CLEAR_TEXT_FIELD_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val nextButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyDescendant(withText(getResourceString(R.string.common_next)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val continueButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.common_next))
|
||||
hasText(getResourceString(R.string.common_continue))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun recentAddressWithText(recipientAddress: String): KNode = child {
|
||||
hasParent(withTestTag(SendAddressScreenTestTags.RECENT_ADDRESS_TITLE))
|
||||
hasText(recipientAddress)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,36 +2,55 @@ 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.NotificationTestTags
|
||||
import com.tangem.core.ui.test.SendConfirmScreenTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import com.tangem.core.ui.test.*
|
||||
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
|
||||
import androidx.compose.ui.test.hasText as withText
|
||||
import com.tangem.common.ui.R as CommonUiR
|
||||
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
|
||||
class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SendConfirmPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
val appBarTitle: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val sendButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.common_send))
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyDescendant(withText(getResourceString(R.string.common_send)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val primaryAmount: KNode = child {
|
||||
hasTestTag(BaseAmountBlockTestTags.PRIMARY_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val minimumSendAmountErrorTitle: KNode = child {
|
||||
val secondaryAmount: KNode = child {
|
||||
hasTestTag(BaseAmountBlockTestTags.SECONDARY_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun leaveDepositButton(amount: String): KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyDescendant(withText(getResourceString(R.string.send_notification_leave_button, amount)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun reduceAmountButton(amount: String): KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyDescendant(withText(getResourceString(R.string.send_notification_reduce_by, amount)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun warningTitle(title: String): KNode = child {
|
||||
hasTestTag(NotificationTestTags.TITLE)
|
||||
hasText(getResourceString(CommonUiR.string.send_notification_invalid_amount_title))
|
||||
hasText(title)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
|
|
@ -40,34 +59,76 @@ class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
|
|||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun minimumSendAmountErrorIcon(amount: String): KNode = child {
|
||||
hasAnySibling(
|
||||
withText(
|
||||
getResourceString(
|
||||
CommonUiR.string.send_notification_invalid_minimum_amount_text,
|
||||
amount,
|
||||
amount,
|
||||
)
|
||||
)
|
||||
)
|
||||
fun sendWarningIcon(message: String): KNode = child {
|
||||
hasTestTag(NotificationTestTags.ICON)
|
||||
hasAnySibling(withText(message))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun minimumSendAmountErrorMessage(
|
||||
amount: String,
|
||||
fun sendWarningMessage(
|
||||
message: String,
|
||||
): KNode = child {
|
||||
hasTestTag(NotificationTestTags.MESSAGE)
|
||||
hasText(
|
||||
getResourceString(
|
||||
CommonUiR.string.send_notification_invalid_minimum_amount_text,
|
||||
amount,
|
||||
amount,
|
||||
)
|
||||
)
|
||||
hasText(message)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun warningMessage(messageResId: Int): KNode = child {
|
||||
hasTestTag(NotificationTestTags.MESSAGE)
|
||||
hasText(getResourceString(messageResId))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun warningIcon(message: String): KNode = child {
|
||||
hasTestTag(NotificationTestTags.ICON)
|
||||
hasAnySibling(withText(message))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun recipientAddress(recipientAddress: String): KNode = child {
|
||||
hasTestTag(SendConfirmScreenTestTags.RECIPIENT_ADDRESS)
|
||||
hasText(recipientAddress)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val blockchainAddress: KNode = child {
|
||||
hasTestTag(SendConfirmScreenTestTags.BLOCKCHAIN_ADDRESS)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val feeSelectorBlock: KNode = child {
|
||||
hasTestTag(FeeSelectorBlockTestTags.SELECTOR_BLOCK)
|
||||
}
|
||||
|
||||
val feeSelectorIcon: KNode = child {
|
||||
hasTestTag(FeeSelectorBlockTestTags.ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val feeSelectorTitle: KNode = child {
|
||||
hasTestTag(FeeSelectorBlockTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val feeSelectorTooltipIcon: KNode = child {
|
||||
hasTestTag(FeeSelectorBlockTestTags.TOOLTIP_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val selectFeeIcon: KNode = child {
|
||||
hasTestTag(FeeSelectorBlockTestTags.SELECT_FEE_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val feeAmount: KNode = child {
|
||||
hasParent(withTestTag(FeeSelectorBlockTestTags.FEE_AMOUNT))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val refreshButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(CoreUiR.string.warning_button_refresh))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSendConfirmScreen(function: SendConfirmPageObject.() -> Unit) =
|
||||
|
|
|
|||
|
|
@ -38,6 +38,16 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val equivalentInputAmount: KNode = child {
|
||||
hasTestTag(SendScreenTestTags.EQUIVALENT_INPUT_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val exchangeIcon: KNode = child {
|
||||
hasTestTag(SendScreenTestTags.EXCHANGE_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val tokenName: KNode = child {
|
||||
hasTestTag(SendScreenTestTags.TOKEN_NAME)
|
||||
useUnmergedTree = true
|
||||
|
|
@ -64,6 +74,12 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val continueButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(SendR.string.common_continue))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSendScreen(function: SendPageObject.() -> Unit) =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
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.BaseDialogTestTags
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import com.tangem.common.ui.R as CommonUIR
|
||||
|
||||
class SwapIsNotSupportedDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SwapIsNotSupportedDialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
fun text(currencyName: String): KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TEXT)
|
||||
hasText(
|
||||
getResourceString(
|
||||
CommonUIR.string.token_button_unavailability_reason_not_exchangeable,
|
||||
currencyName
|
||||
)
|
||||
)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val okButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasText(getResourceString(R.string.common_ok))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onSwapIsNotSupportedDialog(function: SwapIsNotSupportedDialogPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -97,25 +97,31 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
|
|||
)
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
val swapButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
fun receiveButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_receive))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
fun swapButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_swap))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
val sellButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
fun sellButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_sell))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
val buyButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
fun buyButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_buy))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
val sendButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
fun sendButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_send))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,23 @@ package com.tangem.tests
|
|||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.CARDANO_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.pullToRefresh
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.checkSendWarning
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.openSendScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onSendAddressScreen
|
||||
import com.tangem.screens.onSendConfirmScreen
|
||||
import com.tangem.screens.onSendScreen
|
||||
import com.tangem.screens.onTokenDetailsScreen
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
|
@ -26,28 +35,18 @@ class BlockchainTest : BaseTestCase() {
|
|||
val validSendAmount = "10"
|
||||
val minAmount = "ADA 1.00"
|
||||
val address = CARDANO_ADDRESS
|
||||
val scenarioName = "user_tokens_api"
|
||||
val scenarioState = "Cardano"
|
||||
|
||||
val invalidAmountTitle = getResourceString(R.string.send_notification_invalid_amount_title)
|
||||
val invalidAmountMessage =
|
||||
getResourceString(R.string.send_notification_invalid_minimum_amount_text, minAmount, minAmount)
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenName'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton.performClick() }
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Type '$errorSendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
|
|
@ -64,47 +63,30 @@ class BlockchainTest : BaseTestCase() {
|
|||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid amount' error title is displayed") {
|
||||
onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsDisplayed() }
|
||||
step("Assert 'Invalid amount warning' is displayed") {
|
||||
checkSendWarning(
|
||||
title = invalidAmountTitle,
|
||||
message = invalidAmountMessage,
|
||||
)
|
||||
}
|
||||
step("Assert 'Invalid amount' error icon is displayed") {
|
||||
onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Invalid amount' error message is displayed") {
|
||||
onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsDisplayed() }
|
||||
}
|
||||
step("Press system 'Back' button") {
|
||||
device.uiDevice.pressBack()
|
||||
}
|
||||
step("Assert address text field is displayed") {
|
||||
onSendAddressScreen { addressTextField.assertIsDisplayed() }
|
||||
}
|
||||
step("Press system 'Back' button") {
|
||||
device.uiDevice.pressBack()
|
||||
step("Click on 'Amount' field") {
|
||||
onSendConfirmScreen { primaryAmount.clickWithAssertion() }
|
||||
}
|
||||
step("Type '$validSendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(errorSendAmount)
|
||||
amountInputTextField.performTextReplacement(validSendAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
step("Click on 'Continue' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert address text field is displayed") {
|
||||
onSendAddressScreen { addressTextField.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid amount' error title is not displayed") {
|
||||
onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Assert 'Invalid amount' error icon is not displayed") {
|
||||
onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsNotDisplayed() }
|
||||
}
|
||||
step("Assert 'Invalid amount' error message is not displayed") {
|
||||
onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsNotDisplayed() }
|
||||
step("Assert 'Invalid amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = invalidAmountTitle,
|
||||
message = invalidAmountMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,10 +118,16 @@ class BlockchainTest : BaseTestCase() {
|
|||
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||
}
|
||||
step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoErrorState'") {
|
||||
setWireMockScenarioState(scenarioName = rippleAccountInfoScenarioName, state = rippleAccountInfoErrorState)
|
||||
setWireMockScenarioState(
|
||||
scenarioName = rippleAccountInfoScenarioName,
|
||||
state = rippleAccountInfoErrorState
|
||||
)
|
||||
}
|
||||
step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesErrorState'") {
|
||||
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesErrorState)
|
||||
setWireMockScenarioState(
|
||||
scenarioName = rippleAccountLinesScenarioName,
|
||||
state = rippleAccountLinesErrorState
|
||||
)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
|
|
@ -169,10 +157,16 @@ class BlockchainTest : BaseTestCase() {
|
|||
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||
}
|
||||
step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoStartedState'") {
|
||||
setWireMockScenarioState(scenarioName = rippleAccountInfoScenarioName, state = rippleAccountInfoStartedState)
|
||||
setWireMockScenarioState(
|
||||
scenarioName = rippleAccountInfoScenarioName,
|
||||
state = rippleAccountInfoStartedState
|
||||
)
|
||||
}
|
||||
step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesStartedState'") {
|
||||
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesStartedState)
|
||||
setWireMockScenarioState(
|
||||
scenarioName = rippleAccountLinesScenarioName,
|
||||
state = rippleAccountLinesStartedState
|
||||
)
|
||||
}
|
||||
step("Pull to refresh") {
|
||||
pullToRefresh()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
|||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.Allure
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
|
@ -73,7 +72,7 @@ class FeedbackTest : BaseTestCase() {
|
|||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||
}
|
||||
step("Click 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton.performClick() }
|
||||
onTokenDetailsScreen { sendButton().performClick() }
|
||||
}
|
||||
step("Type '$sendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
|
|
@ -130,7 +129,7 @@ class FeedbackTest : BaseTestCase() {
|
|||
MockProvider.resetEmulateError()
|
||||
}
|
||||
).run {
|
||||
Allure.step("Click on 'Accept' button") {
|
||||
step("Click on 'Accept' button") {
|
||||
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
||||
}
|
||||
step("Set scanning error") {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class SwapTokenTest : BaseTestCase() {
|
|||
onTokenDetailsScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { swapButton.performClick() }
|
||||
onTokenDetailsScreen { swapButton().performClick() }
|
||||
}
|
||||
step("Close 'Stories' screen") {
|
||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||
|
|
@ -131,7 +131,7 @@ class SwapTokenTest : BaseTestCase() {
|
|||
disableMobileData()
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { swapButton.performClick() }
|
||||
onTokenDetailsScreen { swapButton().performClick() }
|
||||
}
|
||||
step("Close 'Stories' screen") {
|
||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||
|
|
@ -175,7 +175,7 @@ class SwapTokenTest : BaseTestCase() {
|
|||
onTokenDetailsScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { swapButton.performClick() }
|
||||
onTokenDetailsScreen { swapButton().performClick() }
|
||||
}
|
||||
step("Close 'Stories' screen") {
|
||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||
|
|
|
|||
|
|
@ -2,14 +2,19 @@ package com.tangem.tests.actionButtons
|
|||
|
||||
import androidx.compose.ui.test.longClick
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.annotations.ApiEnv
|
||||
import com.tangem.common.annotations.ApiEnvConfig
|
||||
import com.tangem.common.constants.TestConstants.BITCOIN_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||
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.common.extensions.*
|
||||
import com.tangem.common.utils.*
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironment
|
||||
import com.tangem.scenarios.*
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||
import com.tangem.tap.domain.sdk.mocks.content.TwinsMockContent
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
|
|
@ -281,41 +286,18 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
|||
step("Click on 'Receive' button") {
|
||||
onTokenActionsBottomSheet { receiveButton.performClick() }
|
||||
}
|
||||
step("Assert 'Token receive warning' bottom sheet is displayed") {
|
||||
waitForIdle()
|
||||
step("Go to QR code bottom sheet") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
onTokenReceiveWarningBottomSheet {
|
||||
bottomSheet.assertIsDisplayed()
|
||||
}
|
||||
goToQrCodeBottomSheet()
|
||||
}
|
||||
}
|
||||
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() }
|
||||
step("Check QR code bottom sheet") {
|
||||
checkQrCodeBottomSheetScenario()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiEnv(ApiEnvConfig(ApiConfig.ID.MoonPay, ApiEnvironment.PROD))
|
||||
@AllureId("85")
|
||||
@DisplayName("Action buttons (long tap): check 'Sell' button")
|
||||
@Test
|
||||
|
|
@ -342,10 +324,10 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
|||
}
|
||||
}
|
||||
}
|
||||
step("Assert 'Receive' button is displayed") {
|
||||
step("Assert 'Sell' button is displayed") {
|
||||
onTokenActionsBottomSheet { sellButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Receive' button") {
|
||||
step("Click on 'Sell' button") {
|
||||
onTokenActionsBottomSheet { sellButton.performClick() }
|
||||
}
|
||||
step("Assert Chrome Browser is opened") {
|
||||
|
|
@ -391,4 +373,258 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("895")
|
||||
@DisplayName("Action buttons: check blockchain information by click on 'Buy' button")
|
||||
@Test
|
||||
fun checkClickOnBuyButtonOnMainTest() {
|
||||
val cardType: MockContent = TwinsMockContent
|
||||
val cardName = "Twin"
|
||||
val tokenTitle = "Bitcoin"
|
||||
val tokenSymbol = "BTC"
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen' on '$cardName' card") {
|
||||
openMainScreen(mockContent = cardType, isTwinsCard = true)
|
||||
}
|
||||
step("Assert 'Buy' button is displayed") {
|
||||
onMainScreen { buyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { 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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiEnv(ApiEnvConfig(ApiConfig.ID.MoonPay, ApiEnvironment.PROD))
|
||||
@AllureId("4395")
|
||||
@DisplayName("Action buttons (main screen): click on buttons with success response")
|
||||
@Test
|
||||
fun clickOnActionButtonsWithSuccessResponseTest() {
|
||||
val tokenTitle = "Ethereum"
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Assert 'Buy' button is displayed") {
|
||||
onMainScreen { buyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.performClick() }
|
||||
}
|
||||
step("Assert 'Buy' screen title is displayed") {
|
||||
onBuyTokenScreen { topAppBarTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert token with title: '$tokenTitle' is displayed") {
|
||||
onBuyTokenScreen { tokenWithTitleAndFiatAmount(tokenTitle).assertIsDisplayed() }
|
||||
}
|
||||
step("Press 'Back' button") {
|
||||
device.uiDevice.pressBack()
|
||||
}
|
||||
step("Assert 'Swap' button is displayed") {
|
||||
onMainScreen { swapButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onMainScreen { swapButton.performClick() }
|
||||
}
|
||||
step("Click on close button on stories screen") {
|
||||
onSwapStoriesScreen { closeButton.performClick() }
|
||||
}
|
||||
step("Assert 'Swap' token screen title is displayed") {
|
||||
onSwapTokenScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Press 'Back' button") {
|
||||
device.uiDevice.pressBack()
|
||||
}
|
||||
step("Assert 'Sell' button is displayed") {
|
||||
onMainScreen { sellButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Sell' button") {
|
||||
onMainScreen { sellButton.performClick() }
|
||||
}
|
||||
step("Assert 'Sell' token screen title is displayed") {
|
||||
onSellScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4396")
|
||||
@DisplayName("Action buttons (main screen): click on buttons without data")
|
||||
@Test
|
||||
fun clickOnActionButtonsWithoutDataTest() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
enableWiFi()
|
||||
enableMobileData()
|
||||
}
|
||||
).run {
|
||||
step("Turn off internet") {
|
||||
disableWiFi()
|
||||
disableMobileData()
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Assert 'Buy' button is displayed") {
|
||||
onMainScreen { buyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.performClick() }
|
||||
}
|
||||
step("Check 'Action is unavailable' dialog") {
|
||||
checkActionIsUnavailableDialog()
|
||||
}
|
||||
step("Click on 'Ok' button") {
|
||||
onDialog { okButton.performClick() }
|
||||
}
|
||||
step("Assert 'Swap' button is displayed") {
|
||||
onMainScreen { swapButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onMainScreen { swapButton.performClick() }
|
||||
}
|
||||
step("Check 'Action is unavailable' dialog") {
|
||||
checkActionIsUnavailableDialog()
|
||||
}
|
||||
step("Click on 'Ok' button") {
|
||||
onDialog { okButton.performClick() }
|
||||
}
|
||||
step("Assert 'Sell' button is displayed") {
|
||||
onMainScreen { sellButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Sell' button") {
|
||||
onMainScreen { sellButton.performClick() }
|
||||
}
|
||||
step("Check 'Action is unavailable' dialog") {
|
||||
checkActionIsUnavailableDialog()
|
||||
}
|
||||
step("Click on 'Ok' button") {
|
||||
onDialog { okButton.performClick() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4398")
|
||||
@DisplayName("Action buttons (main screen): click on buttons with error response")
|
||||
@Test
|
||||
fun clickOnActionButtonsWithErrorResponseTest() {
|
||||
val scenarioName = "express_api_assets"
|
||||
val scenarioState = "Error"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName, scenarioState)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Assert 'Buy' button is displayed") {
|
||||
onMainScreen { buyButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Buy' button") {
|
||||
onMainScreen { buyButton.performClick() }
|
||||
}
|
||||
step("Check 'Action is unavailable' dialog") {
|
||||
checkActionIsUnavailableDialog()
|
||||
}
|
||||
step("Click on 'Ok' button") {
|
||||
onDialog { okButton.performClick() }
|
||||
}
|
||||
step("Assert 'Swap' button is displayed") {
|
||||
onMainScreen { swapButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onMainScreen { swapButton.performClick() }
|
||||
}
|
||||
step("Check 'Action is unavailable' dialog") {
|
||||
checkActionIsUnavailableDialog()
|
||||
}
|
||||
step("Click on 'Ok' button") {
|
||||
onDialog { okButton.performClick() }
|
||||
}
|
||||
step("Assert 'Sell' button is displayed") {
|
||||
onMainScreen { sellButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Sell' button") {
|
||||
onMainScreen { sellButton.performClick() }
|
||||
}
|
||||
step("Check 'Action is unavailable' dialog") {
|
||||
checkActionIsUnavailableDialog()
|
||||
}
|
||||
step("Click on 'Ok' button") {
|
||||
onDialog { okButton.performClick() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3642")
|
||||
@DisplayName("Action buttons (main screen): check buttons state")
|
||||
@Test
|
||||
fun checkButtonsStateTest() {
|
||||
val scenarioName = "user_tokens_api"
|
||||
val scenarioState = "EmptyTokensList"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Assert action buttons is not enabled") {
|
||||
assertActionButtonsForMultiCurrencyWallet(isEnabled = false)
|
||||
}
|
||||
step("Reset Wiremock scenario: '$scenarioName'") {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
step("Perform pull to refresh") {
|
||||
pullToRefresh(steps = 10)
|
||||
waitForIdle()
|
||||
}
|
||||
step("Assert action buttons is enabled") {
|
||||
assertActionButtonsForMultiCurrencyWallet(isEnabled = true)
|
||||
}
|
||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||
}
|
||||
step("Perform pull to refresh") {
|
||||
pullToRefresh(steps = 10)
|
||||
waitForIdle()
|
||||
}
|
||||
step("Assert action buttons is not enabled") {
|
||||
assertActionButtonsForMultiCurrencyWallet(isEnabled = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
package com.tangem.tests.actionButtons
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||
import com.tangem.common.extensions.SwipeDirection
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.swipeVertical
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.checkQrCodeBottomSheetScenario
|
||||
import com.tangem.scenarios.goToQrCodeBottomSheet
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onSwapStoriesScreen
|
||||
import com.tangem.screens.onSwapTokenScreen
|
||||
import com.tangem.screens.onTokenDetailsScreen
|
||||
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 TokenDetailsScreenActionButtonsTest : BaseTestCase() {
|
||||
|
||||
@AllureId("594")
|
||||
@DisplayName("Action buttons (token details screen): validate UI")
|
||||
@Test
|
||||
fun actionButtonsValidateUiTest() {
|
||||
val tokenTitle = "Bitcoin"
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
waitForIdle()
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||
}
|
||||
step("Assert 'Receive' button is displayed") {
|
||||
onTokenDetailsScreen { receiveButton().assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Buy' button is displayed") {
|
||||
onTokenDetailsScreen { buyButton().assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Send' button is displayed") {
|
||||
onTokenDetailsScreen { sendButton().assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Swap' button is displayed") {
|
||||
onTokenDetailsScreen { swapButton().assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Sell' button is displayed") {
|
||||
onTokenDetailsScreen { sellButton().assertIsDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("593")
|
||||
@DisplayName("Action buttons (token details screen): check buttons state")
|
||||
@Test
|
||||
fun checkActionButtonsStateTest() {
|
||||
val tokenTitle = "Bitcoin"
|
||||
val actionButtonIsNotDimmed = "Action button is not dimmed"
|
||||
val actionButtonIsDimmed = "Action button is dimmed"
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
waitForIdle()
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||
}
|
||||
step("Assert 'Receive' button is not dimmed") {
|
||||
onTokenDetailsScreen { receiveButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) }
|
||||
}
|
||||
step("Assert 'Buy' button is not dimmed") {
|
||||
onTokenDetailsScreen { buyButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) }
|
||||
}
|
||||
step("Assert 'Send' button is dimmed") {
|
||||
onTokenDetailsScreen { sendButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||
}
|
||||
step("Assert 'Swap' button is dimmed") {
|
||||
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||
}
|
||||
step("Assert 'Sell' button is dimmed") {
|
||||
onTokenDetailsScreen { sellButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4459")
|
||||
@DisplayName("Action buttons (token details screen): check 'Swap' button (success)")
|
||||
@Test
|
||||
fun checkSwapButtonSuccessTest() {
|
||||
val tokenTitle = "Ethereum"
|
||||
val tokenSymbol = "ETH"
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
waitForIdle()
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { 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("4460")
|
||||
@DisplayName("Action buttons (token details screen): check 'Swap' button (provider error)")
|
||||
@Test
|
||||
fun checkSwapButtonProviderErrorTest() {
|
||||
val tokenTitle = "POL (ex-MATIC)"
|
||||
val actionButtonIsDimmed = "Action button is dimmed"
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Swipe up") {
|
||||
swipeVertical(SwipeDirection.UP)
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
waitForIdle()
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||
}
|
||||
step("Assert 'Swap' button is dimmed") {
|
||||
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { swapButton().performClick() }
|
||||
}
|
||||
step("Assert swapping $tokenTitle is not supported dialog text is displayed") {
|
||||
onSwapIsNotSupportedDialog { text(tokenTitle).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Ok' button is displayed") {
|
||||
onSwapIsNotSupportedDialog { okButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Ok' button") {
|
||||
onSwapIsNotSupportedDialog { okButton.performClick() }
|
||||
}
|
||||
step("Assert 'Swap' button is dimmed") {
|
||||
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4461")
|
||||
@DisplayName("Action buttons (token details screen): check 'Swap' button (Express error)")
|
||||
@Test
|
||||
fun checkSwapButtonExpressErrorTest() {
|
||||
val tokenTitle = "Polygon"
|
||||
val actionButtonIsDimmed = "Action button is dimmed"
|
||||
val scenarioName = "express_api_assets"
|
||||
val scenarioState = "Error"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName, scenarioState)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Swipe up") {
|
||||
swipeVertical(SwipeDirection.UP)
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
waitForIdle()
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||
}
|
||||
step("Assert 'Swap' button is dimmed") {
|
||||
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { swapButton().performClick() }
|
||||
}
|
||||
step("Assert operation is unavailable dialog text is displayed") {
|
||||
onOperationIsUnavailableDialog { text.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Ok' button is displayed") {
|
||||
onOperationIsUnavailableDialog { okButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Ok' button") {
|
||||
onOperationIsUnavailableDialog { okButton.performClick() }
|
||||
}
|
||||
step("Assert 'Swap' button is dimmed") {
|
||||
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("3590")
|
||||
@DisplayName("Action buttons (token details screen): validate UI")
|
||||
@Test
|
||||
fun checkReceiveButtonTest() {
|
||||
val tokenTitle = "Bitcoin"
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenTitle'") {
|
||||
waitForIdle()
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||
}
|
||||
step("Assert 'Receive' button is displayed") {
|
||||
onTokenDetailsScreen { receiveButton().performClick() }
|
||||
}
|
||||
step("Go to QR code bottom sheet") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
goToQrCodeBottomSheet()
|
||||
}
|
||||
}
|
||||
step("Check QR code bottom sheet") {
|
||||
checkQrCodeBottomSheetScenario()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,338 @@
|
|||
package com.tangem.tests.send.addressScreen
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.ENS_ETHEREUM_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.ENS_ETHEREUM_RECIPIENT_SHORTENED_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.ENS_NAME
|
||||
import com.tangem.common.constants.TestConstants.ETHEREUM_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.ETHEREUM_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.ETHEREUM_RECIPIENT_SHORTENED_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.XRP_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.*
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import com.tangem.screens.*
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onSendAddressScreen
|
||||
import com.tangem.screens.onSendScreen
|
||||
import com.tangem.screens.onTokenDetailsScreen
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class SendAddressScreenTest : BaseTestCase() {
|
||||
|
||||
@AllureId("4006")
|
||||
@DisplayName("Send (address screen): check address history")
|
||||
@Test
|
||||
fun sendAddressHistoryTest() {
|
||||
val tokenName = "Ethereum"
|
||||
val sendAmount = "1"
|
||||
val recipientShortenedAddress = ETHEREUM_RECIPIENT_SHORTENED_ADDRESS
|
||||
val recipientAddress = ETHEREUM_RECIPIENT_ADDRESS
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenName'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton().performClick() }
|
||||
}
|
||||
step("Type '$sendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(sendAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on previous address") {
|
||||
onSendAddressScreen { recentAddressWithText(recipientShortenedAddress).clickWithAssertion() }
|
||||
}
|
||||
step("Assert recipient address is displayed") {
|
||||
onSendConfirmScreen { recipientAddress(recipientAddress).assertIsDisplayed() }
|
||||
}
|
||||
step("Press system 'Back' button") {
|
||||
device.uiDevice.pressBack()
|
||||
}
|
||||
step("Assert address text field contains correct recipient address") {
|
||||
onSendAddressScreen { addressTextField.assertTextContains(recipientAddress) }
|
||||
}
|
||||
step("Assert 'Recent' title is displayed") {
|
||||
onSendAddressScreen { recentAddressesTitle.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Assert 'Next' button is enabled") {
|
||||
onSendAddressScreen { nextButton.assertIsEnabled() }
|
||||
}
|
||||
step("Click on 'Cross' button") {
|
||||
onSendAddressScreen { clearTextFieldButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert recipient text field is empty") {
|
||||
onSendAddressScreen { addressTextField.assertTextEquals("") }
|
||||
}
|
||||
step("Assert 'Recent' title is displayed") {
|
||||
onSendAddressScreen { recentAddressesTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Next' button is disabled") {
|
||||
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4004")
|
||||
@DisplayName("Send (address screen): check destination tag")
|
||||
@Test
|
||||
fun sendDestinationTagTest() {
|
||||
val tokenName = "XRP Ledger"
|
||||
val sendAmount = "1"
|
||||
val correctMemo = "123"
|
||||
val invalidMemo = "hz"
|
||||
val xrpRecipientAddress = XRP_RECIPIENT_ADDRESS
|
||||
val userTokensScenarioName = "user_tokens_api"
|
||||
val userTokensScenarioState = "XRP"
|
||||
val context = device.context
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(userTokensScenarioName)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenName'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() }
|
||||
}
|
||||
step("Click on 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton().performClick() }
|
||||
}
|
||||
step("Type '$sendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(sendAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Set clipboard text") {
|
||||
setClipboardText(context, correctMemo)
|
||||
}
|
||||
step("Assert 'Destination Tag' title is displayed") {
|
||||
onSendAddressScreen { destinationTagBlockTitle().assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Destination Tag' text is displayed") {
|
||||
onSendAddressScreen { destinationTagBlockText.assertIsDisplayed() }
|
||||
}
|
||||
step("CLick on 'Paste' button") {
|
||||
onSendAddressScreen { destinationTagPasteButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Next' button is disabled") {
|
||||
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||
}
|
||||
step("Type address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(xrpRecipientAddress) }
|
||||
}
|
||||
step("Assert 'Next' button is enabled") {
|
||||
onSendAddressScreen { nextButton.assertIsEnabled() }
|
||||
}
|
||||
step("Click on 'Clear text field button'") {
|
||||
onSendAddressScreen { clearDestinationTagTextFieldButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type invalid memo in input text field") {
|
||||
onSendAddressScreen { destinationTagTextField.performTextReplacement(invalidMemo) }
|
||||
}
|
||||
step("Assert 'Invalid memo' title is displayed") {
|
||||
onSendAddressScreen { destinationTagBlockTitle(isMemoCorrectOrEmpty = false).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Next' button is disabled") {
|
||||
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4543")
|
||||
@DisplayName("Send (address screen): check address field")
|
||||
@Test
|
||||
fun sendAddressFieldTest() {
|
||||
val tokenName = "Ethereum"
|
||||
val sendAmount = "1"
|
||||
val recipientAddress = ETHEREUM_RECIPIENT_ADDRESS
|
||||
val invalidAddress = "s"
|
||||
val walletAddress = ETHEREUM_ADDRESS
|
||||
val context = device.context
|
||||
val recipient = getResourceString(R.string.send_recipient)
|
||||
val notAValidAddress = getResourceString(R.string.send_recipient_address_error)
|
||||
val sameAsWalletAddress = getResourceString(R.string.send_error_address_same_as_wallet)
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
clearClipboard()
|
||||
}
|
||||
).run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenName'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton().performClick() }
|
||||
}
|
||||
step("Type '$sendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(sendAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Set clipboard text") {
|
||||
setClipboardText(context, recipientAddress)
|
||||
}
|
||||
step("Click on 'Paste' button") {
|
||||
onSendAddressScreen { addressPasteButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert address text field contains correct recipient address") {
|
||||
onSendAddressScreen { addressTextField.assertTextContains(recipientAddress) }
|
||||
}
|
||||
step("Assert address text field title is displayed") {
|
||||
onSendAddressScreen { addressTextFieldTitle.assertTextContains(recipient) }
|
||||
}
|
||||
step("Assert 'Next' button is enabled") {
|
||||
onSendAddressScreen { nextButton.assertIsEnabled() }
|
||||
}
|
||||
step("Set clipboard text") {
|
||||
setClipboardText(context, invalidAddress)
|
||||
}
|
||||
step("Click on 'Cross' button") {
|
||||
onSendAddressScreen { clearTextFieldButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Paste' button") {
|
||||
onSendAddressScreen { addressPasteButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert address text field contains invalid address") {
|
||||
onSendAddressScreen { addressTextField.assertTextContains(invalidAddress) }
|
||||
}
|
||||
step("Assert invalid address text field title is displayed") {
|
||||
onSendAddressScreen { addressTextFieldTitle.assertTextContains(notAValidAddress) }
|
||||
}
|
||||
step("Assert 'Next' button is disabled") {
|
||||
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||
}
|
||||
step("Set clipboard text") {
|
||||
setClipboardText(context, walletAddress)
|
||||
}
|
||||
step("Click on 'Cross' button") {
|
||||
onSendAddressScreen { clearTextFieldButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Paste' button") {
|
||||
onSendAddressScreen { addressPasteButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert address text field contains invalid address") {
|
||||
onSendAddressScreen { addressTextField.assertTextContains(walletAddress) }
|
||||
}
|
||||
step("Assert 'Address is the same as wallet address' error title is displayed") {
|
||||
onSendAddressScreen { addressTextFieldTitle.assertTextContains(sameAsWalletAddress) }
|
||||
}
|
||||
step("Assert 'Next' button is disabled") {
|
||||
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4004")
|
||||
@DisplayName("Send (address screen): check ENS name")
|
||||
@Test
|
||||
fun sendEnsNameTest() {
|
||||
val tokenName = "Ethereum"
|
||||
val sendAmount = "1"
|
||||
val ensName = ENS_NAME
|
||||
val invalidEnsName = "l"
|
||||
val ensAddress = ENS_ETHEREUM_RECIPIENT_ADDRESS
|
||||
val ensShortenedAddress = ENS_ETHEREUM_RECIPIENT_SHORTENED_ADDRESS
|
||||
val scenarioName = "eth_call_api"
|
||||
val scenarioState = "EnsName"
|
||||
val notAValidAddress = getResourceString(R.string.send_recipient_address_error)
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenName'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() }
|
||||
}
|
||||
step("Click on 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton().performClick() }
|
||||
}
|
||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||
}
|
||||
step("Type '$sendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(sendAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type ENS name: '$ensName' in text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(ensName) }
|
||||
}
|
||||
step("Assert ENS address displayed") {
|
||||
onSendAddressScreen { resolvedAddress.assertTextContains(ensAddress, substring = true) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert recipient address is displayed") {
|
||||
onSendConfirmScreen { recipientAddress(ensName).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert blockchain address is displayed") {
|
||||
onSendConfirmScreen { blockchainAddress.assertTextContains(ensShortenedAddress, substring = true) }
|
||||
}
|
||||
step("Click on recipient address") {
|
||||
onSendConfirmScreen { recipientAddress(ensName).performClick() }
|
||||
}
|
||||
step("Click on 'Cross' button") {
|
||||
onSendAddressScreen { clearTextFieldButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type invalid ENS name: '$invalidEnsName' in text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(invalidEnsName) }
|
||||
}
|
||||
step("Assert invalid address error title is displayed") {
|
||||
onSendAddressScreen { addressTextFieldTitle.assertTextContains(notAValidAddress) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,300 @@
|
|||
package com.tangem.tests.send.confirmScreen
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.ETHEREUM_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.POLKADOT_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.extensions.*
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.scenarios.*
|
||||
import com.tangem.screens.*
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class SendConfirmScreenTest : BaseTestCase() {
|
||||
|
||||
@AllureId("4003")
|
||||
@DisplayName("Send (Confirm screen): change sending amount")
|
||||
@Test
|
||||
fun changeSendingAmountTest() {
|
||||
val tokenName = "Ethereum"
|
||||
val inputAmount = "1"
|
||||
val newInputAmount = "0.9"
|
||||
val ethereumAmount = "1.00"
|
||||
val fiatAmount = "$2,535.63"
|
||||
val newFiatAmount = "$2,282.07"
|
||||
val newEthereumAmount = "0.90"
|
||||
val recipientAddress = ETHEREUM_RECIPIENT_ADDRESS
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenName'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton().performClick() }
|
||||
}
|
||||
step("Type '$inputAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(inputAmount)
|
||||
}
|
||||
}
|
||||
step("Assert fiat amount = '$fiatAmount'") {
|
||||
onSendScreen { equivalentInputAmount.assertTextContains(fiatAmount) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type recipient address") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(recipientAddress) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert token amount = '$ethereumAmount'") {
|
||||
onSendConfirmScreen { primaryAmount.assertTextContains(ethereumAmount) }
|
||||
}
|
||||
step("Assert fiat amount = '$fiatAmount'") {
|
||||
onSendConfirmScreen { secondaryAmount.assertTextContains(fiatAmount) }
|
||||
}
|
||||
step("Click on token amount") {
|
||||
onSendConfirmScreen { primaryAmount.performClick() }
|
||||
}
|
||||
step("Clear input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextClearance()
|
||||
}
|
||||
}
|
||||
step("Type '$newInputAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performTextReplacement(newInputAmount)
|
||||
}
|
||||
}
|
||||
step("Assert fiat amount = '$newFiatAmount'") {
|
||||
onSendScreen { equivalentInputAmount.assertTextContains(newFiatAmount) }
|
||||
}
|
||||
step("Click on 'Continue' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert token amount = '$newEthereumAmount'") {
|
||||
onSendConfirmScreen { primaryAmount.assertTextContains(newEthereumAmount) }
|
||||
}
|
||||
step("Assert fiat amount = '$newFiatAmount'") {
|
||||
onSendConfirmScreen { secondaryAmount.assertTextContains(newFiatAmount) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("552")
|
||||
@DisplayName("Send (Confirm screen): switch to equivalent")
|
||||
@Test
|
||||
fun switchToEquivalentTest() {
|
||||
val tokenName = "Ethereum"
|
||||
val inputAmount = "1"
|
||||
val tokenAmount = "1.00"
|
||||
val ethereumAmount = "ETH 1.00"
|
||||
val fiatAmount = "$2,535.63"
|
||||
val recipientAddress = ETHEREUM_RECIPIENT_ADDRESS
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click on token with name: '$tokenName'") {
|
||||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||
}
|
||||
step("Click on 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton().performClick() }
|
||||
}
|
||||
step("Type '$inputAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(inputAmount)
|
||||
}
|
||||
}
|
||||
step("Assert fiat amount = '$fiatAmount'") {
|
||||
onSendScreen { equivalentInputAmount.assertTextContains(fiatAmount) }
|
||||
}
|
||||
step("Click on exchange icon") {
|
||||
onSendScreen { exchangeIcon.performClick() }
|
||||
}
|
||||
step("Assert token amount = '$ethereumAmount'") {
|
||||
onSendScreen { equivalentInputAmount.assertTextContains(ethereumAmount) }
|
||||
}
|
||||
step("Click on exchange icon") {
|
||||
onSendScreen { exchangeIcon.performClick() }
|
||||
}
|
||||
step("Assert fiat amount = '$fiatAmount'") {
|
||||
onSendScreen { equivalentInputAmount.assertTextContains(fiatAmount) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type recipient address") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(recipientAddress) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert primary amount = '$tokenAmount'") {
|
||||
onSendConfirmScreen { primaryAmount.assertTextContains(tokenAmount) }
|
||||
}
|
||||
step("Assert secondary amount = '$fiatAmount'") {
|
||||
onSendConfirmScreen { secondaryAmount.assertTextContains(fiatAmount) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("553")
|
||||
@DisplayName("Send (Confirm screen): check fee for blockchain with unknown fee")
|
||||
@Test
|
||||
fun checkFeeForBlockchainWithUnknownFeeTest() {
|
||||
val tokenName = "Polygon"
|
||||
val inputAmount = "1"
|
||||
val recipientAddress = ETHEREUM_RECIPIENT_ADDRESS
|
||||
val currentFeeAmount = "<$0.01"
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Swipe up to token") {
|
||||
swipeVertical(SwipeDirection.UP)
|
||||
}
|
||||
step("Open 'Send Confirm' screen for token '$tokenName'") {
|
||||
openSendConfirmScreen(
|
||||
tokenName = tokenName,
|
||||
inputAmount = inputAmount,
|
||||
recipientAddress = recipientAddress
|
||||
)
|
||||
}
|
||||
step("Check network fee block") {
|
||||
checkNetworkFeeBlock(currentFeeAmount = currentFeeAmount, withFeeSelector = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4565")
|
||||
@DisplayName("Send (Confirm screen): check fee for token with unknown fee")
|
||||
@Test
|
||||
fun checkFeeForTokenWithUnknownFeeTest() {
|
||||
val tokenName = "POL (ex-MATIC)"
|
||||
val inputAmount = "1"
|
||||
val recipientAddress = ETHEREUM_RECIPIENT_ADDRESS
|
||||
val currentFeeAmount = "<$0.01"
|
||||
val scenarioName = "eth_estimate_gas"
|
||||
val scenarioState = "UnknownFee"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Swipe up to token") {
|
||||
swipeVertical(SwipeDirection.UP)
|
||||
}
|
||||
step("Open 'Send Confirm' screen for token '$tokenName'") {
|
||||
openSendConfirmScreen(
|
||||
tokenName = tokenName,
|
||||
inputAmount = inputAmount,
|
||||
recipientAddress = recipientAddress
|
||||
)
|
||||
}
|
||||
step("Check network fee block") {
|
||||
checkNetworkFeeBlock(currentFeeAmount = currentFeeAmount, withFeeSelector = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("554")
|
||||
@DisplayName("Send (Confirm screen): check fee warning")
|
||||
@Test
|
||||
fun checkFeeWarningTest() {
|
||||
val tokenName = "Polkadot"
|
||||
val tokenAmount = "0.1"
|
||||
val warningTitle = getResourceString(R.string.send_fee_unreachable_error_title)
|
||||
val warningMessageResId = R.string.send_fee_unreachable_error_text
|
||||
val currentFeeAmount = "$0.05"
|
||||
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
enableWiFi()
|
||||
enableMobileData()
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Type '$tokenAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(tokenAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(POLKADOT_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Turn off internet") {
|
||||
disableWiFi()
|
||||
disableMobileData()
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Turn on internet") {
|
||||
enableWiFi()
|
||||
enableMobileData()
|
||||
}
|
||||
step("Assert 'Network fee info unreachable' warning title is displayed") {
|
||||
onSendConfirmScreen { warningTitle(warningTitle).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Check your internet connection' warning message is displayed") {
|
||||
onSendConfirmScreen { warningMessage(warningMessageResId).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert warning icon is displayed") {
|
||||
onSendConfirmScreen { warningIcon(warningTitle).assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Refresh' button") {
|
||||
waitForIdle()
|
||||
onSendConfirmScreen { refreshButton.clickWithAssertion() }
|
||||
}
|
||||
step("Check network fee block") {
|
||||
checkNetworkFeeBlock(currentFeeAmount = currentFeeAmount, withFeeSelector = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package com.tangem.tests.send.warnings
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.AZERO_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.scenarios.checkSendWarning
|
||||
import com.tangem.scenarios.openSendScreen
|
||||
import com.tangem.screens.onSendAddressScreen
|
||||
import com.tangem.screens.onSendConfirmScreen
|
||||
import com.tangem.screens.onSendScreen
|
||||
import com.tangem.wallet.R
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class AzeroWarningsTest : BaseTestCase() {
|
||||
private val tokenName = "Aleph Zero"
|
||||
private val amountToLeaveLessThanDeposit = "0.099876587544"
|
||||
private val amountToLeaveGreaterThanDeposit = "0.09"
|
||||
private val depositAmount = "AZERO 0.0000000005"
|
||||
|
||||
private val warningTitle = getResourceString(R.string.send_notification_existential_deposit_title)
|
||||
private val warningMessage = getResourceString(
|
||||
R.string.send_notification_existential_deposit_text, depositAmount
|
||||
)
|
||||
|
||||
@AllureId("4290")
|
||||
@DisplayName("Warnings: check deposit warnings in Azero")
|
||||
@Test
|
||||
fun checkWarning() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Type '$amountToLeaveLessThanDeposit' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(amountToLeaveLessThanDeposit)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(AZERO_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Existential deposit warning' is displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
)
|
||||
}
|
||||
step("Click on 'Leave $depositAmount' button") {
|
||||
onSendConfirmScreen { leaveDepositButton(depositAmount).clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Existential deposit warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
step("Click on 'Amount' field") {
|
||||
onSendConfirmScreen { primaryAmount.clickWithAssertion() }
|
||||
}
|
||||
step("Type '$amountToLeaveGreaterThanDeposit' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(amountToLeaveGreaterThanDeposit)
|
||||
}
|
||||
}
|
||||
step("Click on 'Continue' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Existential deposit warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package com.tangem.tests.send.warnings
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.KUSAMA_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.scenarios.checkSendWarning
|
||||
import com.tangem.scenarios.openSendScreen
|
||||
import com.tangem.screens.onSendAddressScreen
|
||||
import com.tangem.screens.onSendConfirmScreen
|
||||
import com.tangem.screens.onSendScreen
|
||||
import com.tangem.wallet.R
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class KusamaWarningsTest : BaseTestCase() {
|
||||
private val tokenName = "Kusama"
|
||||
private val amountToLeaveLessThanDeposit = "0.300333"
|
||||
private val amountToLeaveGreaterThanDeposit = "0.1"
|
||||
private val depositAmount = "KSM 0.000333333333"
|
||||
|
||||
private val warningTitle = getResourceString(R.string.send_notification_existential_deposit_title)
|
||||
private val warningMessage = getResourceString(
|
||||
R.string.send_notification_existential_deposit_text, depositAmount
|
||||
)
|
||||
|
||||
@AllureId("4291")
|
||||
@DisplayName("Warnings: check deposit warnings in Kusama")
|
||||
@Test
|
||||
fun checkWarning() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Type '$amountToLeaveLessThanDeposit' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(amountToLeaveLessThanDeposit)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(KUSAMA_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Existential deposit warning' is displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
)
|
||||
}
|
||||
step("Click on 'Leave $depositAmount' button") {
|
||||
onSendConfirmScreen { leaveDepositButton(depositAmount).clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Existential deposit warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
step("Click on 'Amount' field") {
|
||||
onSendConfirmScreen { primaryAmount.clickWithAssertion() }
|
||||
}
|
||||
step("Type '$amountToLeaveGreaterThanDeposit' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(amountToLeaveGreaterThanDeposit)
|
||||
}
|
||||
}
|
||||
step("Click on 'Continue' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Existential deposit warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package com.tangem.tests.send.warnings
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.POLKADOT_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.scenarios.checkSendWarning
|
||||
import com.tangem.scenarios.openSendScreen
|
||||
import com.tangem.screens.onSendAddressScreen
|
||||
import com.tangem.screens.onSendConfirmScreen
|
||||
import com.tangem.screens.onSendScreen
|
||||
import com.tangem.wallet.R
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class PolkadotWarningsTest : BaseTestCase() {
|
||||
private val tokenName = "Polkadot"
|
||||
private val amountToLeaveLessThanDeposit = "1.299"
|
||||
private val amountToLeaveGreaterThanDeposit = "0.2"
|
||||
private val depositAmount = "DOT 1.00"
|
||||
|
||||
private val warningTitle = getResourceString(R.string.send_notification_existential_deposit_title)
|
||||
private val warningMessage = getResourceString(
|
||||
R.string.send_notification_existential_deposit_text, depositAmount
|
||||
)
|
||||
|
||||
@AllureId("4289")
|
||||
@DisplayName("Warnings: check deposit warnings in Polkadot")
|
||||
@Test
|
||||
fun checkWarning() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Type '$amountToLeaveLessThanDeposit' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(amountToLeaveLessThanDeposit)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(POLKADOT_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Existential deposit warning' is displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage
|
||||
)
|
||||
}
|
||||
step("Click on 'Leave $depositAmount' button") {
|
||||
onSendConfirmScreen { leaveDepositButton(depositAmount).clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Existential deposit warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
step("Click on 'Amount' field") {
|
||||
onSendConfirmScreen { primaryAmount.clickWithAssertion() }
|
||||
}
|
||||
step("Type '$amountToLeaveGreaterThanDeposit' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(amountToLeaveGreaterThanDeposit)
|
||||
}
|
||||
}
|
||||
step("Click on 'Continue' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Existential deposit warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
package com.tangem.tests.send.warnings
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.SOLANA_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.scenarios.checkSendWarning
|
||||
import com.tangem.scenarios.openSendScreen
|
||||
import com.tangem.screens.onSendAddressScreen
|
||||
import com.tangem.screens.onSendScreen
|
||||
import com.tangem.wallet.R
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class SolanaWarningsTest : BaseTestCase() {
|
||||
private val tokenName = "Solana"
|
||||
private val amountToLeaveLessThanRent = "0.0016941"
|
||||
private val amountToLeaveGreaterThanRent = "0.0000941"
|
||||
private val amountToLeaveRentOnly = "0.00168934"
|
||||
private val rentAmount = "SOL 0.00089088"
|
||||
|
||||
private val invalidAmountTitle = getResourceString(R.string.send_notification_invalid_amount_title)
|
||||
private val invalidAmountMessage = getResourceString(R.string.send_notification_invalid_amount_rent_fee, rentAmount)
|
||||
|
||||
@AllureId("564")
|
||||
@DisplayName("Warnings: warning is displayed, if after send balance is less than rent amount (SOLANA)")
|
||||
@Test
|
||||
fun warningIsDisplayedWhenLeaveLessThanRent() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Type '$amountToLeaveLessThanRent' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(amountToLeaveLessThanRent)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid amount warning' is displayed") {
|
||||
checkSendWarning(
|
||||
title = invalidAmountTitle,
|
||||
message = invalidAmountMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("567")
|
||||
@DisplayName("Warnings: warning is not displayed, if after send balance is greater than rent amount (SOLANA)")
|
||||
@Test
|
||||
fun warningIsNotDisplayedWhenLeaveGreaterThanRent() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Type '$amountToLeaveGreaterThanRent' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(amountToLeaveGreaterThanRent)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = invalidAmountTitle,
|
||||
message = invalidAmountMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("566")
|
||||
@DisplayName("Warnings: warning is not displayed, if after send balance is equal to rent amount (SOLANA)")
|
||||
@Test
|
||||
fun warningIsNotDisplayedWhenLeaveOnlyRent() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Type '$amountToLeaveRentOnly' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(amountToLeaveRentOnly)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = invalidAmountTitle,
|
||||
message = invalidAmountMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("565")
|
||||
@DisplayName("Warnings: warning is not displayed, if after send balance is zero (SOLANA)")
|
||||
@Test
|
||||
fun warningIsNotDisplayedWhenLeaveZeroSol() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Type max amount in input text field") {
|
||||
onSendScreen {
|
||||
maxButton.performClick()
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = invalidAmountTitle,
|
||||
message = invalidAmountMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
package com.tangem.tests.send.warnings
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.XLM_ACTIVATED_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.XLM_NON_ACTIVATED_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.scenarios.checkSendWarning
|
||||
import com.tangem.scenarios.openSendScreen
|
||||
import com.tangem.screens.onSendAddressScreen
|
||||
import com.tangem.screens.onSendConfirmScreen
|
||||
import com.tangem.screens.onSendScreen
|
||||
import com.tangem.wallet.R
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class StellarWarningsTest : BaseTestCase() {
|
||||
private val tokenName = "Stellar"
|
||||
private val mockStateName = "XLM"
|
||||
private val lessThanReserveAmount = "0.5"
|
||||
private val equalToReserveAmount = "1"
|
||||
private val reserveAmount = "XLM 1.00"
|
||||
private val greaterThanReserveAmount = "2"
|
||||
|
||||
private val warningTitle =
|
||||
getResourceString(R.string.send_notification_invalid_reserve_amount_title, reserveAmount)
|
||||
private val warningMessage = getResourceString(R.string.send_notification_invalid_reserve_amount_text)
|
||||
|
||||
@AllureId("4287")
|
||||
@DisplayName("Warnings: check warning, when sending less than reserve")
|
||||
@Test
|
||||
fun checkWarningWhenSendingLessThanReserve() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName, mockStateName)
|
||||
}
|
||||
step("Type '$lessThanReserveAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(lessThanReserveAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type non activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage
|
||||
)
|
||||
}
|
||||
step("Click on 'Address' field") {
|
||||
onSendConfirmScreen { recipientAddress(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||
}
|
||||
step("Type an activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XLM_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4286")
|
||||
@DisplayName("Warnings: check warning when sending amount equal to reserve")
|
||||
@Test
|
||||
fun checkWarningWhenSendingAmountEqualToReserve() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName, mockStateName)
|
||||
}
|
||||
step("Type '$equalToReserveAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(equalToReserveAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type non activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
step("Click on 'Address' field") {
|
||||
onSendConfirmScreen { recipientAddress(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||
}
|
||||
step("Type an activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XLM_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4288")
|
||||
@DisplayName("Warnings: check warning when sending greater than reserve")
|
||||
@Test
|
||||
fun checkWarningWhenSendingGreaterThanReserve() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName, mockStateName)
|
||||
}
|
||||
step("Type '$greaterThanReserveAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(greaterThanReserveAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type non activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
step("Click on 'Address' field") {
|
||||
onSendConfirmScreen { recipientAddress(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||
}
|
||||
step("Type an activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XLM_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
package com.tangem.tests.send.warnings
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.TEZOS_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.extensions.SwipeDirection
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.swipeVertical
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.scenarios.checkSendWarning
|
||||
import com.tangem.scenarios.openSendScreen
|
||||
import com.tangem.screens.onSendAddressScreen
|
||||
import com.tangem.screens.onSendConfirmScreen
|
||||
import com.tangem.screens.onSendScreen
|
||||
import com.tangem.wallet.R
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class TezosWarningsTest : BaseTestCase() {
|
||||
private val tokenName = "Tezos"
|
||||
private val reduceAmount = "0.000001"
|
||||
private val sendAmount = "0.01"
|
||||
|
||||
private val feeIsHighTitle = getResourceString(R.string.send_notification_high_fee_title)
|
||||
private val feeIsHighMessage =
|
||||
getResourceString(R.string.send_notification_high_fee_text, tokenName, reduceAmount)
|
||||
|
||||
@AllureId("4229")
|
||||
@DisplayName("Warnings: warning is displayed when sending max amount")
|
||||
@Test
|
||||
fun warningIsDisplayedWhenSendMaxAmount() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Click 'Max amount' button") {
|
||||
onSendScreen {
|
||||
maxButton.clickWithAssertion()
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(TEZOS_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Swipe up to see the warning") {
|
||||
swipeVertical(SwipeDirection.UP)
|
||||
}
|
||||
step("Assert 'Fee is high warning' is displayed") {
|
||||
checkSendWarning(
|
||||
title = feeIsHighTitle,
|
||||
message = feeIsHighMessage,
|
||||
sendButtonIsDisabled = false
|
||||
)
|
||||
}
|
||||
step("Click on 'Reduce' button") {
|
||||
onSendConfirmScreen { reduceAmountButton(reduceAmount).clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Fee is high warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = feeIsHighTitle,
|
||||
message = feeIsHighMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4230")
|
||||
@DisplayName("Warnings: warning is not displayed when sending not max amount")
|
||||
@Test
|
||||
fun warningIsNotDisplayedWhenSendNotMaxAmount() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Type '$sendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(sendAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(TEZOS_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Fee is high warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = feeIsHighTitle,
|
||||
message = feeIsHighMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
package com.tangem.tests.send.warnings
|
||||
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||
import com.tangem.common.constants.TestConstants.XRP_ACTIVATED_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.constants.TestConstants.XRP_NON_ACTIVATED_RECIPIENT_ADDRESS
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.scenarios.checkSendWarning
|
||||
import com.tangem.scenarios.openSendScreen
|
||||
import com.tangem.screens.onSendAddressScreen
|
||||
import com.tangem.screens.onSendConfirmScreen
|
||||
import com.tangem.screens.onSendScreen
|
||||
import com.tangem.wallet.R
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
||||
@HiltAndroidTest
|
||||
class XRPWarningsTest : BaseTestCase() {
|
||||
private val tokenName = "XRP Ledger"
|
||||
private val mockStateName = "XRP"
|
||||
private val lessThanReserveAmount = "0.5"
|
||||
private val equalToReserveAmount = "1"
|
||||
private val reserveAmount = "XRP 1.00"
|
||||
private val greaterThanReserveAmount = "2"
|
||||
|
||||
private val warningTitle =
|
||||
getResourceString(R.string.send_notification_invalid_reserve_amount_title, reserveAmount)
|
||||
private val warningMessage = getResourceString(R.string.send_notification_invalid_reserve_amount_text)
|
||||
|
||||
@AllureId("4255")
|
||||
@DisplayName("Warnings: check warning, when sending less than reserve")
|
||||
@Test
|
||||
fun checkWarningWhenSendingLessThanReserve() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName, mockStateName)
|
||||
}
|
||||
step("Type '$lessThanReserveAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(lessThanReserveAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type non activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage
|
||||
)
|
||||
}
|
||||
step("Click on 'Address' field") {
|
||||
onSendConfirmScreen { recipientAddress(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||
}
|
||||
step("Type an activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XRP_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4285")
|
||||
@DisplayName("Warnings: check warning when sending amount equal to reserve")
|
||||
@Test
|
||||
fun checkWarningWhenSendingAmountEqualToReserve() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName, mockStateName)
|
||||
}
|
||||
step("Type '$equalToReserveAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(equalToReserveAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type non activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
step("Click on 'Address' field") {
|
||||
onSendConfirmScreen { recipientAddress(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||
}
|
||||
step("Type an activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XRP_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4284")
|
||||
@DisplayName("Warnings: check warning when sending greater than reserve")
|
||||
@Test
|
||||
fun checkWarningWhenSendingGreaterThanReserve() {
|
||||
setupHooks(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName, mockStateName)
|
||||
}
|
||||
step("Type '$greaterThanReserveAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(greaterThanReserveAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Type non activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
step("Click on 'Address' field") {
|
||||
onSendConfirmScreen { recipientAddress(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||
}
|
||||
step("Type an activated address in input text field") {
|
||||
onSendAddressScreen { addressTextField.performTextReplacement(XRP_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = warningTitle,
|
||||
message = warningMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -310,4 +310,8 @@
|
|||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
<queries>
|
||||
<!-- Needed from Android 11 to open Google Wallet for payment with Visa card -->
|
||||
<package android:name="com.google.android.apps.walletnfcrel" />
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@ package com.tangem.tap.common.analytics
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.utils.AnalyticsContextProxy
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
import com.tangem.tap.common.extensions.addHotWalletContext
|
||||
import com.tangem.tap.common.extensions.eraseContext
|
||||
import com.tangem.tap.common.extensions.removeContext
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.extensions.setHotWalletContext
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -17,6 +20,14 @@ internal class DefaultAnalyticsContextProxy : AnalyticsContextProxy {
|
|||
Analytics.setContext(scanResponse)
|
||||
}
|
||||
|
||||
override fun addContext(userWallet: UserWallet) {
|
||||
Analytics.addContext(userWallet)
|
||||
}
|
||||
|
||||
override fun setHotWalletContext() {
|
||||
Analytics.setHotWalletContext()
|
||||
}
|
||||
|
||||
override fun eraseContext() {
|
||||
Analytics.eraseContext()
|
||||
}
|
||||
|
|
@ -25,6 +36,10 @@ internal class DefaultAnalyticsContextProxy : AnalyticsContextProxy {
|
|||
Analytics.addContext(scanResponse)
|
||||
}
|
||||
|
||||
override fun addHotWalletContext() {
|
||||
Analytics.addHotWalletContext()
|
||||
}
|
||||
|
||||
override fun removeContext() {
|
||||
Analytics.removeContext()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ class BlockchainApiExceptionEvent(
|
|||
selectedHost: String,
|
||||
exceptionHost: String,
|
||||
error: String,
|
||||
blockchain: String,
|
||||
) : AnalyticsEvent(
|
||||
category = "BlockchainSdk",
|
||||
event = "Exception",
|
||||
|
|
@ -13,5 +14,6 @@ class BlockchainApiExceptionEvent(
|
|||
AnalyticsParam.BLOCKCHAIN_SELECTED_HOST to selectedHost,
|
||||
AnalyticsParam.BLOCKCHAIN_EXCEPTION_HOST to exceptionHost,
|
||||
AnalyticsParam.ERROR_DESCRIPTION to error,
|
||||
AnalyticsParam.BLOCKCHAIN to blockchain,
|
||||
),
|
||||
)
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.common.analytics.handlers
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.ExceptionHandlerOutput
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import com.tangem.tap.common.analytics.events.BlockchainApiExceptionEvent
|
||||
import javax.inject.Inject
|
||||
|
|
@ -8,12 +10,13 @@ import javax.inject.Inject
|
|||
class BlockchainExceptionHandler @Inject constructor(
|
||||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
) : ExceptionHandlerOutput {
|
||||
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String) {
|
||||
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String, blockchain: Blockchain) {
|
||||
analyticsErrorHandler.sendErrorEvent(
|
||||
BlockchainApiExceptionEvent(
|
||||
selectedHost = nextHost,
|
||||
exceptionHost = currentHost,
|
||||
error = message,
|
||||
blockchain = blockchain.toNetworkId(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.tap.common.analytics.paramsInterceptor
|
||||
|
||||
import com.tangem.core.analytics.api.ParamsInterceptor
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
|
||||
class HotWalletContextInterceptor(
|
||||
val parent: ParamsInterceptor? = null,
|
||||
) : ParamsInterceptor {
|
||||
|
||||
override fun id(): String = HotWalletContextInterceptor.id()
|
||||
|
||||
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true
|
||||
|
||||
override fun intercept(params: MutableMap<String, String>) {
|
||||
params[AnalyticsParam.PRODUCT_TYPE] = "Mobile Wallet"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun id(): String = HotWalletContextInterceptor::class.java.simpleName
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
*/
|
||||
class LinkedCardContextInterceptor(
|
||||
scanResponse: ScanResponse,
|
||||
val parent: LinkedCardContextInterceptor? = null,
|
||||
val parent: ParamsInterceptor? = null,
|
||||
) : ParamsInterceptor {
|
||||
|
||||
private val contextInterceptor = CardContextInterceptor(scanResponse)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.analytics.Analytics
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.tap.common.analytics.paramsInterceptor.HotWalletContextInterceptor
|
||||
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
|
||||
|
||||
/**
|
||||
|
|
@ -24,19 +25,46 @@ fun Analytics.setContext(scanResponse: ScanResponse) {
|
|||
|
||||
fun Analytics.setContext(userWallet: UserWallet) {
|
||||
setUserId(userWallet.walletId.stringValue)
|
||||
// TODO add product type for hot ([REDACTED_TASK_KEY] [Hot Wallet] Analytics)
|
||||
|
||||
if (userWallet is UserWallet.Cold) {
|
||||
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
|
||||
when (userWallet) {
|
||||
is UserWallet.Cold -> {
|
||||
removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
|
||||
}
|
||||
is UserWallet.Hot -> {
|
||||
removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||
addParamsInterceptor(HotWalletContextInterceptor())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Analytics.setHotWalletContext() {
|
||||
addParamsInterceptor(HotWalletContextInterceptor())
|
||||
}
|
||||
|
||||
/**
|
||||
* Erases the context
|
||||
*/
|
||||
fun Analytics.eraseContext() {
|
||||
clearUserId()
|
||||
removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||
removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new context and keeps a previous context as the parent of the new one
|
||||
*/
|
||||
fun Analytics.addContext(userWallet: UserWallet) {
|
||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||
|
||||
val newContext = when (userWallet) {
|
||||
is UserWallet.Cold -> LinkedCardContextInterceptor(userWallet.scanResponse, parent = currentContext)
|
||||
is UserWallet.Hot -> HotWalletContextInterceptor(parent = currentContext)
|
||||
}
|
||||
|
||||
setUserId(userWalletId = userWallet.walletId.stringValue)
|
||||
addParamsInterceptor(newContext)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -48,18 +76,32 @@ fun Analytics.addContext(scanResponse: ScanResponse) {
|
|||
setUserId(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
|
||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||
val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext)
|
||||
|
||||
addParamsInterceptor(newContext)
|
||||
}
|
||||
|
||||
fun Analytics.addHotWalletContext() {
|
||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
|
||||
val newContext = HotWalletContextInterceptor(currentContext)
|
||||
|
||||
addParamsInterceptor(newContext)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current context and restores the previous one if it was present.
|
||||
*/
|
||||
fun Analytics.removeContext() {
|
||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
|
||||
val previousContext = currentContext?.parent ?: return
|
||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||
|
||||
val previousContext = when (currentContext) {
|
||||
is LinkedCardContextInterceptor -> currentContext.parent
|
||||
is HotWalletContextInterceptor -> currentContext.parent
|
||||
else -> null
|
||||
} ?: return
|
||||
|
||||
addParamsInterceptor(previousContext)
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.Locale
|
||||
|
||||
// TODO: move extensions to utils
|
||||
fun BigDecimal.toFormattedString(
|
||||
decimals: Int,
|
||||
roundingMode: RoundingMode = RoundingMode.DOWN,
|
||||
locale: Locale = Locale.US,
|
||||
): String {
|
||||
val symbols = DecimalFormatSymbols(locale)
|
||||
val df = DecimalFormat()
|
||||
df.decimalFormatSymbols = symbols
|
||||
df.maximumFractionDigits = decimals
|
||||
df.minimumFractionDigits = 0
|
||||
df.isGroupingUsed = true
|
||||
df.roundingMode = roundingMode
|
||||
return df.format(this)
|
||||
}
|
||||
|
|
@ -2,8 +2,13 @@ package com.tangem.tap.data
|
|||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -11,7 +16,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.text.encodeToByteArray
|
||||
|
||||
private const val DEFAULT_KEY = "tangem_pay_default_key"
|
||||
private const val ORDER_ID_KEY = "tangem_pay_order_id_key"
|
||||
|
|
@ -19,7 +23,9 @@ private const val ORDER_ID_KEY = "tangem_pay_order_id_key"
|
|||
@Singleton
|
||||
internal class DefaultTangemPayStorage @Inject constructor(
|
||||
@ApplicationContext applicationContext: Context,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : TangemPayStorage {
|
||||
|
||||
private val secureStorage by lazy {
|
||||
|
|
@ -29,14 +35,21 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
|||
name = "tangem_pay_storage",
|
||||
)
|
||||
}
|
||||
private val moshi by lazy {
|
||||
Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
}
|
||||
|
||||
private val tokensAdapter by lazy { moshi.adapter(VisaAuthTokens::class.java) }
|
||||
|
||||
override suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String) {
|
||||
withContext(dispatcherProvider.io) {
|
||||
secureStorage.store(key = createCustomerAddressKey(userWalletId), value = customerWalletAddress)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String? {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
secureStorage.getAsString(createCustomerAddressKey(userWalletId))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun storeAuthTokens(customerWalletAddress: String, tokens: VisaAuthTokens) =
|
||||
withContext(dispatcherProvider.io) {
|
||||
val json = tokensAdapter.toJson(tokens)
|
||||
|
|
@ -67,14 +80,33 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
|||
secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true)
|
||||
}
|
||||
|
||||
override suspend fun getAddToWalletDone(customerWalletAddress: String): Boolean {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
appPreferencesStore.getSyncOrNull(
|
||||
key = PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress),
|
||||
) == true
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun storeAddToWalletDone(customerWalletAddress: String, isDone: Boolean) {
|
||||
withContext(dispatcherProvider.io) {
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), isDone)
|
||||
}
|
||||
}
|
||||
|
||||
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(createOrderIdKey(customerWalletAddress))
|
||||
}
|
||||
override suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) =
|
||||
withContext(dispatcherProvider.io) {
|
||||
secureStorage.delete(createCustomerAddressKey(userWalletId))
|
||||
secureStorage.delete(createKey(customerWalletAddress))
|
||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), false)
|
||||
}
|
||||
|
||||
private fun createCustomerAddressKey(userWalletId: UserWalletId): String = userWalletId.stringValue
|
||||
|
||||
private fun createKey(address: String): String = "${DEFAULT_KEY}_$address"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
|
||||
import com.tangem.datasource.api.moonpay.MoonPayApi
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
|
||||
import com.tangem.tap.network.exchangeServices.DefaultRampManager
|
||||
import com.tangem.tap.network.exchangeServices.SellService
|
||||
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -43,17 +47,15 @@ internal object ActivityModule {
|
|||
@Singleton
|
||||
fun provideDefaultRampManager(
|
||||
appStateHolder: AppStateHolder,
|
||||
expressServiceLoader: ExpressServiceLoader,
|
||||
expressServiceFetcher: ExpressServiceFetcher,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): RampStateManager {
|
||||
return DefaultRampManager(
|
||||
sellService = Provider { requireNotNull(appStateHolder.sellService) },
|
||||
expressServiceLoader = expressServiceLoader,
|
||||
expressServiceFetcher = expressServiceFetcher,
|
||||
currenciesRepository = currenciesRepository,
|
||||
dispatchers = dispatchers,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -63,4 +65,19 @@ internal object ActivityModule {
|
|||
fun provideActivityDelayedWorkCoroutineScope(): CoroutineScope {
|
||||
return CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideExchangeService(
|
||||
environmentConfigStorage: EnvironmentConfigStorage,
|
||||
getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
moonPayApi: MoonPayApi,
|
||||
): SellService {
|
||||
return MoonPayService(
|
||||
api = moonPayApi,
|
||||
apiKeyProvider = Provider { environmentConfigStorage.getConfigSync().moonPayApiKey },
|
||||
secretKeyProvider = Provider { environmentConfigStorage.getConfigSync().moonPayApiSecretKey },
|
||||
userWalletProvider = { getSelectedWalletUseCase.sync().getOrNull() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
|||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
|
||||
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
|
||||
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
||||
import dagger.Module
|
||||
|
|
@ -27,6 +28,7 @@ internal class TangemSdkManagerModule {
|
|||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
visaCardScanHandler: VisaCardScanHandler,
|
||||
visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||
tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||
onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||
): TangemSdkManager {
|
||||
return if (BuildConfig.MOCK_DATA_SOURCE) {
|
||||
|
|
@ -37,6 +39,7 @@ internal class TangemSdkManagerModule {
|
|||
resources = context.resources,
|
||||
visaCardScanHandler = visaCardScanHandler,
|
||||
visaCardActivationTaskFactory = visaCardActivationTaskFactory,
|
||||
tangemPayChallengeTaskFactory = tangemPayChallengeTaskFactory,
|
||||
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.tangem.tap.di.domain
|
|||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
||||
import com.tangem.domain.account.tokens.MainAccountTokensMigration
|
||||
import com.tangem.domain.account.usecase.*
|
||||
import dagger.Module
|
||||
|
|
@ -50,10 +52,12 @@ internal object AccountDomainModule {
|
|||
fun provideRecoverCryptoPortfolioUseCase(
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
mainAccountTokensMigration: MainAccountTokensMigration,
|
||||
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
||||
): RecoverCryptoPortfolioUseCase {
|
||||
return RecoverCryptoPortfolioUseCase(
|
||||
crudRepository = accountsCRUDRepository,
|
||||
mainAccountTokensMigration = mainAccountTokensMigration,
|
||||
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.nft.*
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
|
|
@ -24,9 +26,13 @@ internal object NFTDomainModule {
|
|||
fun providesGetNFTCollectionsUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
): GetNFTCollectionsUseCase = GetNFTCollectionsUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -60,10 +66,12 @@ internal object NFTDomainModule {
|
|||
@Singleton
|
||||
fun providesGetNFTAvailableNetworksUseCase(
|
||||
nftRepository: NFTRepository,
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
): GetNFTNetworksUseCase = GetNFTNetworksUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -179,20 +179,6 @@ internal object OnrampDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampV2QuotesUseCase(
|
||||
settingsRepository: SettingsRepository,
|
||||
onrampRepository: OnrampRepository,
|
||||
onrampErrorResolver: OnrampErrorResolver,
|
||||
): GetOnrampV2QuotesUseCase {
|
||||
return GetOnrampV2QuotesUseCase(
|
||||
settingsRepository = settingsRepository,
|
||||
repository = onrampRepository,
|
||||
errorResolver = onrampErrorResolver,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampProviderWithQuoteUseCase(
|
||||
|
|
|
|||
|
|
@ -20,10 +20,7 @@ import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
|||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
|
||||
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
|
||||
import com.tangem.domain.tokens.repository.*
|
||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
|
||||
|
|
@ -59,24 +56,6 @@ internal object TokensDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchTokenListUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): FetchTokenListUseCase {
|
||||
return FetchTokenListUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchPendingTransactionsUseCase(
|
||||
|
|
@ -183,24 +162,6 @@ internal object TokensDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchCardTokenListUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
stakingIdFactory: StakingIdFactory,
|
||||
): FetchCardTokenListUseCase {
|
||||
return FetchCardTokenListUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
stakingIdFactory = stakingIdFactory,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCryptoCurrencyUseCase(
|
||||
|
|
|
|||
|
|
@ -18,13 +18,17 @@ import com.tangem.core.res.getStringSafe
|
|||
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||
import com.tangem.domain.visa.model.VisaActivationInput
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||
import com.tangem.domain.visa.model.sign
|
||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
|
|
@ -44,6 +48,7 @@ import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
|||
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
|
||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||
import com.tangem.tap.domain.tasks.visa.VisaCustomerWalletApproveTask
|
||||
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
|
||||
|
|
@ -62,6 +67,7 @@ internal class DefaultTangemSdkManager(
|
|||
private val resources: Resources,
|
||||
private val visaCardScanHandler: VisaCardScanHandler,
|
||||
private val visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||
private val tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||
) : TangemSdkManager {
|
||||
|
||||
|
|
@ -511,6 +517,18 @@ internal class DefaultTangemSdkManager(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun tangemPayProduceInitialCredentials(
|
||||
cardId: String,
|
||||
): CompletionResult<TangemPayInitialCredentials> {
|
||||
return coroutineScope {
|
||||
runTaskAsyncReturnOnMain(
|
||||
runnable = tangemPayChallengeTaskFactory.create(coroutineScope = this),
|
||||
cardId = cardId,
|
||||
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -213,5 +213,11 @@ class MockTangemSdkManager(
|
|||
error("Not implemented")
|
||||
}
|
||||
|
||||
override suspend fun tangemPayProduceInitialCredentials(
|
||||
cardId: String,
|
||||
): CompletionResult<TangemPayInitialCredentials> {
|
||||
error("Not implemented")
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.card.*
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.card.EncryptionMode
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
|
|
@ -125,10 +129,18 @@ object WalletMockContent : MockContent {
|
|||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
),
|
||||
DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
),
|
||||
DerivationPath("m/44'/144'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
),
|
||||
DerivationPath("m/44'/501'/0'") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
),
|
||||
),
|
||||
extendedPublicKey = ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||
|
|
@ -235,6 +247,20 @@ object WalletMockContent : MockContent {
|
|||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/1729'/0'/0'") to ExtendedPublicKey( // Tezos
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/111111'/0'/0/0") to ExtendedPublicKey( // Kaspa
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
ByteArrayKey(
|
||||
|
|
@ -244,14 +270,49 @@ object WalletMockContent : MockContent {
|
|||
ExtendedPublicKeysMap(
|
||||
mapOf(
|
||||
DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/501'/0'") to ExtendedPublicKey( // Solana
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/354'/0'/0'/0'") to ExtendedPublicKey( // Polkadot
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/434'/0'/0'/0'") to ExtendedPublicKey( // Kusama
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/643'/0'/0'/0'") to ExtendedPublicKey( // Azero
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/148'/0'") to ExtendedPublicKey( // Stellar
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
|
|
@ -296,9 +357,16 @@ object WalletMockContent : MockContent {
|
|||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // xrp
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
DerivationPath("m/44'/1729'/0'/0'") to ExtendedPublicKey( // Tezos
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/111111'/0'/0/0") to ExtendedPublicKey( // Kaspa
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
|
|
@ -312,19 +380,54 @@ object WalletMockContent : MockContent {
|
|||
ExtendedPublicKeysMap(
|
||||
mapOf(
|
||||
DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/501'/0'") to ExtendedPublicKey( // Solana
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/354'/0'/0'/0'") to ExtendedPublicKey( // Polkadot
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/434'/0'/0'/0'") to ExtendedPublicKey( // Kusama
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/643'/0'/0'/0'") to ExtendedPublicKey( // Azero
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/148'/0'") to ExtendedPublicKey( // Stellar
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
// TODO remove it after test after resolve [REDACTED_JIRA]
|
||||
internal class ResetBackupCardTask(
|
||||
private val userWalletId: UserWalletId,
|
||||
) : CardSessionRunnable<Boolean> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.core.error.ext.tangemError
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource
|
||||
import com.tangem.domain.visa.error.VisaActivationError
|
||||
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||
import com.tangem.domain.visa.model.VisaDataToSignByCustomerWallet
|
||||
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||
import com.tangem.domain.visa.model.sign
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class TangemPayGenerateAddressAndSignChallengeTask @AssistedInject constructor(
|
||||
@Assisted private val coroutineScope: CoroutineScope,
|
||||
private val dispatchersProvider: CoroutineDispatcherProvider,
|
||||
private val visaAuthRemoteDataSource: VisaAuthRemoteDataSource,
|
||||
) : CardSessionRunnable<TangemPayInitialCredentials> {
|
||||
|
||||
override fun run(session: CardSession, callback: CompletionCallback<TangemPayInitialCredentials>) {
|
||||
coroutineScope.launch {
|
||||
callback(runSuspend(session = session))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun runSuspend(session: CardSession): CompletionResult<TangemPayInitialCredentials> {
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||
?: return CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError)
|
||||
|
||||
val derivationResult = runDerivationTask(session, wallet)
|
||||
val address = when (derivationResult) {
|
||||
is CompletionResult.Failure<*> -> return CompletionResult.Failure(derivationResult.error)
|
||||
is CompletionResult.Success<ExtendedPublicKey> -> generateAddressFromExtendedKey(derivationResult.data)
|
||||
}
|
||||
|
||||
val challenge = withContext(dispatchersProvider.io) {
|
||||
visaAuthRemoteDataSource.getCustomerWalletAuthChallenge(address)
|
||||
}.getOrElse { return CompletionResult.Failure(it.tangemError) }
|
||||
|
||||
val dataToSign = VisaDataToSignByCustomerWallet(hashToSign = challenge.challenge)
|
||||
val approveResult = runVisaCustomerWalletApproveTask(
|
||||
session = session,
|
||||
cardId = card.cardId,
|
||||
targetAddress = address,
|
||||
dataToSign = dataToSign,
|
||||
)
|
||||
val signedData = when (approveResult) {
|
||||
is CompletionResult.Failure<*> -> return CompletionResult.Failure(approveResult.error)
|
||||
is CompletionResult.Success<VisaSignedDataByCustomerWallet> -> approveResult.data
|
||||
}
|
||||
|
||||
val authTokens = withContext(dispatchersProvider.io) {
|
||||
visaAuthRemoteDataSource.getTokenWithCustomerWallet(
|
||||
sessionId = challenge.session.sessionId,
|
||||
signature = signedData.signature,
|
||||
nonce = signedData.dataToSign.hashToSign,
|
||||
)
|
||||
}.getOrNull() ?: return CompletionResult.Failure(VisaActivationError.FailedRemoteState.tangemError)
|
||||
|
||||
return CompletionResult.Success(
|
||||
data = TangemPayInitialCredentials(
|
||||
customerWalletAddress = address,
|
||||
authTokens = authTokens,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun runDerivationTask(
|
||||
session: CardSession,
|
||||
wallet: CardWallet,
|
||||
): CompletionResult<ExtendedPublicKey> {
|
||||
val deferred = CompletableDeferred<CompletionResult<ExtendedPublicKey>>()
|
||||
val derivationTask = DeriveWalletPublicKeyTask(
|
||||
walletPublicKey = wallet.publicKey,
|
||||
derivationPath = VisaUtilities.customDerivationPath,
|
||||
)
|
||||
|
||||
derivationTask.run(session = session, callback = deferred::complete)
|
||||
return deferred.await()
|
||||
}
|
||||
|
||||
private suspend fun runVisaCustomerWalletApproveTask(
|
||||
session: CardSession,
|
||||
cardId: String,
|
||||
targetAddress: String,
|
||||
dataToSign: VisaDataToSignByCustomerWallet,
|
||||
): CompletionResult<VisaSignedDataByCustomerWallet> {
|
||||
val deferred = CompletableDeferred<CompletionResult<VisaSignedDataByCustomerWallet>>()
|
||||
val task = VisaCustomerWalletApproveTask(
|
||||
visaDataForApprove = VisaCustomerWalletApproveTask.Input(
|
||||
cardId = cardId,
|
||||
targetAddress = targetAddress,
|
||||
hashToSign = dataToSign.hashToSign,
|
||||
sign = dataToSign::sign,
|
||||
),
|
||||
)
|
||||
task.run(session = session, callback = deferred::complete)
|
||||
return deferred.await()
|
||||
}
|
||||
|
||||
private fun generateAddressFromExtendedKey(extendedPublicKey: ExtendedPublicKey): String {
|
||||
val derivationData = VisaUtilities.visaBlockchain.makeAddressesFromExtendedPublicKey(
|
||||
extendedPublicKey = extendedPublicKey,
|
||||
cachedIndex = null,
|
||||
)
|
||||
return derivationData.address
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(coroutineScope: CoroutineScope): TangemPayGenerateAddressAndSignChallengeTask
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ import com.tangem.common.extensions.toHexString
|
|||
import com.tangem.core.error.ext.tangemError
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility
|
||||
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation
|
||||
|
|
@ -59,21 +58,7 @@ class VisaCustomerWalletApproveTask(
|
|||
session: CardSession,
|
||||
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
val cardDTO = CardDTO(card)
|
||||
|
||||
val derivationStyle = cardDTO.derivationStyleProvider.getDerivationStyle() ?: run {
|
||||
proceedApproveWithLegacyCard(
|
||||
card = card,
|
||||
session = session,
|
||||
callback = callback,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val derivationPath = VisaUtilities.visaDefaultDerivationPath(derivationStyle) ?: run {
|
||||
callback(CompletionResult.Failure(VisaActivationError.FailedToCreateAddress.tangemError))
|
||||
return
|
||||
}
|
||||
val derivationPath = VisaUtilities.customDerivationPath
|
||||
|
||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
||||
callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError))
|
||||
|
|
|
|||
|
|
@ -16,14 +16,11 @@ import com.tangem.core.ui.message.BottomSheetMessage
|
|||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase
|
||||
import com.tangem.domain.balancehiding.BalanceHidingSettings
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.notifications.GetApplicationIdUseCase
|
||||
import com.tangem.domain.notifications.SendPushTokenUseCase
|
||||
|
|
@ -42,8 +39,7 @@ import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
|||
import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase
|
||||
import com.tangem.feature.swap.analytics.StoriesEvents
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
|
||||
import com.tangem.tap.network.exchangeServices.SellService
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -83,9 +79,9 @@ internal class MainViewModel @Inject constructor(
|
|||
private val apiConfigsManager: ApiConfigsManager,
|
||||
private val multiQuoteUpdater: MultiQuoteUpdater,
|
||||
private val appStateHolder: AppStateHolder,
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
private val appRouterConfig: AppRouterConfig,
|
||||
private val sellService: SellService,
|
||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -195,22 +191,12 @@ internal class MainViewModel @Inject constructor(
|
|||
|
||||
private fun initializeOffRamp() {
|
||||
viewModelScope.launch {
|
||||
val sellService = makeSellExchangeService(environmentConfig = environmentConfigStorage.getConfigSync())
|
||||
appStateHolder.sellService = sellService
|
||||
|
||||
sellService.update()
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeSellExchangeService(environmentConfig: EnvironmentConfig): ExchangeService {
|
||||
return MoonPayService(
|
||||
apiKey = environmentConfig.moonPayApiKey,
|
||||
secretKey = environmentConfig.moonPayApiSecretKey,
|
||||
isLogEnabled = LogConfig.network.moonPayService,
|
||||
userWalletProvider = { getSelectedWalletUseCase.sync().getOrNull() },
|
||||
)
|
||||
}
|
||||
|
||||
private fun observeFlips() {
|
||||
listenToFlipsUseCase().launchIn(viewModelScope)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,44 +2,14 @@ package com.tangem.tap.network.exchangeServices
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class CryptoCurrencyConverter(
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
) : TwoWayConverter<Currency, CryptoCurrency> {
|
||||
internal object CryptoCurrencyConverter : Converter<CryptoCurrency, Currency> {
|
||||
|
||||
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory(excludedBlockchains) }
|
||||
|
||||
override fun convert(value: Currency): CryptoCurrency {
|
||||
return when (value) {
|
||||
is Currency.Blockchain -> requireNotNull(
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = value.blockchain,
|
||||
extraDerivationPath = value.derivationPath,
|
||||
userWallet = getSelectedWallet(),
|
||||
),
|
||||
)
|
||||
is Currency.Token -> requireNotNull(
|
||||
cryptoCurrencyFactory.createToken(
|
||||
sdkToken = value.token,
|
||||
blockchain = value.blockchain,
|
||||
extraDerivationPath = value.derivationPath,
|
||||
userWallet = getSelectedWallet(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: CryptoCurrency): Currency {
|
||||
override fun convert(value: CryptoCurrency): Currency {
|
||||
val blockchain = value.network.toBlockchain()
|
||||
if (blockchain == Blockchain.Unknown) error("CryptoCurrencyConverter convertBack Unknown blockchain")
|
||||
return when (value) {
|
||||
|
|
@ -60,15 +30,4 @@ internal class CryptoCurrencyConverter(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSelectedWallet(): UserWallet {
|
||||
val userWalletListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
|
||||
val hotWalletFeatureToggles = store.inject(DaggerGraphState::hotWalletFeatureToggles)
|
||||
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
requireNotNull(userWalletsListRepository.selectedUserWallet.value)
|
||||
} else {
|
||||
requireNotNull(userWalletListManager.selectedUserWalletSync)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,13 +5,11 @@ import arrow.core.raise.catch
|
|||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import arrow.core.right
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
|
||||
import com.tangem.datasource.api.express.models.response.Asset
|
||||
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.exchange.ExpressAvailabilityState
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.express.models.ExpressAsset
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -26,17 +24,13 @@ import com.tangem.utils.isNullOrZero
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultRampManager(
|
||||
private val sellService: Provider<ExchangeService>,
|
||||
private val expressServiceLoader: ExpressServiceLoader,
|
||||
private val sellService: Provider<SellService>,
|
||||
private val expressServiceFetcher: ExpressServiceFetcher,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : RampStateManager {
|
||||
|
||||
private val cryptoCurrencyConverter = CryptoCurrencyConverter(excludedBlockchains)
|
||||
|
||||
override suspend fun availableForBuy(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
|
|
@ -56,7 +50,7 @@ internal class DefaultRampManager(
|
|||
return either {
|
||||
val isSellSupportedByService = catch(
|
||||
block = {
|
||||
val serviceCurrency = cryptoCurrencyConverter.convertBack(status.currency)
|
||||
val serviceCurrency = CryptoCurrencyConverter.convert(status.currency)
|
||||
|
||||
sellService().availableForSell(currency = serviceCurrency)
|
||||
},
|
||||
|
|
@ -96,7 +90,7 @@ internal class DefaultRampManager(
|
|||
return availabilityState.toReason(cryptoCurrency.name)
|
||||
}
|
||||
|
||||
override fun getSellInitializationStatus(): Flow<ExchangeServiceInitializationStatus> {
|
||||
override fun getSellInitializationStatus(): Flow<SellServiceInitializationStatus> {
|
||||
return sellService.invoke().initializationStatus
|
||||
}
|
||||
|
||||
|
|
@ -106,8 +100,8 @@ internal class DefaultRampManager(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getExpressInitializationStatus(userWalletId: UserWalletId): Flow<ExchangeServiceInitializationStatus> {
|
||||
return expressServiceLoader.getInitializationStatus(userWalletId)
|
||||
override fun getExpressInitializationStatus(userWalletId: UserWalletId): Flow<SellServiceInitializationStatus> {
|
||||
return expressServiceFetcher.getInitializationStatus(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun getSendUnavailabilityReason(
|
||||
|
|
@ -151,14 +145,14 @@ internal class DefaultRampManager(
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): ExpressAvailabilityState {
|
||||
val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull()
|
||||
val asset = expressServiceFetcher.getInitializationStatus(userWalletId).firstOrNull()
|
||||
?: return ExpressAvailabilityState.Loading
|
||||
return when (asset) {
|
||||
is Lce.Error -> ExpressAvailabilityState.Error
|
||||
is Lce.Loading -> ExpressAvailabilityState.Loading
|
||||
is Lce.Content -> {
|
||||
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) }
|
||||
foundAsset?.exchangeAvailable?.toSwapAvailabilityState()
|
||||
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(assetId = it.id) }
|
||||
foundAsset?.isExchangeAvailable?.toSwapAvailabilityState()
|
||||
?: ExpressAvailabilityState.AssetNotFound
|
||||
}
|
||||
}
|
||||
|
|
@ -168,15 +162,15 @@ internal class DefaultRampManager(
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): ExpressAvailabilityState {
|
||||
val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull()
|
||||
val asset = expressServiceFetcher.getInitializationStatus(userWalletId).firstOrNull()
|
||||
?: return ExpressAvailabilityState.Loading
|
||||
|
||||
return when (asset) {
|
||||
is Lce.Error -> ExpressAvailabilityState.Error
|
||||
is Lce.Loading -> ExpressAvailabilityState.Loading
|
||||
is Lce.Content -> {
|
||||
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(it) }
|
||||
foundAsset?.onrampAvailable?.toOnrampAvailabilityState()
|
||||
val foundAsset = asset.getOrNull()?.find { cryptoCurrency.findAssetPredicate(assetId = it.id) }
|
||||
foundAsset?.isOnrampAvailable?.toOnrampAvailabilityState()
|
||||
?: ExpressAvailabilityState.AssetNotFound
|
||||
}
|
||||
}
|
||||
|
|
@ -211,8 +205,13 @@ internal class DefaultRampManager(
|
|||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrency.findAssetPredicate(asset: Asset): Boolean {
|
||||
val contractAddress = (this as? CryptoCurrency.Token)?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE
|
||||
return asset.network == network.backendId && asset.contractAddress.equals(contractAddress, ignoreCase = true)
|
||||
private fun CryptoCurrency.findAssetPredicate(assetId: ExpressAsset.ID): Boolean {
|
||||
val currencyAssedId = ExpressAsset.ID(
|
||||
networkId = this.network.backendId,
|
||||
contractAddress = (this as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
|
||||
return assetId.networkId == currencyAssedId.networkId &&
|
||||
assetId.contractAddress.equals(currencyAssedId.contractAddress, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,11 +5,11 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.tap.domain.model.Currency
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
typealias ExchangeServiceInitializationStatus = Lce<Throwable, Any>
|
||||
typealias SellServiceInitializationStatus = Lce<Throwable, Any>
|
||||
|
||||
interface ExchangeService {
|
||||
interface SellService {
|
||||
|
||||
val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||
val initializationStatus: StateFlow<SellServiceInitializationStatus>
|
||||
|
||||
suspend fun update()
|
||||
|
||||
|
|
@ -5,7 +5,9 @@ import android.util.Base64
|
|||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.common.services.performRequest
|
||||
import com.tangem.datasource.api.common.createRetrofitInstance
|
||||
import com.tangem.datasource.api.moonpay.MoonPayApi
|
||||
import com.tangem.datasource.api.moonpay.MoonPayCurrencies
|
||||
import com.tangem.datasource.api.moonpay.MoonPayUserStatus
|
||||
import com.tangem.domain.card.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.common.extensions.withIOContext
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
|
|
@ -14,9 +16,10 @@ import com.tangem.domain.core.utils.lceLoading
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus
|
||||
import com.tangem.tap.network.exchangeServices.SellService
|
||||
import com.tangem.tap.network.exchangeServices.SellServiceInitializationStatus
|
||||
import com.tangem.tap.network.exchangeServices.moonpay.models.MoonPayAvailableCurrency
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import timber.log.Timber
|
||||
|
|
@ -24,25 +27,18 @@ import javax.crypto.Mac
|
|||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class MoonPayService(
|
||||
private val apiKey: String,
|
||||
private val secretKey: String,
|
||||
private val isLogEnabled: Boolean,
|
||||
private val api: MoonPayApi,
|
||||
private val apiKeyProvider: Provider<String>,
|
||||
private val secretKeyProvider: Provider<String>,
|
||||
private val userWalletProvider: () -> UserWallet?,
|
||||
) : ExchangeService {
|
||||
) : SellService {
|
||||
|
||||
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||
override val initializationStatus: StateFlow<SellServiceInitializationStatus>
|
||||
get() = _initializationStatus
|
||||
|
||||
private val _initializationStatus: MutableStateFlow<ExchangeServiceInitializationStatus> =
|
||||
private val _initializationStatus: MutableStateFlow<SellServiceInitializationStatus> =
|
||||
MutableStateFlow(value = lceLoading())
|
||||
|
||||
private val api: MoonPayApi by lazy {
|
||||
createRetrofitInstance(
|
||||
baseUrl = MoonPayApi.MOOONPAY_BASE_URL,
|
||||
logEnabled = isLogEnabled,
|
||||
).create(MoonPayApi::class.java)
|
||||
}
|
||||
|
||||
private var status: MoonPayStatus? = null
|
||||
|
||||
override suspend fun update() {
|
||||
|
|
@ -51,7 +47,7 @@ class MoonPayService(
|
|||
_initializationStatus.value = lceLoading()
|
||||
|
||||
performRequest {
|
||||
val userStatus = when (val result = performRequest { api.getUserStatus(apiKey) }) {
|
||||
val userStatus = when (val result = performRequest { api.getUserStatus(apiKeyProvider()) }) {
|
||||
is Result.Failure -> {
|
||||
Timber.e("Failed to load user status", result.error)
|
||||
_initializationStatus.value = result.error.lceError()
|
||||
|
|
@ -60,7 +56,7 @@ class MoonPayService(
|
|||
is Result.Success -> result.data
|
||||
}
|
||||
|
||||
val currencies = when (val result = performRequest { api.getCurrencies(apiKey) }) {
|
||||
val currencies = when (val result = performRequest { api.getCurrencies(apiKeyProvider()) }) {
|
||||
is Result.Failure -> {
|
||||
Timber.e("Failed to load currencies", result.error)
|
||||
_initializationStatus.value = result.error.lceError()
|
||||
|
|
@ -78,7 +74,7 @@ class MoonPayService(
|
|||
MoonPayAvailableCurrency(
|
||||
currencyCode = currency.code,
|
||||
networkCode = currency.metadata?.networkCode ?: return@mapNotNull null,
|
||||
contractAddress = currency.metadata.contractAddress,
|
||||
contractAddress = currency.metadata?.contractAddress,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +163,7 @@ class MoonPayService(
|
|||
val uri = Uri.Builder()
|
||||
.scheme(SCHEME)
|
||||
.authority(URL_SELL)
|
||||
.appendQueryParameter("apiKey", apiKey)
|
||||
.appendQueryParameter("apiKey", apiKeyProvider())
|
||||
.appendQueryParameter("baseCurrencyCode", moonpayCurrency.currencyCode.uppercase())
|
||||
.appendQueryParameter("refundWalletAddress", walletAddress)
|
||||
.appendQueryParameter("redirectURL", "tangem://redirect_sell?currency_id=${cryptoCurrency.id.value}")
|
||||
|
|
@ -191,7 +187,7 @@ class MoonPayService(
|
|||
|
||||
private fun createSignature(data: String): String {
|
||||
val sha256Hmac = Mac.getInstance("HmacSHA256")
|
||||
val secretKey = SecretKeySpec(secretKey.toByteArray(), "HmacSHA256")
|
||||
val secretKey = SecretKeySpec(secretKeyProvider().toByteArray(), "HmacSHA256")
|
||||
sha256Hmac.init(secretKey)
|
||||
val sha256encoded = sha256Hmac.doFinal("?$data".toByteArray())
|
||||
return Base64.encodeToString(sha256encoded, Base64.NO_WRAP)
|
||||
|
|
|
|||
|
|
@ -160,6 +160,6 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
|
|||
Pepecoin, PepecoinTestnet -> null
|
||||
Hyperliquid, HyperliquidTestnet -> null
|
||||
Quai, QuaiTestnet -> null
|
||||
// Linea, LineaTestnet -> null
|
||||
// ArbitrumNova -> null
|
||||
Linea, LineaTestnet -> null
|
||||
ArbitrumNova -> null
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import com.tangem.tap.common.extensions.dispatchDialogShow
|
|||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.SellService
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Store
|
||||
import javax.inject.Inject
|
||||
|
|
@ -19,7 +19,7 @@ import javax.inject.Inject
|
|||
class AppStateHolder @Inject constructor() : ReduxStateHolder {
|
||||
|
||||
var mainStore: Store<AppState>? = null
|
||||
var sellService: ExchangeService? = null
|
||||
var sellService: SellService? = null
|
||||
|
||||
override fun dispatch(action: Action) {
|
||||
mainStore?.dispatch(action)
|
||||
|
|
|
|||
|
|
@ -36,9 +36,10 @@ import com.tangem.features.send.v2.api.SendComponent
|
|||
import com.tangem.features.send.v2.api.SendEntryPointComponent
|
||||
import com.tangem.features.staking.api.StakingComponent
|
||||
import com.tangem.features.swap.SwapComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayDetailsComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.*
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.ContinueOnboarding
|
||||
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.Deeplink
|
||||
import com.tangem.features.tokendetails.TokenDetailsComponent
|
||||
import com.tangem.features.wallet.WalletEntryComponent
|
||||
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
|
||||
|
|
@ -62,6 +63,7 @@ internal class ChildFactory @Inject constructor(
|
|||
private val detailsComponentFactory: DetailsComponent.Factory,
|
||||
private val walletSettingsComponentFactory: WalletSettingsComponent.Factory,
|
||||
private val walletBackupComponentFactory: WalletBackupComponent.Factory,
|
||||
private val walletHardwareBackupComponentFactory: WalletHardwareBackupComponent.Factory,
|
||||
private val disclaimerComponentFactory: DisclaimerComponent.Factory,
|
||||
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
|
||||
private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory,
|
||||
|
|
@ -100,6 +102,7 @@ internal class ChildFactory @Inject constructor(
|
|||
private val chooseManagedTokensComponentFactory: ChooseManagedTokensComponent.Factory,
|
||||
private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory,
|
||||
private val createWalletStartComponentFactory: CreateWalletStartComponent.Factory,
|
||||
private val createHardwareWalletComponentFactory: CreateHardwareWalletComponent.Factory,
|
||||
private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory,
|
||||
private val upgradeWalletComponentFactory: UpgradeWalletComponent.Factory,
|
||||
private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory,
|
||||
|
|
@ -107,8 +110,9 @@ internal class ChildFactory @Inject constructor(
|
|||
private val createWalletBackupComponentFactory: CreateWalletBackupComponent.Factory,
|
||||
private val updateAccessCodeComponentFactory: UpdateAccessCodeComponent.Factory,
|
||||
private val viewPhraseComponentFactory: ViewPhraseComponent.Factory,
|
||||
private val forgetWalletComponentFactory: ForgetWalletComponent.Factory,
|
||||
private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory,
|
||||
private val tangemPayDetailsComponentFactory: TangemPayDetailsComponent.Factory,
|
||||
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
|
||||
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
|
||||
private val kycComponentFactory: KycComponent.Factory,
|
||||
private val yieldSupplyPromoComponentFactory: YieldSupplyPromoComponent.Factory,
|
||||
|
|
@ -138,7 +142,6 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.ManageTokens -> {
|
||||
val source = when (route.source) {
|
||||
AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS
|
||||
AppRoute.ManageTokens.Source.ONBOARDING -> ManageTokensSource.ONBOARDING
|
||||
AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES
|
||||
}
|
||||
|
||||
|
|
@ -187,6 +190,15 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = walletBackupComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.WalletHardwareBackup -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = WalletHardwareBackupComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
),
|
||||
componentFactory = walletHardwareBackupComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.MarketsTokenDetails -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
|
|
@ -293,7 +305,7 @@ internal class ChildFactory @Inject constructor(
|
|||
context = context,
|
||||
params = StakingComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
cryptoCurrencyId = route.cryptoCurrencyId,
|
||||
cryptoCurrency = route.cryptoCurrency,
|
||||
yieldId = route.yieldId,
|
||||
),
|
||||
componentFactory = stakingComponentFactory,
|
||||
|
|
@ -308,6 +320,13 @@ internal class ChildFactory @Inject constructor(
|
|||
userWalletId = route.userWalletId,
|
||||
isInitialReverseOrder = route.isInitialReverseOrder,
|
||||
screenSource = route.screenSource,
|
||||
tangemPayInput = route.tangemPayInput?.let { tangemPayInput ->
|
||||
SwapComponent.Params.TangemPayInput(
|
||||
cryptoAmount = tangemPayInput.cryptoAmount,
|
||||
fiatAmount = tangemPayInput.fiatAmount,
|
||||
depositAddress = tangemPayInput.depositAddress,
|
||||
)
|
||||
},
|
||||
),
|
||||
componentFactory = swapComponentFactory,
|
||||
)
|
||||
|
|
@ -496,6 +515,13 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = createWalletSelectionComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.CreateHardwareWallet -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = Unit,
|
||||
componentFactory = createHardwareWalletComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.CreateMobileWallet -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
|
|
@ -533,6 +559,7 @@ internal class ChildFactory @Inject constructor(
|
|||
context = context,
|
||||
params = CreateWalletBackupComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
isUpgradeFlow = route.isUpgradeFlow,
|
||||
),
|
||||
componentFactory = createWalletBackupComponentFactory,
|
||||
)
|
||||
|
|
@ -555,6 +582,15 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = viewPhraseComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.ForgetWallet -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = ForgetWalletComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
),
|
||||
componentFactory = forgetWalletComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.SendEntryPoint -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
|
|
@ -604,8 +640,11 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.TangemPayDetails -> {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = TangemPayDetailsComponent.Params(config = route.config),
|
||||
componentFactory = tangemPayDetailsComponentFactory,
|
||||
params = TangemPayDetailsContainerComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
config = route.config,
|
||||
),
|
||||
componentFactory = tangemPayDetailsContainerComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.TangemPayOnboarding -> {
|
||||
|
|
|
|||
|
|
@ -34,18 +34,36 @@ interface Injected {
|
|||
|
||||
// Test Logging
|
||||
subprojects {
|
||||
tasks.withType<Test> {
|
||||
tasks.withType<Test>().configureEach {
|
||||
val taskName = name.lowercase()
|
||||
if (taskName.contains("external") ||
|
||||
taskName.contains("internal") ||
|
||||
taskName.contains("release") ||
|
||||
taskName.contains("mocked") ||
|
||||
taskName.contains("huawei")
|
||||
) {
|
||||
enabled = false
|
||||
println("Skipping test task: $name")
|
||||
} else {
|
||||
println("Test task scheduled: $name")
|
||||
}
|
||||
|
||||
testLogging {
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showStandardStreams = true
|
||||
|
||||
afterSuite(KotlinClosure2<TestDescriptor, TestResult, Unit>({ desc, result ->
|
||||
if (desc.parent == null) { // will match the outermost suite
|
||||
val output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)"
|
||||
val output =
|
||||
"Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)"
|
||||
val startItem = "| "
|
||||
val endItem = " |"
|
||||
val repeatLength = startItem.length + output.length + endItem.length
|
||||
println("\n" + "-".repeat(repeatLength) + "\n" + startItem + output + endItem + "\n" + "-".repeat(repeatLength))
|
||||
println(
|
||||
"\n" + "-".repeat(repeatLength) + "\n" + startItem + output + endItem + "\n" + "-".repeat(
|
||||
repeatLength
|
||||
)
|
||||
)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.domain.models.PortfolioId
|
|||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
|
|
@ -126,9 +127,12 @@ sealed class AppRoute(val path: String) : Route {
|
|||
val portfolioId: PortfolioId? = null,
|
||||
) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/${portfolioId?.stringValue}") {
|
||||
|
||||
/**
|
||||
* Source of launching the screen.
|
||||
* ManageTokens screen launched from Onboarding by another route. See `OnboardingRoute.ManageTokens`.
|
||||
*/
|
||||
enum class Source {
|
||||
STORIES,
|
||||
ONBOARDING,
|
||||
SETTINGS,
|
||||
}
|
||||
}
|
||||
|
|
@ -178,13 +182,21 @@ sealed class AppRoute(val path: String) : Route {
|
|||
val userWalletId: UserWalletId,
|
||||
val isInitialReverseOrder: Boolean = false,
|
||||
val screenSource: String,
|
||||
val tangemPayInput: TangemPayInput? = null,
|
||||
) : AppRoute(
|
||||
path = "/swap" +
|
||||
"/${currencyFrom.id.value}" +
|
||||
"/${currencyTo?.id?.value}" +
|
||||
"/${userWalletId.stringValue}" +
|
||||
"/$isInitialReverseOrder",
|
||||
)
|
||||
) {
|
||||
@Serializable
|
||||
data class TangemPayInput(
|
||||
val cryptoAmount: SerializedBigDecimal,
|
||||
val fiatAmount: SerializedBigDecimal,
|
||||
val depositAddress: String,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object AppCurrencySelector : AppRoute(path = "/app_currency_selector")
|
||||
|
|
@ -192,9 +204,9 @@ sealed class AppRoute(val path: String) : Route {
|
|||
@Serializable
|
||||
data class Staking(
|
||||
val userWalletId: UserWalletId,
|
||||
val cryptoCurrencyId: CryptoCurrency.ID,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val yieldId: String,
|
||||
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/$yieldId")
|
||||
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrency.id.value}/$yieldId")
|
||||
|
||||
@Serializable
|
||||
data class PushNotification(
|
||||
|
|
@ -217,6 +229,11 @@ sealed class AppRoute(val path: String) : Route {
|
|||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/wallet_backup/${userWalletId.stringValue}")
|
||||
|
||||
@Serializable
|
||||
data class WalletHardwareBackup(
|
||||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/wallet_hardware_backup/${userWalletId.stringValue}")
|
||||
|
||||
@Serializable
|
||||
data object Markets : AppRoute(path = "/markets")
|
||||
|
||||
|
|
@ -322,6 +339,9 @@ sealed class AppRoute(val path: String) : Route {
|
|||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
object CreateHardwareWallet : AppRoute(path = "/create_hardware_wallet")
|
||||
|
||||
@Serializable
|
||||
object CreateMobileWallet : AppRoute(path = "/create_mobile_wallet")
|
||||
|
||||
|
|
@ -341,6 +361,7 @@ sealed class AppRoute(val path: String) : Route {
|
|||
@Serializable
|
||||
data class CreateWalletBackup(
|
||||
val userWalletId: UserWalletId,
|
||||
val isUpgradeFlow: Boolean,
|
||||
) : AppRoute(path = "/create_wallet_backup/${userWalletId.stringValue}")
|
||||
|
||||
@Serializable
|
||||
|
|
@ -353,6 +374,11 @@ sealed class AppRoute(val path: String) : Route {
|
|||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/view_seed_phrase/${userWalletId.stringValue}")
|
||||
|
||||
@Serializable
|
||||
data class ForgetWallet(
|
||||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/forget_wallet/${userWalletId.stringValue}")
|
||||
|
||||
@Serializable
|
||||
data class SendEntryPoint(
|
||||
val userWalletId: UserWalletId,
|
||||
|
|
@ -383,8 +409,9 @@ sealed class AppRoute(val path: String) : Route {
|
|||
|
||||
@Serializable
|
||||
data class TangemPayDetails(
|
||||
val userWalletId: UserWalletId,
|
||||
val config: TangemPayDetailsConfig,
|
||||
) : AppRoute(path = "/tangem_pay_details")
|
||||
) : AppRoute(path = "/tangem_pay_details/${userWalletId.stringValue}")
|
||||
|
||||
@Serializable
|
||||
data class TangemPayOnboarding(
|
||||
|
|
|
|||
|
|
@ -1,21 +1,30 @@
|
|||
package com.tangem.common.ui.account
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.Subtitle2State
|
||||
import com.tangem.core.ui.extensions.pluralReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.quote.PriceChange
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import java.math.BigDecimal
|
||||
|
||||
class AccountCryptoPortfolioItemStateConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val account: Account.CryptoPortfolio,
|
||||
private val priceChangeLce: Lce<Unit, PriceChange>? = null,
|
||||
private val onItemClick: ((Account.CryptoPortfolio) -> Unit)? = null,
|
||||
private val onItemLongClick: ((Account.CryptoPortfolio) -> Unit)? = null,
|
||||
) : Converter<TotalFiatBalance, TokenItemState> {
|
||||
|
|
@ -31,6 +40,14 @@ class AccountCryptoPortfolioItemStateConverter(
|
|||
private fun Account.CryptoPortfolio.mapToContentState(
|
||||
fiatBalance: TotalFiatBalance.Loaded,
|
||||
): TokenItemState.Content {
|
||||
val subtitle2State = when (fiatBalance.amount.isZero()) {
|
||||
true -> null
|
||||
false -> priceChangeLce?.fold(
|
||||
ifLoading = { priceChange -> priceChange?.toSubtitle2State() ?: Subtitle2State.Loading },
|
||||
ifError = { null },
|
||||
ifContent = { priceChange -> priceChange.toSubtitle2State() },
|
||||
)
|
||||
}
|
||||
return TokenItemState.Content(
|
||||
id = account.accountId.value,
|
||||
iconState = AccountIconItemStateConverter.convert(this),
|
||||
|
|
@ -50,14 +67,14 @@ class AccountCryptoPortfolioItemStateConverter(
|
|||
.format { fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol) },
|
||||
isFlickering = fiatBalance.source == StatusSource.CACHE,
|
||||
),
|
||||
subtitle2State = null,
|
||||
subtitle2State = subtitle2State,
|
||||
onItemClick = onItemClick?.let { onItemClick -> { onItemClick(account) } },
|
||||
onItemLongClick = onItemLongClick?.let { onItemLongClick -> { onItemLongClick(account) } },
|
||||
)
|
||||
}
|
||||
|
||||
private fun Account.CryptoPortfolio.mapToLoadingState(): TokenItemState.Loading {
|
||||
return TokenItemState.Loading(
|
||||
private fun Account.CryptoPortfolio.mapToLoadingState(): TokenItemState.Content {
|
||||
return TokenItemState.Content(
|
||||
id = account.accountId.value,
|
||||
iconState = AccountIconItemStateConverter.convert(account),
|
||||
titleState = TokenItemState.TitleState.Content(
|
||||
|
|
@ -71,6 +88,10 @@ class AccountCryptoPortfolioItemStateConverter(
|
|||
),
|
||||
isAvailable = false,
|
||||
),
|
||||
fiatAmountState = FiatAmountState.Loading,
|
||||
subtitle2State = Subtitle2State.Loading,
|
||||
onItemLongClick = null,
|
||||
onItemClick = onItemClick?.let { onItemClick -> { onItemClick(account) } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -97,4 +118,14 @@ class AccountCryptoPortfolioItemStateConverter(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun BigDecimal.getPriceChangeType(): PriceChangeType = PriceChangeConverter.fromBigDecimal(value = this)
|
||||
|
||||
private fun StatusSource.isFlickering(): Boolean = this == StatusSource.CACHE
|
||||
|
||||
private fun PriceChange.toSubtitle2State(): Subtitle2State = Subtitle2State.PriceChangeContent(
|
||||
priceChangePercent = this.value.format { percent() },
|
||||
type = this.value.getPriceChangeType(),
|
||||
isFlickering = this.source.isFlickering(),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.common.ui.account
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
|
|
@ -25,7 +26,10 @@ sealed interface AccountNameUM {
|
|||
*/
|
||||
data object DefaultMain : AccountNameUM {
|
||||
|
||||
override val value: TextReference = resourceReference(R.string.account_main_account_title)
|
||||
@get:StringRes
|
||||
val stringResId: Int = R.string.account_main_account_title
|
||||
|
||||
override val value: TextReference = resourceReference(stringResId)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -33,7 +37,7 @@ sealed interface AccountNameUM {
|
|||
*
|
||||
* @property raw the raw string value of the custom account name
|
||||
*/
|
||||
class Custom(internal val raw: String) : AccountNameUM {
|
||||
data class Custom(internal val raw: String) : AccountNameUM {
|
||||
|
||||
override val value: TextReference = stringReference(value = raw)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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.platform.testTag
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -29,6 +30,8 @@ fun AccountTitle(
|
|||
accountTitleUM: AccountTitleUM,
|
||||
modifier: Modifier = Modifier,
|
||||
textStyle: TextStyle = TangemTheme.typography.subtitle2,
|
||||
textColor: Color = TangemTheme.colors.text.tertiary,
|
||||
iconSize: AccountIconSize = AccountIconSize.ExtraSmall,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
@ -40,19 +43,20 @@ fun AccountTitle(
|
|||
Text(
|
||||
text = accountTitleUM.prefixText.resolveReference(),
|
||||
style = textStyle,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
color = textColor,
|
||||
)
|
||||
AccountLabel(
|
||||
name = accountTitleUM.name,
|
||||
icon = accountTitleUM.icon,
|
||||
iconSize = AccountIconSize.ExtraSmall,
|
||||
iconSize = iconSize,
|
||||
nameStyle = textStyle,
|
||||
nameColor = textColor,
|
||||
)
|
||||
}
|
||||
is AccountTitleUM.Text -> Text(
|
||||
text = accountTitleUM.title.resolveReference(),
|
||||
style = textStyle,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
color = textColor,
|
||||
modifier = Modifier.testTag(SendScreenTestTags.AMOUNT_CONTAINER_TITLE),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
package com.tangem.common.ui.account
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.components.SpacerW12
|
||||
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.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.models.account.AccountName
|
||||
|
||||
@Composable
|
||||
fun PortfolioSelectRow(
|
||||
state: PortfolioSelectUM,
|
||||
modifier: Modifier = Modifier,
|
||||
leftContent: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clickable(enabled = state.isMultiChoice, onClick = state.onClick)
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
leftContent()
|
||||
val leftText = if (state.isAccountMode) R.string.account_details_title else R.string.wc_common_wallet
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResourceSafe(leftText),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
SpacerW12()
|
||||
if (state.icon != null) {
|
||||
AccountIcon(
|
||||
name = state.name,
|
||||
icon = state.icon,
|
||||
size = AccountIconSize.Small,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
text = state.name.resolveReference(),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
if (state.isMultiChoice) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(width = 18.dp, height = 24.dp),
|
||||
painter = painterResource(id = R.drawable.ic_select_18_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class PortfolioSelectUM(
|
||||
val icon: CryptoPortfolioIconUM?,
|
||||
val name: TextReference,
|
||||
val isAccountMode: Boolean,
|
||||
val isMultiChoice: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PortfolioSelectRowPreview(@PreviewParameter(PreviewProvider::class) state: PortfolioSelectUM) {
|
||||
TangemThemePreview {
|
||||
PortfolioSelectRow(
|
||||
state = state,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.tertiary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class PreviewProvider : PreviewParameterProvider<PortfolioSelectUM> {
|
||||
|
||||
override val values: Sequence<PortfolioSelectUM>
|
||||
get() = sequenceOf(PortfolioSelectRowPreviewData.account, PortfolioSelectRowPreviewData.wallet)
|
||||
}
|
||||
|
||||
object PortfolioSelectRowPreviewData {
|
||||
val account
|
||||
get() = PortfolioSelectUM(
|
||||
icon = AccountIconPreviewData.randomAccountIcon(),
|
||||
name = AccountName.DefaultMain.toUM().value,
|
||||
isAccountMode = true,
|
||||
isMultiChoice = true,
|
||||
onClick = {},
|
||||
)
|
||||
val wallet
|
||||
get() = PortfolioSelectUM(
|
||||
icon = null,
|
||||
name = stringReference("Wallet Name"),
|
||||
isMultiChoice = false,
|
||||
isAccountMode = false,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import androidx.compose.ui.focus.focusRequester
|
|||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
|
|
@ -47,6 +48,7 @@ import com.tangem.core.ui.format.bigdecimal.fiat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.SendScreenTestTags
|
||||
import com.tangem.core.ui.utils.rememberDecimalFormat
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
|
|
@ -191,7 +193,8 @@ private fun BoxScope.AmountFieldCurrencyInfo(amountUM: AmountState.Data, onCurre
|
|||
.size(16.dp)
|
||||
.graphicsLayer {
|
||||
rotationY = iconRotateState
|
||||
},
|
||||
}
|
||||
.testTag(SendScreenTestTags.EXCHANGE_ICON),
|
||||
)
|
||||
AmountFieldCurrencyIcon(
|
||||
amountUM = amountUM,
|
||||
|
|
@ -227,6 +230,7 @@ private fun AmountFieldCurrencyIcon(amountUM: AmountState.Data) {
|
|||
style = TangemTheme.typography.caption2.copy(textDirection = TextDirection.ContentOrLtr),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.testTag(SendScreenTestTags.EQUIVALENT_INPUT_AMOUNT),
|
||||
)
|
||||
if (isFiatValue) {
|
||||
CurrencyIcon(
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ sealed class AnalyticsParam {
|
|||
data object LongTap : ScreensSources("Long Tap")
|
||||
data object Markets : ScreensSources("Markets")
|
||||
data object HotWallet : ScreensSources("Hot Wallet")
|
||||
data object TangemPay : ScreensSources("Tangem Pay")
|
||||
}
|
||||
|
||||
sealed class TxSentFrom(val value: String) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.core.analytics.utils
|
||||
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -9,9 +10,15 @@ interface AnalyticsContextProxy {
|
|||
|
||||
fun setContext(scanResponse: ScanResponse)
|
||||
|
||||
fun addContext(userWallet: UserWallet)
|
||||
|
||||
fun setHotWalletContext()
|
||||
|
||||
fun eraseContext()
|
||||
|
||||
fun addContext(scanResponse: ScanResponse)
|
||||
|
||||
fun addHotWalletContext()
|
||||
|
||||
fun removeContext()
|
||||
}
|
||||
|
|
@ -30,9 +30,5 @@
|
|||
{
|
||||
"name": "zklink",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "scroll",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
@ -27,6 +27,7 @@ sealed class ApiConfig {
|
|||
TangemPay,
|
||||
BlockAid,
|
||||
YieldSupply,
|
||||
MoonPay,
|
||||
}
|
||||
|
||||
private fun initializeId(): ID {
|
||||
|
|
@ -37,6 +38,7 @@ sealed class ApiConfig {
|
|||
is TangemPay -> ID.TangemPay
|
||||
is BlockAid -> ID.BlockAid
|
||||
is YieldSupply -> ID.YieldSupply
|
||||
is MoonPay -> ID.MoonPay
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
|
||||
/**
|
||||
* MoonPay [ApiConfig]
|
||||
*/
|
||||
internal class MoonPay : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
|
||||
|
||||
override val environmentConfigs: List<ApiEnvironmentConfig> = listOf(
|
||||
createProdEnvironment(),
|
||||
createMockEnvironment(),
|
||||
)
|
||||
|
||||
private fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.MOCK
|
||||
DEBUG_BUILD_TYPE,
|
||||
INTERNAL_BUILD_TYPE,
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig {
|
||||
return ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.moonpay.com/",
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMockEnvironment(): ApiEnvironmentConfig {
|
||||
return ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.MOCK,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,10 +14,12 @@ sealed class ApiResponse<T : Any> {
|
|||
* Represents a successful response from the API
|
||||
*
|
||||
* @property data the data returned by the API
|
||||
* @property code the HTTP status code of the response
|
||||
* @property headers the headers returned by the API
|
||||
*/
|
||||
data class Success<T : Any>(
|
||||
val data: T,
|
||||
val code: ApiResponseError.HttpException.Code = ApiResponseError.HttpException.Code.OK,
|
||||
override val headers: Map<String, List<String>> = emptyMap(),
|
||||
) : ApiResponse<T>()
|
||||
|
||||
|
|
@ -37,11 +39,20 @@ sealed class ApiResponse<T : Any> {
|
|||
* Wraps data in a [ApiResponse.Success] instance
|
||||
*
|
||||
* @param data the data to wrap
|
||||
* @param code the HTTP status code of the response
|
||||
* @param headers the headers returned by the API
|
||||
* @return a [ApiResponse.Success] instance containing the provided data
|
||||
*/
|
||||
internal fun <T : Any> apiSuccess(data: T, headers: Map<String, List<String>>): ApiResponse<T> {
|
||||
return ApiResponse.Success(data, headers)
|
||||
internal fun <T : Any> apiSuccess(
|
||||
data: T,
|
||||
code: ApiResponseError.HttpException.Code?,
|
||||
headers: Map<String, List<String>>,
|
||||
): ApiResponse<T> {
|
||||
return ApiResponse.Success(
|
||||
data = data,
|
||||
code = code ?: ApiResponseError.HttpException.Code.OK,
|
||||
headers = headers,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -18,8 +18,13 @@ sealed class ApiResponseError : Exception() {
|
|||
val errorBody: String?,
|
||||
) : ApiResponseError() {
|
||||
|
||||
// TODO: extract Code from HttpException
|
||||
// region Error Codes
|
||||
enum class Code(val numericCode: Int) {
|
||||
// 2xx Success
|
||||
OK(numericCode = 200),
|
||||
CREATED(numericCode = 201),
|
||||
ACCEPTED(numericCode = 202),
|
||||
// 3xx Server Errors
|
||||
NOT_MODIFIED(numericCode = 304),
|
||||
// 4xx Server Errors
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@ internal fun <T : Any> Response<T>.toSafeApiResponse(analyticsErrorHandler: Anal
|
|||
val headers = headers().toMultimap()
|
||||
val body = body()
|
||||
|
||||
val code = ApiResponseError.HttpException.Code.entries
|
||||
.firstOrNull { it.numericCode == code() }
|
||||
|
||||
return if (isSuccessful && body != null) {
|
||||
apiSuccess(data = body, headers = headers)
|
||||
apiSuccess(data = body, code = code, headers = headers)
|
||||
} else {
|
||||
val code = ApiResponseError.HttpException.Code.entries
|
||||
.firstOrNull { it.numericCode == code() }
|
||||
val e = try {
|
||||
if (code == null) {
|
||||
ApiResponseError.UnknownException(IllegalArgumentException("Unknown error status code: ${code()}"))
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
package com.tangem.datasource.api.express.models
|
||||
|
||||
object TangemExpressValues {
|
||||
const val EMPTY_CONTRACT_ADDRESS_VALUE = "0"
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.network.exchangeServices.moonpay
|
||||
package com.tangem.datasource.api.moonpay
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
|
@ -7,17 +7,11 @@ import retrofit2.http.Query
|
|||
|
||||
interface MoonPayApi {
|
||||
|
||||
@GET(MOOONPAY_IP_ADDRESS_REQUEST_URL)
|
||||
@GET("v4/ip_address/")
|
||||
suspend fun getUserStatus(@Query("apiKey") moonPayApiKey: String): MoonPayUserStatus
|
||||
|
||||
@GET(MOOONPAY_CURRENCIES_REQUEST_URL)
|
||||
@GET("v3/currencies/")
|
||||
suspend fun getCurrencies(@Query("apiKey") moonPayApiKey: String): List<MoonPayCurrencies>
|
||||
|
||||
companion object {
|
||||
const val MOOONPAY_BASE_URL = "https://api.moonpay.com/"
|
||||
const val MOOONPAY_IP_ADDRESS_REQUEST_URL = "v4/ip_address/"
|
||||
const val MOOONPAY_CURRENCIES_REQUEST_URL = "v3/currencies/"
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
@ -7,6 +7,7 @@ import retrofit2.http.Body
|
|||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
|
|
@ -146,4 +147,10 @@ interface TangemPayApi {
|
|||
@Header("Authorization") authHeader: String,
|
||||
@Body body: CardDetailsRequest,
|
||||
): ApiResponse<CardDetailsResponse>
|
||||
|
||||
@PUT("v1/customer/card/pin")
|
||||
suspend fun setPin(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: SetPinRequest,
|
||||
): ApiResponse<SetPinResponse>
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SetPinRequest(
|
||||
@Json(name = "pin") val pin: String,
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
@Json(name = "iv") val iv: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SetPinResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "result") val result: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -137,6 +137,9 @@ interface TangemTechApi {
|
|||
|
||||
@GET("v1/user-wallets/wallets/by-app/{app_id}")
|
||||
suspend fun getWallets(@Path("app_id") appId: String): ApiResponse<List<WalletResponse>>
|
||||
|
||||
@POST("v1/user-wallets/wallets")
|
||||
suspend fun createWallet(@Body body: OnlyWalletIdBody): ApiResponse<Unit>
|
||||
// endregion
|
||||
|
||||
// promo
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnlyWalletIdBody(
|
||||
@Json(name = "id") val walletId: String,
|
||||
)
|
||||
|
|
@ -20,4 +20,18 @@ data class GetWalletAccountsResponse(
|
|||
@Json(name = "sort") val sort: SortType,
|
||||
@Json(name = "totalAccounts") val totalAccounts: Int,
|
||||
)
|
||||
}
|
||||
|
||||
/** Flattens the tokens from all wallet accounts into a single list */
|
||||
fun GetWalletAccountsResponse.flattenTokens(): List<UserTokensResponse.Token> {
|
||||
return accounts.flatMap { it.tokens.orEmpty() }
|
||||
}
|
||||
|
||||
/** Converts the [GetWalletAccountsResponse] into a [UserTokensResponse] */
|
||||
fun GetWalletAccountsResponse.toUserTokensResponse(): UserTokensResponse {
|
||||
return UserTokensResponse(
|
||||
group = wallet.group,
|
||||
sort = wallet.sort,
|
||||
tokens = flattenTokens(),
|
||||
)
|
||||
}
|
||||
|
|
@ -74,4 +74,10 @@ internal object ApiConfigsModule {
|
|||
fun provideBlockAidConfig(environmentConfigStorage: EnvironmentConfigStorage): ApiConfig {
|
||||
return BlockAid(environmentConfigStorage)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideMoonPayConfig(): ApiConfig {
|
||||
return MoonPay()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,14 @@ import com.tangem.datasource.api.common.blockaid.BlockAidApi
|
|||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||
import com.tangem.datasource.api.common.config.MoonPay
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.api.common.config.managers.DevApiConfigsManager
|
||||
import com.tangem.datasource.api.common.config.managers.MockApiConfigsManager
|
||||
import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.moonpay.MoonPayApi
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
|
|
@ -130,4 +132,13 @@ internal object NetworkModule {
|
|||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMoonPayApi(retrofitApiBuilder: RetrofitApiBuilder): MoonPayApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.MoonPay,
|
||||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.datasource.di.exchangeservice
|
||||
|
||||
import com.tangem.datasource.exchangeservice.swap.DefaultExpressServiceLoader
|
||||
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface ExchangeServiceLoaderModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindExpressServiceLoader(defaultExpressServiceLoader: DefaultExpressServiceLoader): ExpressServiceLoader
|
||||
}
|
||||
|
|
@ -197,6 +197,7 @@ internal class RetrofitApiBuilder @Inject constructor(
|
|||
|
||||
val excludedApiForLogging: Set<ApiConfig.ID> = setOf(
|
||||
// ApiConfig.ID.StakeKit,
|
||||
ApiConfig.ID.MoonPay,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.datasource.exchangeservice.swap
|
||||
|
||||
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
|
||||
import com.tangem.datasource.api.express.models.response.Asset
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Express service loader
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ExpressServiceLoader {
|
||||
|
||||
/** Update service using [userWallet] and [userTokens] */
|
||||
suspend fun update(userWallet: UserWallet, userTokens: List<LeastTokenInfo>)
|
||||
|
||||
/** Get initialization status by [userWalletId] */
|
||||
fun getInitializationStatus(userWalletId: UserWalletId): Flow<Lce<Throwable, List<Asset>>>
|
||||
}
|
||||
|
|
@ -171,6 +171,9 @@ object PreferencesKeys {
|
|||
fun getHotWalletUnlockDeadlineKey(attemptId: String) =
|
||||
longPreferencesKey(name = "hotWalletUnlockDeadline_$attemptId")
|
||||
|
||||
fun getTangemPayAddToWalletKey(customerWalletAddress: String) =
|
||||
booleanPreferencesKey("tangem_pay_add_to_wallet_done_key_$customerWalletAddress")
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
|
||||
interface TangemPayStorage {
|
||||
|
||||
suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String)
|
||||
suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String?
|
||||
|
||||
suspend fun storeAuthTokens(customerWalletAddress: String, tokens: VisaAuthTokens)
|
||||
|
||||
suspend fun getAuthTokens(customerWalletAddress: String): VisaAuthTokens?
|
||||
|
|
@ -14,5 +18,9 @@ interface TangemPayStorage {
|
|||
|
||||
suspend fun clearOrderId(customerWalletAddress: String)
|
||||
|
||||
suspend fun clearAll(customerWalletAddress: String)
|
||||
suspend fun getAddToWalletDone(customerWalletAddress: String): Boolean
|
||||
|
||||
suspend fun storeAddToWalletDone(customerWalletAddress: String, isDone: Boolean)
|
||||
|
||||
suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String)
|
||||
}
|
||||
|
|
@ -71,6 +71,7 @@ class ApiConfigTest {
|
|||
ApiConfig.ID.StakeKit -> StakeKit(stakeKitAuthProvider = mockk())
|
||||
ApiConfig.ID.TangemPay -> TangemPay(appVersionProvider = mockk())
|
||||
ApiConfig.ID.BlockAid -> BlockAid(configStorage = mockk())
|
||||
ApiConfig.ID.MoonPay -> MoonPay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ internal class ProdApiConfigsManagerTest {
|
|||
ApiConfig.ID.StakeKit -> StakeKit(stakeKitAuthProvider = stakeKitAuthProvider)
|
||||
ApiConfig.ID.TangemPay -> TangemPay(appVersionProvider = appVersionProvider)
|
||||
ApiConfig.ID.BlockAid -> BlockAid(configStorage = environmentConfigStorage)
|
||||
ApiConfig.ID.MoonPay -> MoonPay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -121,6 +122,7 @@ internal class ProdApiConfigsManagerTest {
|
|||
ApiConfig.ID.StakeKit -> createStakeKitModel()
|
||||
ApiConfig.ID.TangemPay -> createTangemPayModel()
|
||||
ApiConfig.ID.BlockAid -> createBlockAidSdkModel()
|
||||
ApiConfig.ID.MoonPay -> createMoonPayModel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -263,6 +265,16 @@ internal class ProdApiConfigsManagerTest {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createMoonPayModel(): TestModel {
|
||||
return TestModel(
|
||||
id = ApiConfig.ID.MoonPay,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.moonpay.com/",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.checkHeaderValueOrEmpty(): String {
|
||||
for (i in this.indices) {
|
||||
val c = this[i]
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@
|
|||
<string name="common_fee_selector_title">Velocidad y tarifa</string>
|
||||
<string name="common_finish">Finalizar</string>
|
||||
<string name="common_generate_addresses">Sincronizar direcciones</string>
|
||||
<string name="common_get_started">Comenzar</string>
|
||||
<string name="common_go_to_provider">Ir al proveedor</string>
|
||||
<string name="common_go_to_token">Ir al token</string>
|
||||
<string name="common_got_it">Entendido</string>
|
||||
|
|
@ -984,6 +985,7 @@
|
|||
<string name="staking_details_account_fee_info">Una cuenta de staking es una cuenta especial donde se almacenan los tokens SOL de staking. Se crea cuando delegas sus tokens a un validador para participar en la validación de transacciones y ganar recompensas. Se cobra una pequeña tarifa por crear la cuenta de staking, que se devuelve una vez que se completa el staking.</string>
|
||||
<string name="staking_details_annual_percentage_rate">Porcentaje de rendimiento anual</string>
|
||||
<string name="staking_details_annual_percentage_rate_info">El porcentaje de rendimiento anual que podrá ganar como participante en el staking.</string>
|
||||
<string name="staking_details_annual_percentage_yield_info">APY muestra el porcentaje de rendimiento anual del validador en función de su rendimiento</string>
|
||||
<string name="staking_details_apr">APR</string>
|
||||
<string name="staking_details_apy">APY</string>
|
||||
<string name="staking_details_auto_claiming_rewards_daily_text">Las recompensas se acumulan automáticamente en su saldo de staking diariamente.</string>
|
||||
|
|
@ -1160,6 +1162,7 @@
|
|||
<string name="token_button_unavailability_reason_pending_transaction_send">El envío de fondos estará disponible una vez que se complete(n) la(s) transacción(es) pendiente(s) en la red %s</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">Vender %s no es compatible con los proveedores actuales, pero estamos trabajando para agregar más opciones.</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">El staking de %s no es compatible con los proveedores actuales, pero estamos trabajando para agregar más opciones.</string>
|
||||
<string name="token_button_unavailability_reason_yield_supply_approval">Se ha revocado la aprobación. Sus fondos permanecen en modo de rendimiento. Para realizar acciones, vuelva al modo de rendimiento y otorgue el permiso nuevamente.</string>
|
||||
<string name="token_details_generate_xpub">Generar XPUB</string>
|
||||
<string name="token_details_hide_alert_hide">Ocultar</string>
|
||||
<string name="token_details_hide_alert_message">Está a punto de ocultar este token de la pantalla principal. Puede volver a agregarlo en cualquier momento a través de la página de gestión de tokens.</string>
|
||||
|
|
@ -1549,6 +1552,7 @@
|
|||
<string name="yield_module_stop_earning_sheet_description">Al desactivar esto, retirará sus fondos de Aave a %s en su billetera y dejará de ganar recompensas.</string>
|
||||
<string name="yield_module_stop_earning_sheet_fee_note">La comisión de red se deducirá del importe que retire.</string>
|
||||
<string name="yield_module_stop_earning_sheet_title">Desactivar el modo de rendimiento</string>
|
||||
<string name="yield_module_supply">Suministrar</string>
|
||||
<string name="yield_module_supply_apr">Suministro APY</string>
|
||||
<string name="yield_module_token_details_earn_notification_apy">APY</string>
|
||||
<string name="yield_module_token_details_earn_notification_description">Los intereses se devengan automáticamente</string>
|
||||
|
|
@ -1560,5 +1564,6 @@
|
|||
<string name="yield_module_unable_to_cover_fee_description">Deposite algo de %1$s %2$s para cubrir la tarifa de red para las transacciones</string>
|
||||
<string name="yield_module_unable_to_cover_fee_title">No se puede cubrir la tarifa %s</string>
|
||||
<string name="yield_module_unavailable_subtitle">El servicio de intereses no está disponible en este momento. Vuelva a intentarlo más tarde.</string>
|
||||
<string name="yield_module_unavailable_title">Ganancias no disponibles</string>
|
||||
<string name="yield_module_unavailable_title">Modo de rendimiento no disponible</string>
|
||||
<string name="yield_supply_chart_loading_error">No se puede cargar el gráfico...</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@
|
|||
<string name="common_fee_selector_title">Vitesse et frais</string>
|
||||
<string name="common_finish">Terminer</string>
|
||||
<string name="common_generate_addresses">Synchroniser les adresses</string>
|
||||
<string name="common_get_started">Commencer</string>
|
||||
<string name="common_go_to_provider">Aller au fournisseur</string>
|
||||
<string name="common_go_to_token">Aller au jeton</string>
|
||||
<string name="common_got_it">Compris</string>
|
||||
|
|
@ -949,6 +950,7 @@
|
|||
<string name="settings_forget_wallet_footer">Cela supprimera le portefeuille de l\'application. Le portefeuille lui-même peut être ajouté à nouveau.</string>
|
||||
<string name="settings_wallet_name_title">Nom</string>
|
||||
<string name="stake_token_description">Faites travailler votre jeton pour vous</string>
|
||||
<string name="staking_account_initialization_message">Pour commencer à staker, votre compte TON doit être activé avec une transaction de 1 TON. Les fonds restent sur votre compte, car cette étape sert uniquement à l\'activer pour le staking.</string>
|
||||
<string name="staking_amount_requirement_error">Le montant à staker doit être au moins %s</string>
|
||||
<string name="staking_amount_tron_integer_error">Le montant du staking sera arrondi à %1$s TRX en raison des règles du réseau.</string>
|
||||
<string name="staking_amount_tron_integer_error_unstaking">Le montant d\'annulation du staking sera arrondi à %1$s TRX en raison des règles du réseau.</string>
|
||||
|
|
|
|||
|
|
@ -1318,6 +1318,7 @@
|
|||
<string name="user_wallet_list_rename_popup_title">Переименование кошелька</string>
|
||||
<string name="user_wallet_list_unlock_all">Разблокировать все</string>
|
||||
<string name="user_wallet_list_unlock_all_with">Разблокировать все с %s</string>
|
||||
<string name="visa_onboarding_account_activation_navigation_title">Активация аккаунта</string>
|
||||
<string name="visa_onboarding_pin_not_accepted">ПИН не принят. Попробуйте ещё раз или введите другой код.</string>
|
||||
<string name="wallet_add_support_title">Что выбрать?</string>
|
||||
<string name="wallet_balance_blockchain_unreachable_try_later">Блокчейн недоступен. Попробуйте позже.</string>
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@
|
|||
<string name="common_fee_selector_title">Швидкість та комісія</string>
|
||||
<string name="common_finish">Завершити</string>
|
||||
<string name="common_generate_addresses">Синхронізувати адреси</string>
|
||||
<string name="common_get_started">Почати</string>
|
||||
<string name="common_go_to_provider">Перейти до провайдера</string>
|
||||
<string name="common_go_to_token">Перейти до токену</string>
|
||||
<string name="common_got_it">Зrozуміло</string>
|
||||
|
|
|
|||
|
|
@ -969,6 +969,7 @@
|
|||
</plurals>
|
||||
<string name="onramp_timing_minutes">%s min</string>
|
||||
<string name="onramp_title_available_from">Available from</string>
|
||||
<string name="onramp_title_available_up_to">Available up to</string>
|
||||
<string name="onramp_title_you_get">You get</string>
|
||||
<string name="onramp_tos_external_providers">Service is provided by an external provider. \nTangem is not responsible.</string>
|
||||
<string name="onramp_transaction_status_footer_text">You can close this screen and check the transaction status on the token details screen.</string>
|
||||
|
|
@ -1370,6 +1371,8 @@
|
|||
<string name="tangem_pay_status_pending">Pending</string>
|
||||
<string name="tangem_pay_transaction_declined_notification_text">The bank rejected this transaction request.</string>
|
||||
<string name="tangem_pay_transaction_fee_notification_text">This fee goes to cover the cost of handling your transfer.</string>
|
||||
<string name="tangem_pay_unfreeze_card_alert_body">Keep using your money. You can freeze anytime.</string>
|
||||
<string name="tangem_pay_unfreeze_card_alert_title">Unfreeze your card?</string>
|
||||
<string name="tangem_pay_withdrawal">Withdrawal</string>
|
||||
<string name="tangempay_card_details_add_funds">Add funds</string>
|
||||
<string name="tangempay_card_details_add_funds_subtitle">Top-up options</string>
|
||||
|
|
@ -1394,6 +1397,7 @@
|
|||
<string name="tangempay_card_details_reveal_text">Reveal</string>
|
||||
<string name="tangempay_card_details_swap_description">Swap any asset in your portfolio for card</string>
|
||||
<string name="tangempay_card_details_title">Card details</string>
|
||||
<string name="tangempay_card_details_unfreeze_card">Unfreeze Card</string>
|
||||
<string name="tangempay_issue_card_notification_title">Create account and issue a card</string>
|
||||
<string name="tangempay_kyc_card_ready_notification_title">Tangem Pay</string>
|
||||
<string name="tangempay_kyc_in_progress_notification_button">View Status</string>
|
||||
|
|
|
|||
|
|
@ -6,18 +6,21 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -34,6 +37,7 @@ data class SmallButtonConfig(
|
|||
val onClick: () -> Unit,
|
||||
val icon: TangemButtonIconPosition = TangemButtonIconPosition.None,
|
||||
val isEnabled: Boolean = true,
|
||||
val isLoading: Boolean = false,
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
@ -68,7 +72,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
|||
label = "Update background color",
|
||||
)
|
||||
|
||||
Row(
|
||||
Box(
|
||||
modifier = modifier
|
||||
.defaultMinSize(
|
||||
minWidth = TangemTheme.dimens.size46,
|
||||
|
|
@ -79,7 +83,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
|||
color = backgroundColor,
|
||||
shape = shape,
|
||||
)
|
||||
.clickable(enabled = config.isEnabled, onClick = config.onClick)
|
||||
.clickable(enabled = !config.isLoading && config.isEnabled, onClick = config.onClick)
|
||||
.padding(
|
||||
paddingValues = when (config.icon) {
|
||||
is TangemButtonIconPosition.None -> PaddingValues(
|
||||
|
|
@ -95,42 +99,54 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
|||
)
|
||||
},
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
ContentContainer(
|
||||
iconPosition = config.icon,
|
||||
text = {
|
||||
val textColor by animateColorAsState(
|
||||
targetValue = when {
|
||||
!config.isEnabled -> TangemTheme.colors.text.disabled
|
||||
isPrimary -> TangemTheme.colors.text.primary2
|
||||
else -> TangemTheme.colors.text.primary1
|
||||
},
|
||||
label = "Update text color",
|
||||
)
|
||||
if (config.isLoading) {
|
||||
CircularProgressIndicator(
|
||||
strokeWidth = TangemTheme.dimens.size2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier.size(TangemTheme.dimens.size16),
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.conditional(config.isLoading) { alpha(0f) },
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
ContentContainer(
|
||||
iconPosition = config.icon,
|
||||
text = {
|
||||
val textColor by animateColorAsState(
|
||||
targetValue = when {
|
||||
!config.isEnabled -> TangemTheme.colors.text.disabled
|
||||
isPrimary -> TangemTheme.colors.text.primary2
|
||||
else -> TangemTheme.colors.text.primary1
|
||||
},
|
||||
label = "Update text color",
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing4),
|
||||
text = config.text.resolveReference(),
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.button,
|
||||
)
|
||||
},
|
||||
icon = { iconResId ->
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size16),
|
||||
painter = painterResource(id = iconResId),
|
||||
tint = if (config.isEnabled) {
|
||||
TangemTheme.colors.icon.secondary
|
||||
} else {
|
||||
TangemTheme.colors.icon.inactive
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing4),
|
||||
text = config.text.resolveReference(),
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.button,
|
||||
)
|
||||
},
|
||||
icon = { iconResId ->
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size16),
|
||||
painter = painterResource(id = iconResId),
|
||||
tint = if (config.isEnabled) {
|
||||
TangemTheme.colors.icon.secondary
|
||||
} else {
|
||||
TangemTheme.colors.icon.inactive
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -172,6 +188,7 @@ private fun ButtonsSample() {
|
|||
)
|
||||
PrimarySmallButton(config = config)
|
||||
SecondarySmallButton(config = config.copy(text = TextReference.Str(value = "Add")))
|
||||
SecondarySmallButton(config = config.copy(text = TextReference.Str(value = "Add"), isLoading = true))
|
||||
SecondarySmallButton(
|
||||
config = config.copy(
|
||||
text = TextReference.Str(value = "Rating"),
|
||||
|
|
@ -191,5 +208,12 @@ private fun ButtonsSample() {
|
|||
isEnabled = false,
|
||||
),
|
||||
)
|
||||
SecondarySmallButton(
|
||||
config = config.copy(
|
||||
text = TextReference.Str(value = "Add token"),
|
||||
icon = TangemButtonIconPosition.Start(iconResId = R.drawable.ic_plus_24),
|
||||
isLoading = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,8 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -133,6 +135,13 @@ fun ActionBaseButton(
|
|||
}
|
||||
}
|
||||
.clip(shape)
|
||||
.semantics {
|
||||
contentDescription = if (config.shouldDimContent) {
|
||||
"Action button is dimmed"
|
||||
} else {
|
||||
"Action button is not dimmed"
|
||||
}
|
||||
}
|
||||
.combinedClickable(
|
||||
enabled = config.isEnabled,
|
||||
onClick = config.onClick,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.themedColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.PopUpMenuTestTags
|
||||
|
|
@ -28,7 +29,7 @@ fun TangemDropdownItem(item: TangemDropdownMenuItem, dismissParent: () -> Unit,
|
|||
.padding(vertical = TangemTheme.dimens.spacing8, horizontal = TangemTheme.dimens.spacing16)
|
||||
.testTag(PopUpMenuTestTags.BUTTON),
|
||||
text = item.title.resolveReference(),
|
||||
style = TangemTheme.typography.button.copy(color = item.textColorProvider()),
|
||||
style = TangemTheme.typography.button.copy(color = item.textColor.resolveReference()),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ private fun Preview_TokenDetailsAppBarDropdownItem() {
|
|||
dismissParent = {},
|
||||
item = TangemDropdownMenuItem(
|
||||
title = TextReference.Res(id = R.string.token_details_hide_token),
|
||||
textColorProvider = { TangemTheme.colors.text.warning },
|
||||
textColor = themedColor { TangemTheme.colors.text.warning },
|
||||
onClick = { },
|
||||
),
|
||||
)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue