diff --git a/.bundle/config b/.bundle/config
deleted file mode 100644
index 2369228816..0000000000
--- a/.bundle/config
+++ /dev/null
@@ -1,2 +0,0 @@
----
-BUNDLE_PATH: "vendor/bundle"
diff --git a/.gitignore b/.gitignore
index 345207b4eb..c5484c9083 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,19 +38,10 @@ app/src/external/google-services.json
# LayoutInspector files
/captures
-# bundler
-/vendor
-
# OS-specific files
.DS_Store
.DS_Store?
-# fastlane files
-**/fastlane/report.xml
-**/fastlane/Preview.html
-**/fastlane/screenshots
-**/fastlane/test_output
-
# Kotlin Plugin
.kotlin/
find-latest-release-branch.output
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 5647ad7cfc..29b9efc572 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -75,6 +75,10 @@
+
+
+
+
diff --git a/Gemfile b/Gemfile
deleted file mode 100644
index cdd3a6b349..0000000000
--- a/Gemfile
+++ /dev/null
@@ -1,6 +0,0 @@
-source "https://rubygems.org"
-
-gem "fastlane"
-
-plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
-eval_gemfile(plugins_path) if File.exist?(plugins_path)
diff --git a/Gemfile.lock b/Gemfile.lock
deleted file mode 100644
index 535cdb9a32..0000000000
--- a/Gemfile.lock
+++ /dev/null
@@ -1,220 +0,0 @@
-GEM
- remote: https://rubygems.org/
- specs:
- CFPropertyList (3.0.6)
- rexml
- addressable (2.8.1)
- public_suffix (>= 2.0.2, < 6.0)
- artifactory (3.0.15)
- atomos (0.1.3)
- aws-eventstream (1.2.0)
- aws-partitions (1.702.0)
- aws-sdk-core (3.170.0)
- aws-eventstream (~> 1, >= 1.0.2)
- aws-partitions (~> 1, >= 1.651.0)
- aws-sigv4 (~> 1.5)
- jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.62.0)
- aws-sdk-core (~> 3, >= 3.165.0)
- aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.119.0)
- aws-sdk-core (~> 3, >= 3.165.0)
- aws-sdk-kms (~> 1)
- aws-sigv4 (~> 1.4)
- aws-sigv4 (1.5.2)
- aws-eventstream (~> 1, >= 1.0.2)
- babosa (1.0.4)
- claide (1.1.0)
- colored (1.2)
- colored2 (3.1.2)
- commander (4.6.0)
- highline (~> 2.0.0)
- declarative (0.0.20)
- digest-crc (0.6.4)
- rake (>= 12.0.0, < 14.0.0)
- domain_name (0.5.20190701)
- unf (>= 0.0.5, < 1.0.0)
- dotenv (2.8.1)
- emoji_regex (3.2.3)
- excon (0.98.0)
- faraday (1.10.3)
- faraday-em_http (~> 1.0)
- faraday-em_synchrony (~> 1.0)
- faraday-excon (~> 1.1)
- faraday-httpclient (~> 1.0)
- faraday-multipart (~> 1.0)
- faraday-net_http (~> 1.0)
- faraday-net_http_persistent (~> 1.0)
- faraday-patron (~> 1.0)
- faraday-rack (~> 1.0)
- faraday-retry (~> 1.0)
- ruby2_keywords (>= 0.0.4)
- faraday-cookie_jar (0.0.7)
- faraday (>= 0.8.0)
- http-cookie (~> 1.0.0)
- faraday-em_http (1.0.0)
- faraday-em_synchrony (1.0.0)
- faraday-excon (1.1.0)
- faraday-httpclient (1.0.1)
- faraday-multipart (1.0.4)
- multipart-post (~> 2)
- faraday-net_http (1.0.1)
- faraday-net_http_persistent (1.2.0)
- faraday-patron (1.0.0)
- faraday-rack (1.0.0)
- faraday-retry (1.0.3)
- faraday_middleware (1.2.0)
- faraday (~> 1.0)
- fastimage (2.2.6)
- fastlane (2.211.0)
- CFPropertyList (>= 2.3, < 4.0.0)
- addressable (>= 2.8, < 3.0.0)
- artifactory (~> 3.0)
- aws-sdk-s3 (~> 1.0)
- babosa (>= 1.0.3, < 2.0.0)
- bundler (>= 1.12.0, < 3.0.0)
- colored
- commander (~> 4.6)
- dotenv (>= 2.1.1, < 3.0.0)
- emoji_regex (>= 0.1, < 4.0)
- excon (>= 0.71.0, < 1.0.0)
- faraday (~> 1.0)
- faraday-cookie_jar (~> 0.0.6)
- faraday_middleware (~> 1.0)
- fastimage (>= 2.1.0, < 3.0.0)
- gh_inspector (>= 1.1.2, < 2.0.0)
- google-apis-androidpublisher_v3 (~> 0.3)
- google-apis-playcustomapp_v1 (~> 0.1)
- google-cloud-storage (~> 1.31)
- highline (~> 2.0)
- json (< 3.0.0)
- jwt (>= 2.1.0, < 3)
- mini_magick (>= 4.9.4, < 5.0.0)
- multipart-post (~> 2.0.0)
- naturally (~> 2.2)
- optparse (~> 0.1.1)
- plist (>= 3.1.0, < 4.0.0)
- rubyzip (>= 2.0.0, < 3.0.0)
- security (= 0.1.3)
- simctl (~> 1.6.3)
- terminal-notifier (>= 2.0.0, < 3.0.0)
- terminal-table (>= 1.4.5, < 2.0.0)
- tty-screen (>= 0.6.3, < 1.0.0)
- tty-spinner (>= 0.8.0, < 1.0.0)
- word_wrap (~> 1.0.0)
- xcodeproj (>= 1.13.0, < 2.0.0)
- xcpretty (~> 0.3.0)
- xcpretty-travis-formatter (>= 0.0.3)
- fastlane-plugin-firebase_app_distribution (0.4.2)
- gh_inspector (1.1.3)
- google-apis-androidpublisher_v3 (0.33.0)
- google-apis-core (>= 0.9.1, < 2.a)
- google-apis-core (0.10.0)
- addressable (~> 2.5, >= 2.5.1)
- googleauth (>= 0.16.2, < 2.a)
- httpclient (>= 2.8.1, < 3.a)
- mini_mime (~> 1.0)
- representable (~> 3.0)
- retriable (>= 2.0, < 4.a)
- rexml
- webrick
- google-apis-iamcredentials_v1 (0.16.0)
- google-apis-core (>= 0.9.1, < 2.a)
- google-apis-playcustomapp_v1 (0.12.0)
- google-apis-core (>= 0.9.1, < 2.a)
- google-apis-storage_v1 (0.19.0)
- google-apis-core (>= 0.9.0, < 2.a)
- google-cloud-core (1.6.0)
- google-cloud-env (~> 1.0)
- google-cloud-errors (~> 1.0)
- google-cloud-env (1.6.0)
- faraday (>= 0.17.3, < 3.0)
- google-cloud-errors (1.3.0)
- google-cloud-storage (1.44.0)
- addressable (~> 2.8)
- digest-crc (~> 0.4)
- google-apis-iamcredentials_v1 (~> 0.1)
- google-apis-storage_v1 (~> 0.19.0)
- google-cloud-core (~> 1.6)
- googleauth (>= 0.16.2, < 2.a)
- mini_mime (~> 1.0)
- googleauth (1.3.0)
- faraday (>= 0.17.3, < 3.a)
- jwt (>= 1.4, < 3.0)
- memoist (~> 0.16)
- multi_json (~> 1.11)
- os (>= 0.9, < 2.0)
- signet (>= 0.16, < 2.a)
- highline (2.0.3)
- http-cookie (1.0.5)
- domain_name (~> 0.5)
- httpclient (2.8.3)
- jmespath (1.6.2)
- json (2.6.3)
- jwt (2.6.0)
- memoist (0.16.2)
- mini_magick (4.12.0)
- mini_mime (1.1.2)
- multi_json (1.15.0)
- multipart-post (2.0.0)
- nanaimo (0.3.0)
- naturally (2.2.1)
- optparse (0.1.1)
- os (1.1.4)
- plist (3.6.0)
- public_suffix (5.0.1)
- rake (13.0.6)
- representable (3.2.0)
- declarative (< 0.1.0)
- trailblazer-option (>= 0.1.1, < 0.2.0)
- uber (< 0.2.0)
- retriable (3.1.2)
- rexml (3.2.5)
- rouge (2.0.7)
- ruby2_keywords (0.0.5)
- rubyzip (2.3.2)
- security (0.1.3)
- signet (0.17.0)
- addressable (~> 2.8)
- faraday (>= 0.17.5, < 3.a)
- jwt (>= 1.5, < 3.0)
- multi_json (~> 1.10)
- simctl (1.6.10)
- CFPropertyList
- naturally
- terminal-notifier (2.0.0)
- terminal-table (1.8.0)
- unicode-display_width (~> 1.1, >= 1.1.1)
- trailblazer-option (0.1.2)
- tty-cursor (0.7.1)
- tty-screen (0.8.1)
- tty-spinner (0.9.3)
- tty-cursor (~> 0.7)
- uber (0.1.0)
- unf (0.1.4)
- unf_ext
- unf_ext (0.0.8.2)
- unicode-display_width (1.8.0)
- webrick (1.8.1)
- word_wrap (1.0.0)
- xcodeproj (1.22.0)
- CFPropertyList (>= 2.3.3, < 4.0)
- atomos (~> 0.1.3)
- claide (>= 1.0.2, < 2.0)
- colored2 (~> 3.1)
- nanaimo (~> 0.3.0)
- rexml (~> 3.2.4)
- xcpretty (0.3.0)
- rouge (~> 2.0.7)
- xcpretty-travis-formatter (1.0.1)
- xcpretty (~> 0.2, >= 0.0.7)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- fastlane
- fastlane-plugin-firebase_app_distribution
-
-BUNDLED WITH
- 2.5.23
diff --git a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt
index 8a40e56185..96c353f84c 100644
--- a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt
+++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt
@@ -139,10 +139,11 @@ abstract class BaseTestCase : TestCase(
return ApplicationInjectionExecutionRule(
toggleStates = mapOf(
"NEW_TOKEN_RECEIVE_ENABLED" to true,
- "WALLET_BALANCE_FETCHER_ENABLED" to true,
- "SWAP_REDESIGN_ENABLED" to true,
+ "SWAP_REDESIGN_ENABLED" to false,
"NEW_ONRAMP_MAIN_ENABLED" to true,
- "HOT_WALLET_ENABLED" to true
+ "HOT_WALLET_ENABLED" to true,
+ "YIELD_SUPPLY_FEATURE_ENABLED" to true,
+ "ACCOUNTS_FEATURE_ENABLED" to true
)
)
}
diff --git a/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt b/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt
index cb7ed9ff51..fd5d74d374 100644
--- a/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt
+++ b/app/src/androidTest/kotlin/com/tangem/common/constants/TestConstants.kt
@@ -8,6 +8,7 @@ object TestConstants {
const val ETHEREUM_RECIPIENT_ADDRESS = "0x5aa711F440Eb6d4361148bBD89d03464628ace84"
const val ETHEREUM_RECIPIENT_SHORTENED_ADDRESS = "0x5aa711F440Eb6d43...89d03464628ace84"
const val BITCOIN_ADDRESS = "bc1qtg9aa6jcpqtvun0pe0uct7sxm8nq2nsxfmfxm3"
+ const val BITCOIN_RECIPIENT_ADDRESS = "bc1qt90qc0na7z05nh63kyd78tujfc8vqv6sl7e4a9"
const val CARDANO_ADDRESS =
"addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p"
const val SOLANA_RECIPIENT_ADDRESS = "5fcy9woa8Di1QHcce65CsV3XKrxdB2pD4HJx5xx82ipM"
@@ -30,6 +31,7 @@ object TestConstants {
const val XRP_ACTIVATED_RECIPIENT_ADDRESS = "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"
const val DOGECOIN_RECIPIENT_ADDRESS = "DJQR3bdhBKcFGMHX2BkMCkrMFApNWNzr6V"
const val DOGECOIN_ADDRESS = "DJ2TaZ5vvp3mBLugUpKjVM3pRBLi4uYaqz"
+ const val TERRA_RECIPIENT_ADDRESS = "terra148dmp5ccazcwdmrcpvqz5rprnn886kemqen3tj"
const val WAIT_UNTIL_TIMEOUT = 20_000L
const val WAIT_UNTIL_TIMEOUT_LONG = 30_000L
diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/DialogScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/DialogScenarios.kt
index bca0130819..57d70b597e 100644
--- a/app/src/androidTest/kotlin/com/tangem/scenarios/DialogScenarios.kt
+++ b/app/src/androidTest/kotlin/com/tangem/scenarios/DialogScenarios.kt
@@ -9,6 +9,7 @@ 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.onDataNotLoadedDialog
import com.tangem.screens.onFailedTransactionDialog
import io.qameta.allure.kotlin.Allure.step
@@ -76,4 +77,16 @@ fun BaseTestCase.checkActionIsUnavailableDialog() {
step("Assert 'Action is unavailable' dialog 'Ok' button is displayed") {
onActionIsUnavailableDialog { okButton.assertIsDisplayed() }
}
+}
+
+fun BaseTestCase.checkDataNotLoadedDialog() {
+ step("Assert 'The data has not loaded yet' dialog title is displayed") {
+ onDataNotLoadedDialog { title.assertIsDisplayed() }
+ }
+ step("Assert 'The data has not loaded yet' dialog text is displayed") {
+ onDataNotLoadedDialog { text.assertIsDisplayed() }
+ }
+ step("Assert 'The data has not loaded yet' dialog 'Ok' button is displayed") {
+ onDataNotLoadedDialog { okButton.assertIsDisplayed() }
+ }
}
\ No newline at end of file
diff --git a/app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt b/app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt
index 6bcaa775de..066600df5e 100644
--- a/app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt
+++ b/app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt
@@ -54,6 +54,10 @@ fun BaseTestCase.checkNetworkFeeBlock(currentFeeAmount: String, withFeeSelector:
step("Assert select fee icon is displayed") {
onSendConfirmScreen { selectFeeIcon.assertIsDisplayed() }
}
+ } else {
+ step("Assert select fee icon is not displayed") {
+ onSendConfirmScreen { selectFeeIcon.assertIsNotDisplayed() }
+ }
}
}
@@ -138,4 +142,29 @@ fun BaseTestCase.checkRecentAddressItem(address: String, description: String?) {
recentAddressItem(recipientAddress = address, description = description).assertIsDisplayed()
}
}
+}
+
+fun BaseTestCase.checkCustomFeeTooltip(title: String, tooltip: String) {
+ step("Click on tooltip icon for '$title'") {
+ waitForIdle()
+ onSendSelectNetworkFeeBottomSheet { tooltipIcon(title).performClick() }
+ }
+ step("Check '$title' tooltip text") {
+ onSendSelectNetworkFeeBottomSheet { tooltipText(tooltip).assertIsDisplayed() }
+ }
+ step("Click on tooltip icon again to close tooltip") {
+ onSendSelectNetworkFeeBottomSheet { tooltipIcon(title).performClick() }
+ }
+}
+
+fun BaseTestCase.checkChangesInInputTextField(title: String, newValue: String, addition: String = "") {
+ step("Click on '$title' input text field") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(title).performClick() }
+ }
+ step("Type '$newValue' in '$title' input text field") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(title).performTextReplacement(newValue) }
+ }
+ step("Assert '$title' value: '$newValue + $addition'") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(title).assertTextContains(newValue + addition) }
+ }
}
\ No newline at end of file
diff --git a/app/src/androidTest/kotlin/com/tangem/screens/DataNotLoadedDialogPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/DataNotLoadedDialogPageObject.kt
new file mode 100644
index 0000000000..e7efab995c
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/tangem/screens/DataNotLoadedDialogPageObject.kt
@@ -0,0 +1,36 @@
+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 DataNotLoadedDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
+ ComposeScreen(semanticsProvider = semanticsProvider) {
+
+ val title: KNode = child {
+ hasTestTag(BaseDialogTestTags.TITLE)
+ hasText(getResourceString(CommonUIR.string.action_buttons_service_loading_alert_title))
+ useUnmergedTree = true
+ }
+
+ val text: KNode = child {
+ hasTestTag(BaseDialogTestTags.TEXT)
+ hasText(getResourceString(CommonUIR.string.action_buttons_service_loading_alert_message))
+ useUnmergedTree = true
+ }
+
+ val okButton: KNode = child {
+ hasTestTag(BaseButtonTestTags.BUTTON)
+ hasText(getResourceString(R.string.common_ok))
+ }
+}
+
+internal fun BaseTestCase.onDataNotLoadedDialog(function: DataNotLoadedDialogPageObject.() -> Unit) =
+ onComposeScreen(composeTestRule, function)
\ No newline at end of file
diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SelectNetworkFeePageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SelectNetworkFeePageObject.kt
deleted file mode 100644
index 35a55722df..0000000000
--- a/app/src/androidTest/kotlin/com/tangem/screens/SelectNetworkFeePageObject.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.tangem.screens
-
-import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
-import com.tangem.common.BaseTestCase
-import com.tangem.core.ui.test.SelectNetworkFeeBottomSheetTestTags
-import com.tangem.core.ui.test.TopAppBarTestTags
-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
-
-class SelectNetworkFeePageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
- ComposeScreen(semanticsProvider = semanticsProvider) {
-
- val title: KNode = child {
- hasTestTag(TopAppBarTestTags.TITLE)
- hasText(getResourceString(R.string.common_fee_selector_title))
- useUnmergedTree = true
- }
-
- val marketSelectorItem: KNode = child {
- hasTestTag(SelectNetworkFeeBottomSheetTestTags.SELECTOR_ITEM)
- hasAnyChild(withText(getResourceString(R.string.common_fee_selector_option_market)))
- useUnmergedTree = true
- }
-
- val fastSelectorItem: KNode = child {
- hasTestTag(SelectNetworkFeeBottomSheetTestTags.SELECTOR_ITEM)
- hasAnyChild(withText(getResourceString(R.string.common_fee_selector_option_fast)))
- useUnmergedTree = true
- }
-
- val readMoreTextBlock: KNode = child {
- hasTestTag(SelectNetworkFeeBottomSheetTestTags.READ_MORE_TEXT)
- useUnmergedTree = true
- }
-}
-
-internal fun BaseTestCase.onSelectNetworkFeeBottomSheet(function: SelectNetworkFeePageObject.() -> Unit) =
- onComposeScreen(composeTestRule, function)
\ No newline at end of file
diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SendAddressPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SendAddressPageObject.kt
index aa4b7b3647..0458682ee0 100644
--- a/app/src/androidTest/kotlin/com/tangem/screens/SendAddressPageObject.kt
+++ b/app/src/androidTest/kotlin/com/tangem/screens/SendAddressPageObject.kt
@@ -40,8 +40,7 @@ class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
}
val addressTextFieldHint: KNode = child {
- hasParent(withTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD))
- useUnmergedTree = true
+ hasTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD)
}
fun recipientNetworkCaution(network: String): KNode = child {
@@ -95,10 +94,8 @@ class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
hasAnyDescendant(withText(description, substring = true))
}
if (isMyWallet) {
- hasAnySibling(withText(getResourceString(CoreUiR.string.send_recipient_wallets_title)))
hasAnyDescendant(withText(getResourceString(CoreUiR.string.manage_tokens_network_selector_wallet)))
} else {
- hasAnySibling(withText(getResourceString(CoreUiR.string.send_recent_transactions)))
hasAnyDescendant(withTestTag(SendAddressScreenTestTags.RECENT_ADDRESS_TRANSACTION_ICON))
}
}
@@ -119,8 +116,7 @@ class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
}
val destinationTagTextFieldHint: KNode = child {
- hasParent(withTestTag(SendAddressScreenTestTags.DESTINATION_TAG_TEXT_FIELD))
- useUnmergedTree = true
+ hasTestTag(SendAddressScreenTestTags.DESTINATION_TAG_TEXT_FIELD)
}
val destinationTagBlockText: KNode = child {
diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SendSelectNetworkFeeBottomSheetPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SendSelectNetworkFeeBottomSheetPageObject.kt
new file mode 100644
index 0000000000..977765ac50
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/tangem/screens/SendSelectNetworkFeeBottomSheetPageObject.kt
@@ -0,0 +1,102 @@
+package com.tangem.screens
+
+import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
+import com.tangem.common.BaseTestCase
+import com.tangem.core.ui.test.BaseBottomSheetTestTags
+import com.tangem.core.ui.test.BaseButtonTestTags
+import com.tangem.core.ui.test.SelectNetworkFeeBottomSheetTestTags
+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.hasTestTag as withTestTag
+import androidx.compose.ui.test.hasText as withText
+
+class SendSelectNetworkFeeBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
+ ComposeScreen(semanticsProvider = semanticsProvider) {
+
+ val chooseSpeedTitle: KNode = child {
+ hasTestTag(BaseBottomSheetTestTags.TITLE)
+ hasText(getResourceString(R.string.fee_selector_choose_speed_title))
+ useUnmergedTree = true
+ }
+
+ fun regularFeeSelectorItem(title: String): KNode = child {
+ hasTestTag(SelectNetworkFeeBottomSheetTestTags.REGULAR_FEE_ITEM)
+ hasAnyDescendant(withText(title))
+ hasAnyDescendant(withTestTag(SelectNetworkFeeBottomSheetTestTags.REGULAR_ITEM_ICON))
+ hasAnyDescendant(withTestTag(SelectNetworkFeeBottomSheetTestTags.REGULAR_ITEM_TITLE))
+ hasAnyDescendant(withTestTag(SelectNetworkFeeBottomSheetTestTags.TOKEN_AMOUNT))
+ hasAnyDescendant(withTestTag(SelectNetworkFeeBottomSheetTestTags.FIAT_AMOUNT))
+ hasAnyDescendant(withTestTag(SelectNetworkFeeBottomSheetTestTags.DOT_SIGN))
+ useUnmergedTree = true
+ }
+
+ val customSelectorItem: KNode = child {
+ hasTestTag(SelectNetworkFeeBottomSheetTestTags.CUSTOM_FEE_ITEM)
+ hasAnyDescendant(withText(getResourceString(R.string.common_custom)))
+ hasAnyDescendant(withTestTag(SelectNetworkFeeBottomSheetTestTags.CUSTOM_ITEM_ICON))
+ hasAnyDescendant(withTestTag(SelectNetworkFeeBottomSheetTestTags.CUSTOM_ITEM_TITLE))
+ useUnmergedTree = true
+ }
+
+ fun customInputItem(title: String, hasFiatAmount: Boolean = false): KNode = child {
+ hasTestTag(SelectNetworkFeeBottomSheetTestTags.CUSTOM_INPUT_ITEM)
+ hasAnyDescendant(withText(title))
+ hasAnyDescendant(withTestTag(SelectNetworkFeeBottomSheetTestTags.CUSTOM_INPUT_ITEM_TITLE))
+ hasAnyDescendant(withTestTag(SelectNetworkFeeBottomSheetTestTags.CUSTOM_INPUT_ITEM_TOOLTIP_ICON))
+ hasAnyDescendant(withTestTag(SelectNetworkFeeBottomSheetTestTags.CUSTOM_INPUT_ITEM_INPUT_TEXT_FIELD))
+ useUnmergedTree = true
+ if (hasFiatAmount) {
+ hasAnyDescendant(withTestTag(SelectNetworkFeeBottomSheetTestTags.CUSTOM_INPUT_ITEM_FIAT_AMOUNT))
+ }
+ }
+
+ val nonceInputItem: KNode = child {
+ hasTestTag(SelectNetworkFeeBottomSheetTestTags.NONCE_INPUT_ITEM)
+ hasAnyDescendant(withText(getResourceString(R.string.send_nonce)))
+ hasAnyDescendant(withText(getResourceString(R.string.send_nonce_hint)))
+ useUnmergedTree = true
+ }
+
+ fun tooltipIcon(title: String): KNode = child {
+ hasAnySibling(withText(title))
+ useUnmergedTree = true
+ }
+
+ fun tooltipText(text: String): KNode = child {
+ hasText(text)
+ useUnmergedTree = true
+ }
+
+ private fun inputTextField(title: String): KNode = child {
+ hasTestTag(SelectNetworkFeeBottomSheetTestTags.CUSTOM_INPUT_ITEM_INPUT_TEXT_FIELD)
+ hasAnySibling(withText(title))
+ useUnmergedTree = true
+ }
+
+ fun inputTextFieldValue(title: String): KNode = inputTextField(title).child {
+ hasParent(withTestTag(SelectNetworkFeeBottomSheetTestTags.CUSTOM_INPUT_ITEM_INPUT_TEXT_FIELD))
+ useUnmergedTree = true
+ }
+
+ val nonceInputTextField: KNode = child {
+ hasTestTag(SelectNetworkFeeBottomSheetTestTags.NONCE_INPUT_TEXT_FIELD)
+ useUnmergedTree = true
+ }
+
+ val customInputItemFiatAmount: KNode = child {
+ hasTestTag(SelectNetworkFeeBottomSheetTestTags.CUSTOM_INPUT_ITEM_FIAT_AMOUNT)
+ useUnmergedTree = true
+ }
+
+ val doneButton: KNode = child {
+ hasTestTag(BaseButtonTestTags.TEXT)
+ hasText(getResourceString(R.string.common_done))
+ useUnmergedTree = true
+ }
+}
+
+internal fun BaseTestCase.onSendSelectNetworkFeeBottomSheet(function: SendSelectNetworkFeeBottomSheetPageObject.() -> Unit) =
+ onComposeScreen(composeTestRule, function)
\ No newline at end of file
diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SwapSelectNetworkFeeBottomSheetPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SwapSelectNetworkFeeBottomSheetPageObject.kt
new file mode 100644
index 0000000000..6705194521
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/tangem/screens/SwapSelectNetworkFeeBottomSheetPageObject.kt
@@ -0,0 +1,39 @@
+package com.tangem.screens
+
+import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
+import com.tangem.common.BaseTestCase
+import com.tangem.core.ui.test.SelectNetworkFeeBottomSheetTestTags
+import com.tangem.wallet.R
+import io.github.kakaocup.compose.node.element.ComposeScreen
+import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
+import io.github.kakaocup.compose.node.element.KNode
+import io.github.kakaocup.kakao.common.utilities.getResourceString
+
+class SwapSelectNetworkFeeBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
+ ComposeScreen(semanticsProvider = semanticsProvider) {
+
+ val title: KNode = child {
+ hasText(getResourceString(R.string.fee_selector_choose_speed_title))
+ useUnmergedTree = true
+ }
+
+ val marketSelectorItem: KNode = child {
+ hasTestTag(SelectNetworkFeeBottomSheetTestTags.REGULAR_ITEM_TITLE)
+ hasText(getResourceString(R.string.common_fee_selector_option_market))
+ useUnmergedTree = true
+ }
+
+ val fastSelectorItem: KNode = child {
+ hasTestTag(SelectNetworkFeeBottomSheetTestTags.REGULAR_ITEM_TITLE)
+ hasText(getResourceString(R.string.common_fee_selector_option_fast))
+ useUnmergedTree = true
+ }
+
+ val readMoreTextBlock: KNode = child {
+ hasTestTag(SelectNetworkFeeBottomSheetTestTags.LEARN_MORE_TEXT)
+ useUnmergedTree = true
+ }
+}
+
+internal fun BaseTestCase.onSwapSelectNetworkFeeBottomSheet(function: SwapSelectNetworkFeeBottomSheetPageObject.() -> Unit) =
+ onComposeScreen(composeTestRule, function)
\ No newline at end of file
diff --git a/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt
index d2f4fa7b1b..188c9ece2e 100644
--- a/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt
+++ b/app/src/androidTest/kotlin/com/tangem/screens/SwapTokenPageObject.kt
@@ -29,7 +29,12 @@ class SwapTokenPageObject(semanticsProvider: SemanticsNodeInteractionsProvider)
}
val networkFeeBlock: KNode = child {
- hasTestTag(BaseBlockTestTags.BLOCK)
+ hasTestTag(FeeSelectorBlockTestTags.SELECTOR_BLOCK)
+ useUnmergedTree = true
+ }
+
+ val selectFeeIcon: KNode = child {
+ hasTestTag(FeeSelectorBlockTestTags.SELECT_FEE_ICON)
useUnmergedTree = true
}
diff --git a/app/src/androidTest/kotlin/com/tangem/tests/OrganizeTokensTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/OrganizeTokensTest.kt
index e4ac654ef2..fb132f5e1d 100644
--- a/app/src/androidTest/kotlin/com/tangem/tests/OrganizeTokensTest.kt
+++ b/app/src/androidTest/kotlin/com/tangem/tests/OrganizeTokensTest.kt
@@ -113,8 +113,8 @@ class OrganizeTokensTest : BaseTestCase() {
}
step("Check positions of tokens on 'Organize tokens' screen") {
onOrganizeTokensScreen {
- tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed()
- tokenWithTitleAndPosition(ethereumTitle, 2).assertIsDisplayed()
+ tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed()
+ tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed()
}
}
}
@@ -200,10 +200,10 @@ class OrganizeTokensTest : BaseTestCase() {
}
step("Check positions of tokens on 'Organize tokens' screen") {
onOrganizeTokensScreen {
- tokenWithTitleAndPosition(bitcoinTitle, 1).assertIsDisplayed()
- tokenWithTitleAndPosition(ethereumTitle, 2).assertIsDisplayed()
- tokenWithTitleAndPosition(polygonTitle, 3).assertIsDisplayed()
- tokenWithTitleAndPosition(polExMaticTitle, 4).assertIsDisplayed()
+ tokenWithTitleAndPosition(bitcoinTitle, 0).assertIsDisplayed()
+ tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed()
+ tokenWithTitleAndPosition(polygonTitle, 2).assertIsDisplayed()
+ tokenWithTitleAndPosition(polExMaticTitle, 3).assertIsDisplayed()
}
}
step("Click 'By Balance' button") {
@@ -213,10 +213,10 @@ class OrganizeTokensTest : BaseTestCase() {
}
step("Check positions of tokens by balance on 'Organize tokens' screen") {
onOrganizeTokensScreen {
- tokenWithTitleAndPosition(ethereumTitle, 1).assertIsDisplayed()
- tokenWithTitleAndPosition(polExMaticTitle, 2).assertIsDisplayed()
- tokenWithTitleAndPosition(polygonTitle, 3).assertIsDisplayed()
- tokenWithTitleAndPosition(bitcoinTitle, 4).assertIsDisplayed()
+ tokenWithTitleAndPosition(ethereumTitle, 0).assertIsDisplayed()
+ tokenWithTitleAndPosition(polExMaticTitle, 1).assertIsDisplayed()
+ tokenWithTitleAndPosition(polygonTitle, 2).assertIsDisplayed()
+ tokenWithTitleAndPosition(bitcoinTitle, 3).assertIsDisplayed()
}
}
step("Click 'Apply' button") {
diff --git a/app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt
index d237730598..f57ae57734 100644
--- a/app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt
+++ b/app/src/androidTest/kotlin/com/tangem/tests/SwapTokenTest.kt
@@ -84,7 +84,7 @@ class SwapTokenTest : BaseTestCase() {
}
step("Assert 'Providers' block is displayed") {
onSwapTokenScreen {
- flakySafely(WAIT_UNTIL_TIMEOUT) {
+ flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
providersBlock.assertIsDisplayed()
}
}
@@ -210,24 +210,25 @@ class SwapTokenTest : BaseTestCase() {
step("Click on 'Network fee' block") {
onSwapTokenScreen {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
- networkFeeBlock.clickWithAssertion()
+ selectFeeIcon.clickWithAssertion()
}
}
}
step("Assert 'Select fee' bottom sheet title is displayed") {
- onSelectNetworkFeeBottomSheet { title.assertIsDisplayed() }
+ onSwapSelectNetworkFeeBottomSheet { title.assertIsDisplayed() }
}
step("Assert 'Market' item is displayed") {
- onSelectNetworkFeeBottomSheet { marketSelectorItem.assertIsDisplayed() }
+ printSemanticTree(rootIndex = 1, useUnmergedTree = true)
+ onSwapSelectNetworkFeeBottomSheet { marketSelectorItem.assertIsDisplayed() }
}
step("Assert 'Fast' item is displayed") {
- onSelectNetworkFeeBottomSheet { fastSelectorItem.assertIsDisplayed() }
+ onSwapSelectNetworkFeeBottomSheet { fastSelectorItem.assertIsDisplayed() }
}
step("Assert 'Read more' text block is displayed") {
- onSelectNetworkFeeBottomSheet { readMoreTextBlock.assertIsDisplayed() }
+ onSwapSelectNetworkFeeBottomSheet { readMoreTextBlock.assertIsDisplayed() }
}
step("Click on 'Fast' item") {
- onSelectNetworkFeeBottomSheet { fastSelectorItem.assertIsDisplayed() }
+ onSwapSelectNetworkFeeBottomSheet { fastSelectorItem.assertIsDisplayed() }
}
step("Assert 'Network fee' block is displayed") {
onSwapTokenScreen {
diff --git a/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/MainScreenActionButtonsTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/MainScreenActionButtonsTest.kt
index fd03d3bd1e..e807c84361 100644
--- a/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/MainScreenActionButtonsTest.kt
+++ b/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/MainScreenActionButtonsTest.kt
@@ -6,9 +6,11 @@ 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.extensions.*
-import com.tangem.common.utils.*
+import com.tangem.common.utils.assertClipboardTextEquals
+import com.tangem.common.utils.clearClipboard
+import com.tangem.common.utils.resetWireMockScenarioState
+import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.scenarios.*
diff --git a/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt
index a531a3cc1a..d14e14e295 100644
--- a/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt
+++ b/app/src/androidTest/kotlin/com/tangem/tests/actionButtons/TokenDetailsScreenActionButtonsTest.kt
@@ -12,10 +12,6 @@ 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
@@ -84,8 +80,8 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
step("Assert 'Buy' button is not dimmed") {
onTokenDetailsScreen { buyButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) }
}
- step("Assert 'Send' button is dimmed") {
- onTokenDetailsScreen { sendButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
+ step("Assert 'Send' button is not dimmed") {
+ onTokenDetailsScreen { sendButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) }
}
step("Assert 'Swap' button is dimmed") {
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
@@ -239,7 +235,7 @@ class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
waitForIdle()
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
}
- step("Assert 'Receive' button is displayed") {
+ step("Click on 'Receive' button") {
onTokenDetailsScreen { receiveButton().performClick() }
}
step("Go to QR code bottom sheet") {
diff --git a/app/src/androidTest/kotlin/com/tangem/tests/send/feeScreen/SendFeeScreenTest.kt b/app/src/androidTest/kotlin/com/tangem/tests/send/feeScreen/SendFeeScreenTest.kt
new file mode 100644
index 0000000000..31b9035761
--- /dev/null
+++ b/app/src/androidTest/kotlin/com/tangem/tests/send/feeScreen/SendFeeScreenTest.kt
@@ -0,0 +1,441 @@
+package com.tangem.tests.send.feeScreen
+
+import com.tangem.common.BaseTestCase
+import com.tangem.common.constants.TestConstants.BITCOIN_RECIPIENT_ADDRESS
+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.TERRA_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.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 SendFeeScreenTest : BaseTestCase() {
+
+ @AllureId("4906")
+ @DisplayName("Send (Fee screen): check fee block for fee in token")
+ @Test
+ fun checkFeeBlockForFeeInTokenTest() {
+ val tokenName = "TerraClassicUSD"
+ val scenarioName = "Terra"
+ val tokenAmount = "1"
+ val feeAmount = "<$0.01"
+
+ setupHooks(
+ additionalAfterSection = {
+ resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
+ resetWireMockScenarioState(QUOTES_API_SCENARIO)
+ }
+ ).run {
+ step("Open 'Send' screen") {
+ openSendScreen(tokenName, scenarioName)
+ }
+ step("Type '$tokenAmount' in input text field") {
+ onSendScreen {
+ amountInputTextField.performClick()
+ amountInputTextField.performTextReplacement(tokenAmount)
+ }
+ }
+ step("Click on 'Next' button") {
+ onSendAddressScreen { nextButton.clickWithAssertion() }
+ }
+ step("Type recipient address") {
+ onSendAddressScreen { addressTextField.performTextReplacement(TERRA_RECIPIENT_ADDRESS) }
+ }
+ step("Click on 'Next' button") {
+ onSendScreen { nextButton.clickWithAssertion() }
+ }
+ step("Assert fee block is displayed without fee selector") {
+ checkNetworkFeeBlock(currentFeeAmount = feeAmount, withFeeSelector = false)
+ }
+ }
+ }
+
+ @AllureId("4868")
+ @DisplayName("Send (Fee screen): check fee block for fixed fee")
+ @Test
+ fun checkFeeBlockForFixedFeeTest() {
+ val tokenName = "Polkadot"
+ val tokenAmount = "1"
+ val feeAmount = "$0.05"
+
+ setupHooks(
+ additionalAfterSection = {
+ resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
+ resetWireMockScenarioState(QUOTES_API_SCENARIO)
+ }
+ ).run {
+ step("Open 'Send' screen") {
+ openSendScreen(tokenName)
+ }
+ step("Type '$tokenAmount' in input text field") {
+ onSendScreen {
+ amountInputTextField.performClick()
+ amountInputTextField.performTextReplacement(tokenAmount)
+ }
+ }
+ step("Click on 'Next' button") {
+ onSendAddressScreen { nextButton.clickWithAssertion() }
+ }
+ step("Type recipient address") {
+ onSendAddressScreen { addressTextField.performTextReplacement(POLKADOT_RECIPIENT_ADDRESS) }
+ }
+ step("Click on 'Next' button") {
+ onSendScreen { nextButton.clickWithAssertion() }
+ }
+ step("Assert fee block is displayed without fee selector") {
+ checkNetworkFeeBlock(currentFeeAmount = feeAmount, withFeeSelector = false)
+ }
+ step("Click on 'Fee selector' block") {
+ onSendConfirmScreen { feeSelectorBlock.performClick() }
+ }
+ step("Assert 'Fee selector' bottom sheet is not displayed") {
+ onSwapSelectNetworkFeeBottomSheet { title.assertIsNotDisplayed() }
+ }
+ }
+ }
+
+ @AllureId("4869")
+ @DisplayName("Send (Fee screen): check network fee bottom sheet for EVM networks")
+ @Test
+ fun checkNetworkFeeBottomSheetForEvmTest() {
+ val tokenName = "Ethereum"
+ val tokenAmount = "0.1"
+ val feeAmount = "~$1.06"
+ val fiatFeeAmount = "$1.09"
+ val newFeeAmount = "~$1.09"
+ val marketSelectorItem = getResourceString(R.string.common_fee_selector_option_market)
+ val fastSelectorItem = getResourceString(R.string.common_fee_selector_option_fast)
+ val slowSelectorItem = getResourceString(R.string.common_fee_selector_option_slow)
+ val feeUpTo = getResourceString(R.string.send_max_fee)
+ val feeUpToTooltip = getResourceString(R.string.send_custom_amount_fee_footer)
+ val feeUpToValue = "0.00042 ETH"
+ val newFeeUpToValue = "0.00043"
+ val maxFee = getResourceString(R.string.send_custom_evm_max_fee)
+ val maxFeeTooltip = getResourceString(R.string.send_custom_evm_max_fee_footer)
+ val maxFeeValue = "20 GWEI"
+ val newMaxFeeValue = "21"
+ val priorityFee = getResourceString(R.string.send_custom_evm_priority_fee)
+ val priorityFeeTooltip = getResourceString(R.string.send_custom_evm_priority_fee_footer)
+ val priorityFeeValue = "2 GWEI"
+ val newPriorityFeeValue = "3"
+ val gasLimit = getResourceString(R.string.send_gas_limit)
+ val gasLimitTooltip = getResourceString(R.string.send_gas_limit_footer)
+ val gasLimitValue = "21,000 "
+ val newGasLimitValue = "22"
+ val nonce = getResourceString(R.string.send_nonce)
+ val nonceTooltip = getResourceString(R.string.send_nonce_footer)
+ val nonceValue = "1"
+
+ setupHooks(
+ additionalAfterSection = {
+ resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
+ resetWireMockScenarioState(QUOTES_API_SCENARIO)
+ }
+ ).run {
+ step("Open 'Send' screen") {
+ openSendScreen(tokenName)
+ }
+ step("Type '$tokenAmount' in input text field") {
+ onSendScreen {
+ amountInputTextField.performClick()
+ amountInputTextField.performTextReplacement(tokenAmount)
+ }
+ }
+ step("Click on 'Next' button") {
+ onSendAddressScreen { nextButton.clickWithAssertion() }
+ }
+ step("Type recipient address") {
+ onSendAddressScreen { addressTextField.performTextReplacement(ETHEREUM_RECIPIENT_ADDRESS) }
+ }
+ step("Click on 'Next' button") {
+ onSendScreen { nextButton.clickWithAssertion() }
+ }
+ step("Assert fee block is displayed with fee selector") {
+ checkNetworkFeeBlock(currentFeeAmount = feeAmount, withFeeSelector = true)
+ }
+ step("Click on fee selector icon") {
+ onSendConfirmScreen { feeSelectorIcon.performClick() }
+ }
+ step("Assert 'Fee selector' bottom sheet title is displayed") {
+ onSendSelectNetworkFeeBottomSheet { chooseSpeedTitle.assertIsDisplayed() }
+ }
+ step("Assert '$marketSelectorItem' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(marketSelectorItem).assertIsDisplayed() }
+ }
+ step("Assert '$fastSelectorItem' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(fastSelectorItem).assertIsDisplayed() }
+ }
+ step("Assert '$slowSelectorItem' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(slowSelectorItem).assertIsDisplayed() }
+ }
+ step("Assert '$marketSelectorItem' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(marketSelectorItem).assertIsDisplayed() }
+ }
+ step("Assert 'Custom' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { customSelectorItem.assertIsDisplayed() }
+ }
+ step("Click on 'Custom' selector item") {
+ onSendSelectNetworkFeeBottomSheet { customSelectorItem.performClick() }
+ }
+ step("Assert '$feeUpTo' input item is displayed") {
+ onSendSelectNetworkFeeBottomSheet {
+ customInputItem(title = feeUpTo, hasFiatAmount = true).assertIsDisplayed()
+ }
+ }
+ step("Assert '$maxFee' input item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { customInputItem(maxFee).assertIsDisplayed() }
+ }
+ step("Assert '$priorityFee' input item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { customInputItem(priorityFee).assertIsDisplayed() }
+ }
+ step("Assert '$gasLimit' input item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { customInputItem(gasLimit).assertIsDisplayed() }
+ }
+ step("Swipe up") {
+ swipeVertical(SwipeDirection.UP)
+ }
+ step("Assert '$nonce' input item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { nonceInputItem.assertIsDisplayed() }
+ }
+ step("Check '$feeUpTo' tooltip") {
+ checkCustomFeeTooltip(title = feeUpTo, tooltip = feeUpToTooltip)
+ }
+ step("Check '$maxFee' tooltip") {
+ checkCustomFeeTooltip(title = maxFee, tooltip = maxFeeTooltip)
+ }
+ step("Check '$priorityFee' tooltip") {
+ checkCustomFeeTooltip(title = priorityFee, tooltip = priorityFeeTooltip)
+ }
+ step("Check '$gasLimit' tooltip") {
+ checkCustomFeeTooltip(title = gasLimit, tooltip = gasLimitTooltip)
+ }
+ step("Check '$nonce' tooltip") {
+ checkCustomFeeTooltip(title = nonce, tooltip = nonceTooltip)
+ }
+ step("Assert '$feeUpTo' value: '$feeUpToValue'") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(feeUpTo).assertTextContains(feeUpToValue) }
+ }
+ step("Assert '$maxFee' value: '$maxFeeValue'") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(maxFee).assertTextContains(maxFeeValue) }
+ }
+ step("Assert '$priorityFee' value: '$priorityFeeValue'") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(priorityFee).assertTextContains(priorityFeeValue) }
+ }
+ step("Assert '$gasLimit' value: '$gasLimitValue'") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(gasLimit).assertTextContains(gasLimitValue) }
+ }
+ step("Check changes in '$maxFee' input text field") {
+ checkChangesInInputTextField(title = maxFee, newValue = newMaxFeeValue, addition = " GWEI")
+ }
+ step("Check changes in '$priorityFee' input text field") {
+ checkChangesInInputTextField(title = priorityFee, newValue = newPriorityFeeValue, addition = " GWEI")
+ }
+ step("Check changes in '$gasLimit' input text field") {
+ checkChangesInInputTextField(title = gasLimit, newValue = newGasLimitValue, addition = " ")
+ }
+ step("Type '$nonceValue' in '$nonce' text field") {
+ onSendSelectNetworkFeeBottomSheet {
+ nonceInputTextField.performClick()
+ nonceInputTextField.performTextReplacement(nonceValue)
+ }
+ }
+ step("Assert '$nonce' value: '$nonceValue'") {
+ onSendSelectNetworkFeeBottomSheet { nonceInputTextField.assertTextContains(nonceValue) }
+ }
+ step("Check changes in '$feeUpTo' input text field") {
+ checkChangesInInputTextField(title = feeUpTo, newValue = newFeeUpToValue, addition = " ETH")
+ }
+ step("Assert new fiat fee amount: '$fiatFeeAmount'") {
+ onSendSelectNetworkFeeBottomSheet { customInputItemFiatAmount.assertTextContains(fiatFeeAmount) }
+ }
+ step("Click on 'Done' button") {
+ onSendSelectNetworkFeeBottomSheet { doneButton.performClick() }
+ }
+ step("Assert fee block is displayed with new fee amount: '$newFeeAmount'") {
+ checkNetworkFeeBlock(currentFeeAmount = newFeeAmount, withFeeSelector = true)
+ }
+ }
+ }
+
+ @AllureId("4870")
+ @DisplayName("Send (Fee screen): check network fee bottom sheet for Bitcoin")
+ @Test
+ fun checkNetworkFeeBottomSheetForBitcoinTest() {
+ val tokenName = "Bitcoin"
+ val tokenAmount = "0.00000001"
+ val feeAmount = "$2.86"
+ val fiatFeeAmount = "$0.24"
+ val marketSelectorItem = getResourceString(R.string.common_fee_selector_option_market)
+ val fastSelectorItem = getResourceString(R.string.common_fee_selector_option_fast)
+ val slowSelectorItem = getResourceString(R.string.common_fee_selector_option_slow)
+ val feeUpTo = getResourceString(R.string.send_max_fee)
+ val feeUpToValue = "0.0000264 BTC"
+ val newFeeUpToValue = "0.0000022 BTC"
+ val satoshi = getResourceString(R.string.send_satoshi_per_byte_title)
+ val satoshiValue = "2"
+ val decimalNumber = "2.11"
+ val newSatoshiValue = "1"
+ val bitcoinUtxoScenarioName = "bitcoin_utxo"
+ val bitcoinUtxoScenarioState = "Balance"
+ val feeScenarioName = "bitcoin_estimate_smart_fee"
+ val feeScenarioState = "Started"
+
+ setupHooks(
+ additionalAfterSection = {
+ resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
+ resetWireMockScenarioState(bitcoinUtxoScenarioName)
+ }
+ ).run {
+ step("Set WireMock scenario: '$bitcoinUtxoScenarioName' to state: '$bitcoinUtxoScenarioState'") {
+ setWireMockScenarioState(bitcoinUtxoScenarioName, bitcoinUtxoScenarioState)
+ }
+ step("Set WireMock scenario: '$feeScenarioName' to state: '$feeScenarioState'") {
+ setWireMockScenarioState(feeScenarioName, feeScenarioState)
+ }
+ step("Open 'Main' screen") {
+ openMainScreen()
+ }
+ step("Synchronize addresses") {
+ synchronizeAddresses()
+ }
+ step("Open 'Send address' screen") {
+ openSendAddressScreen(tokenName, tokenAmount)
+ }
+ step("Type recipient address") {
+ onSendAddressScreen { addressTextField.performTextReplacement(BITCOIN_RECIPIENT_ADDRESS) }
+ }
+ step("Click on 'Next' button") {
+ onSendScreen { nextButton.clickWithAssertion() }
+ }
+ step("Assert fee block is displayed with fee selector") {
+ checkNetworkFeeBlock(currentFeeAmount = feeAmount, withFeeSelector = true)
+ }
+ step("Click on fee selector icon") {
+ onSendConfirmScreen { feeSelectorIcon.performClick() }
+ }
+ step("Assert 'Choose speed' bottom sheet title is displayed") {
+ onSendSelectNetworkFeeBottomSheet { chooseSpeedTitle.assertIsDisplayed() }
+ }
+ step("Assert '$fastSelectorItem' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(fastSelectorItem).assertIsDisplayed() }
+ }
+ step("Assert '$slowSelectorItem' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(slowSelectorItem).assertIsDisplayed() }
+ }
+ step("Assert '$marketSelectorItem' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(marketSelectorItem).assertIsDisplayed() }
+ }
+ step("Assert 'Custom' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { customSelectorItem.assertIsDisplayed() }
+ }
+ step("Click on 'Custom' selector item") {
+ onSendSelectNetworkFeeBottomSheet { customSelectorItem.performClick() }
+ }
+ step("Assert '$feeUpTo' input item is displayed") {
+ onSendSelectNetworkFeeBottomSheet {
+ customInputItem(title = feeUpTo, hasFiatAmount = true).assertIsDisplayed()
+ }
+ }
+ step("Assert '$feeUpTo' value: '$feeUpToValue'") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(feeUpTo).assertTextContains(feeUpToValue) }
+ }
+ step("Assert '$satoshi' input item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { customInputItem(satoshi).assertIsDisplayed() }
+ }
+ step("Click on '$satoshi' input text field") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(satoshi).performClick() }
+ }
+ step("Type '$decimalNumber' in '$satoshi' input text field") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(satoshi).performTextReplacement(decimalNumber) }
+ }
+ step("Assert '$satoshi' value: '$satoshiValue + '") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(satoshi).assertTextContains("$satoshiValue ") }
+ }
+ step("Check changes in '$satoshi' input text field") {
+ checkChangesInInputTextField(title = satoshi, newValue = newSatoshiValue, addition = " ")
+ }
+ step("Assert new fiat fee amount: '$fiatFeeAmount'") {
+ onSendSelectNetworkFeeBottomSheet { customInputItemFiatAmount.assertTextContains(fiatFeeAmount) }
+ }
+ step("Assert '$feeUpTo' value: '$newFeeUpToValue'") {
+ onSendSelectNetworkFeeBottomSheet { inputTextFieldValue(feeUpTo).assertTextContains(newFeeUpToValue) }
+ }
+ step("Click on 'Done' button") {
+ onSendSelectNetworkFeeBottomSheet { doneButton.performClick() }
+ }
+ step("Assert fee block is displayed with new fee amount: '$fiatFeeAmount'") {
+ checkNetworkFeeBlock(currentFeeAmount = fiatFeeAmount, withFeeSelector = true)
+ }
+ }
+ }
+
+ @AllureId("4871")
+ @DisplayName("Send (Fee screen): check network fee bottom sheet networks with fee in token")
+ @Test
+ fun checkNetworkFeeBottomSheetForVeThorTest() {
+ val tokenName = "VeThor"
+ val mockState = "Vechain"
+ val tokenAmount = "0.1"
+ val feeAmount = "<$0.01"
+ val marketSelectorItem = getResourceString(R.string.common_fee_selector_option_market)
+ val fastSelectorItem = getResourceString(R.string.common_fee_selector_option_fast)
+ val slowSelectorItem = getResourceString(R.string.common_fee_selector_option_slow)
+
+ setupHooks(
+ additionalAfterSection = {
+ resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
+ resetWireMockScenarioState(QUOTES_API_SCENARIO)
+ }
+ ).run {
+ step("Open 'Send' screen") {
+ openSendScreen(tokenName = tokenName, mockState = mockState)
+ }
+ step("Type '$tokenAmount' in input text field") {
+ onSendScreen {
+ amountInputTextField.performClick()
+ amountInputTextField.performTextReplacement(tokenAmount)
+ }
+ }
+ step("Click on 'Next' button") {
+ onSendAddressScreen { nextButton.clickWithAssertion() }
+ }
+ step("Type recipient address") {
+ onSendAddressScreen { addressTextField.performTextReplacement(ETHEREUM_RECIPIENT_ADDRESS) }
+ }
+ step("Click on 'Next' button") {
+ onSendScreen { nextButton.clickWithAssertion() }
+ }
+ step("Assert fee block is displayed with fee selector") {
+ checkNetworkFeeBlock(currentFeeAmount = feeAmount, withFeeSelector = true)
+ }
+ step("Click on fee selector icon") {
+ onSendConfirmScreen { feeSelectorIcon.performClick() }
+ }
+ step("Assert 'Choose speed' bottom sheet title is displayed") {
+ onSendSelectNetworkFeeBottomSheet { chooseSpeedTitle.assertIsDisplayed() }
+ }
+ step("Assert '$fastSelectorItem' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(fastSelectorItem).assertIsDisplayed() }
+ }
+ step("Assert '$slowSelectorItem' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(slowSelectorItem).assertIsDisplayed() }
+ }
+ step("Assert '$marketSelectorItem' selector item is displayed") {
+ onSendSelectNetworkFeeBottomSheet { regularFeeSelectorItem(marketSelectorItem).assertIsDisplayed() }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ebc857a78a..97a76b6ac8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -307,6 +307,17 @@
android:host="tangem.onelink.me"
android:scheme="https" />
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt
index bf7f5f336a..4e83789e83 100644
--- a/app/src/main/java/com/tangem/tap/TangemApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt
@@ -360,7 +360,7 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
private fun createReduxStore(): Store {
return Store(
- reducer = { action, state -> appReducer(action, state) },
+ reducer = { action, state -> appReducer(action, requireNotNull(state)) },
middleware = AppState.getMiddleware(),
state = AppState(
daggerGraphState = DaggerGraphState(
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
index 71fcd29497..65315fc4cd 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
@@ -6,9 +6,7 @@ import com.tangem.tap.features.welcome.redux.WelcomeReducer
import com.tangem.tap.proxy.redux.DaggerGraphReducer
import org.rekotlin.Action
-@Suppress("CanBeNonNullable")
-fun appReducer(action: Action, state: AppState?): AppState {
- requireNotNull(state)
+fun appReducer(action: Action, state: AppState): AppState {
if (action is AppAction.RestoreState) return action.state
return AppState(
diff --git a/app/src/main/java/com/tangem/tap/core/ui/DefaultDesignFeatureToggles.kt b/app/src/main/java/com/tangem/tap/core/ui/DefaultDesignFeatureToggles.kt
new file mode 100644
index 0000000000..ca241efce8
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/core/ui/DefaultDesignFeatureToggles.kt
@@ -0,0 +1,11 @@
+package com.tangem.tap.core.ui
+
+import com.tangem.core.configtoggle.feature.FeatureTogglesManager
+import com.tangem.core.ui.DesignFeatureToggles
+import javax.inject.Inject
+
+class DefaultDesignFeatureToggles @Inject constructor(
+ featureTogglesManager: FeatureTogglesManager,
+) : DesignFeatureToggles {
+ override val isRedesignEnabled: Boolean = featureTogglesManager.isFeatureEnabled("APP_REDESIGN_ENABLED")
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt b/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt
index de5f7ef9e3..b5b3c5ea29 100644
--- a/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt
@@ -4,6 +4,7 @@ import androidx.compose.material3.SnackbarHostState
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.ui.DefaultUiMessageSender
import com.tangem.core.decompose.ui.UiMessageSender
+import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.core.ui.message.EventMessageHandler
@@ -23,12 +24,14 @@ internal object UiDependenciesModule {
fun provideUiDependencies(
vibratorHapticManager: VibratorHapticManager,
appThemeModeHolder: AppThemeModeHolder,
+ designFeatureToggles: DesignFeatureToggles,
): UiDependencies {
return object : UiDependencies {
override val vibratorHapticManager = vibratorHapticManager
override val appThemeModeHolder = appThemeModeHolder
override val globalSnackbarHostState: SnackbarHostState = SnackbarHostState()
override val eventMessageHandler: EventMessageHandler = EventMessageHandler()
+ override val designFeatureToggles: DesignFeatureToggles = designFeatureToggles
}
}
diff --git a/app/src/main/java/com/tangem/tap/di/core/ui/CoreUiBindsModule.kt b/app/src/main/java/com/tangem/tap/di/core/ui/CoreUiBindsModule.kt
new file mode 100644
index 0000000000..b02028c5bf
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/di/core/ui/CoreUiBindsModule.kt
@@ -0,0 +1,16 @@
+package com.tangem.tap.di.core.ui
+
+import com.tangem.core.ui.DesignFeatureToggles
+import com.tangem.tap.core.ui.DefaultDesignFeatureToggles
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+interface CoreUiBindsModule {
+
+ @Binds
+ fun bindDesignFeatureToggles(impl: DefaultDesignFeatureToggles): DesignFeatureToggles
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt
index 6e154f3fc1..e56953ffdc 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt
@@ -115,7 +115,7 @@ object MarketsDomainModule {
return FilterAvailableNetworksForWalletUseCase(
userWalletsListManager = userWalletsListManager,
userWalletsListRepository = userWalletsListRepository,
- useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
+ shouldUseNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
excludedBlockchains = excludedBlockchains,
)
}
diff --git a/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt
index 2fbc11170b..cf1a7707c6 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt
@@ -142,22 +142,6 @@ internal object NFTDomainModule {
return GetWalletNFTEnabledUseCase(walletsRepository)
}
- @Provides
- @Singleton
- fun provideClearNFTCacheUseCase(
- nftCleaner: NFTCleaner,
- currenciesRepository: CurrenciesRepository,
- accountsFeatureToggles: AccountsFeatureToggles,
- singleAccountListSupplier: SingleAccountListSupplier,
- ): ObserveAndClearNFTCacheIfNeedUseCase {
- return ObserveAndClearNFTCacheIfNeedUseCase(
- nftCleaner = nftCleaner,
- currenciesRepository = currenciesRepository,
- accountsFeatureToggles = accountsFeatureToggles,
- singleAccountListSupplier = singleAccountListSupplier,
- )
- }
-
@Provides
@Singleton
fun provideGetNftCurrencyUseCase(nftRepository: NFTRepository): GetNFTCurrencyUseCase {
diff --git a/app/src/main/java/com/tangem/tap/di/domain/NewsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/NewsDomainModule.kt
index 55b1457a55..d3e9da60e7 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/NewsDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/NewsDomainModule.kt
@@ -6,39 +6,48 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
-import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object NewsDomainModule {
@Provides
- @Singleton
fun provideGetNewsCategoriesUseCase(repository: NewsRepository): GetNewsCategoriesUseCase {
return GetNewsCategoriesUseCase(repository)
}
@Provides
- @Singleton
fun provideObserveNewsDetailsUseCase(repository: NewsRepository): ObserveNewsDetailsUseCase {
return ObserveNewsDetailsUseCase(repository)
}
@Provides
- @Singleton
fun provideObserveTrendingNewsUseCase(repository: NewsRepository): ManageTrendingNewsUseCase {
return ManageTrendingNewsUseCase(repository)
}
@Provides
- @Singleton
fun provideGetNewsListBatchFlowUseCase(repository: NewsRepository): GetNewsListBatchFlowUseCase {
return GetNewsListBatchFlowUseCase(repository)
}
@Provides
- @Singleton
fun provideFetchTrendingNewsUseCase(repository: NewsRepository): FetchTrendingNewsUseCase {
return FetchTrendingNewsUseCase(repository)
}
+
+ @Provides
+ fun provideMarkArticleAsViewedUseCase(repository: NewsRepository): MarkArticleAsViewedUseCase {
+ return MarkArticleAsViewedUseCase(repository)
+ }
+
+ @Provides
+ fun provideToggleArticleLikedUseCase(repository: NewsRepository): ToggleArticleLikedUseCase {
+ return ToggleArticleLikedUseCase(repository)
+ }
+
+ @Provides
+ fun provideGetNewsUseCase(repository: NewsRepository): GetNewsUseCase {
+ return GetNewsUseCase(repository)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt
index 5e28ca4628..256d103aa3 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt
@@ -83,4 +83,14 @@ internal object NotificationsDomainModule {
): GetNetworksAvailableForNotificationsUseCase {
return GetNetworksAvailableForNotificationsUseCase(pushNotificationsRepository = pushNotificationsRepository)
}
+
+ @Provides
+ @Singleton
+ fun provideClearApplicationIdUseCase(
+ pushNotificationsRepository: PushNotificationsRepository,
+ ): ClearApplicationIdUseCase {
+ return ClearApplicationIdUseCase(
+ pushNotificationsRepository = pushNotificationsRepository,
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt
index 08a4bc7a3b..a823079f33 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt
@@ -94,12 +94,12 @@ internal object StakingDomainModule {
@Singleton
fun provideFetchStakingOptionsUseCase(
stakeKitRepository: StakeKitRepository,
- p2pRepository: P2PEthPoolRepository,
+ p2pEthPoolRepository: P2PEthPoolRepository,
stakingErrorResolver: StakingErrorResolver,
): FetchStakingOptionsUseCase {
return FetchStakingOptionsUseCase(
stakeKitRepository = stakeKitRepository,
- p2pRepository = p2pRepository,
+ p2pEthPoolRepository = p2pEthPoolRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
index 7528f0f18b..5bb200ad04 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
@@ -282,11 +282,13 @@ internal object TokensDomainModule {
currenciesRepository: CurrenciesRepository,
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
dispatchers: CoroutineDispatcherProvider,
+ currencyChecksRepository: CurrencyChecksRepository,
): GetBalanceNotEnoughForFeeWarningUseCase {
return GetBalanceNotEnoughForFeeWarningUseCase(
currenciesRepository = currenciesRepository,
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
dispatchers = dispatchers,
+ currencyChecksRepository = currencyChecksRepository,
)
}
@@ -294,9 +296,8 @@ internal object TokensDomainModule {
@Singleton
fun provideIsAmountSubtractAvailableUseCase(
currenciesRepository: CurrenciesRepository,
- dispatchers: CoroutineDispatcherProvider,
): IsAmountSubtractAvailableUseCase {
- return IsAmountSubtractAvailableUseCase(currenciesRepository, dispatchers)
+ return IsAmountSubtractAvailableUseCase(currenciesRepository)
}
@Provides
diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
index 8965f60090..ff1b6bb97a 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
@@ -5,12 +5,18 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
+import com.tangem.domain.tokens.GetMultiCryptoCurrencyStatusUseCase
+import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.GetViewedTokenReceiveWarningUseCase
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
+import com.tangem.domain.tokens.repository.CurrenciesRepository
+import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.transaction.FeeRepository
+import com.tangem.domain.transaction.GaslessTransactionRepository
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.WalletAddressServiceRepository
import com.tangem.domain.transaction.usecase.*
+import com.tangem.domain.transaction.usecase.gasless.*
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@@ -21,7 +27,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
-@Suppress("TooManyFunctions")
+@Suppress("TooManyFunctions", "LargeClass")
@Module
@InstallIn(SingletonComponent::class)
internal object TransactionDomainModule {
@@ -265,4 +271,126 @@ internal object TransactionDomainModule {
): SendLargeSolanaTransactionUseCase {
return SendLargeSolanaTransactionUseCase(cardSdkConfigRepository, walletManagersFacade)
}
+
+ @Provides
+ @Singleton
+ fun provideGetAvailableFeeTokensUseCase(
+ gaslessTransactionRepository: GaslessTransactionRepository,
+ currenciesRepository: CurrenciesRepository,
+ getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase,
+ currencyChecksRepository: CurrencyChecksRepository,
+ ): GetAvailableFeeTokensUseCase {
+ return GetAvailableFeeTokensUseCase(
+ gaslessTransactionRepository = gaslessTransactionRepository,
+ currenciesRepository = currenciesRepository,
+ getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase,
+ currencyChecksRepository = currencyChecksRepository,
+ )
+ }
+
+ @Provides
+ @Singleton
+ fun provideGetFeeForGaslessUseCase(
+ walletManagersFacade: WalletManagersFacade,
+ gaslessTransactionRepository: GaslessTransactionRepository,
+ currenciesRepository: CurrenciesRepository,
+ getFeeUseCase: GetFeeUseCase,
+ getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase,
+ currencyChecksRepository: CurrencyChecksRepository,
+ ): GetFeeForGaslessUseCase {
+ return GetFeeForGaslessUseCase(
+ walletManagersFacade = walletManagersFacade,
+ demoConfig = DemoConfig,
+ gaslessTransactionRepository = gaslessTransactionRepository,
+ currenciesRepository = currenciesRepository,
+ getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase,
+ getFeeUseCase = getFeeUseCase,
+ currencyChecksRepository = currencyChecksRepository,
+ )
+ }
+
+ @Provides
+ @Singleton
+ fun provideGetFeeForTokenUseCase(
+ walletManagersFacade: WalletManagersFacade,
+ gaslessTransactionRepository: GaslessTransactionRepository,
+ currenciesRepository: CurrenciesRepository,
+ getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase,
+ currencyChecksRepository: CurrencyChecksRepository,
+ ): GetFeeForTokenUseCase {
+ return GetFeeForTokenUseCase(
+ gaslessTransactionRepository = gaslessTransactionRepository,
+ walletManagersFacade = walletManagersFacade,
+ demoConfig = DemoConfig,
+ currenciesRepository = currenciesRepository,
+ getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase,
+ currencyChecksRepository = currencyChecksRepository,
+ )
+ }
+
+ @Provides
+ @Singleton
+ fun provideIsGaslessFeeSupportedForNetwork(
+ currencyChecksRepository: CurrencyChecksRepository,
+ ): IsGaslessFeeSupportedForNetwork {
+ return IsGaslessFeeSupportedForNetwork(currencyChecksRepository)
+ }
+
+ @Provides
+ @Singleton
+ fun provideCreateAndSendGaslessTransactionUseCase(
+ walletManagersFacade: WalletManagersFacade,
+ gaslessTransactionRepository: GaslessTransactionRepository,
+ getSingCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
+ cardSdkConfigRepository: CardSdkConfigRepository,
+ tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
+ ): CreateAndSendGaslessTransactionUseCase {
+ return CreateAndSendGaslessTransactionUseCase(
+ walletManagersFacade = walletManagersFacade,
+ getSingleCryptoCurrencyStatusUseCase = getSingCryptoCurrencyStatusUseCase,
+ gaslessTransactionRepository = gaslessTransactionRepository,
+ cardSdkConfigRepository = cardSdkConfigRepository,
+ getHotWalletSigner = tangemHotWalletSignerFactory::create,
+ )
+ }
+
+ @Provides
+ @Singleton
+ fun provideEstimateFeeForTokenUseCase(
+ walletManagersFacade: WalletManagersFacade,
+ gaslessTransactionRepository: GaslessTransactionRepository,
+ currenciesRepository: CurrenciesRepository,
+ getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase,
+ currencyChecksRepository: CurrencyChecksRepository,
+ ): EstimateFeeForTokenUseCase {
+ return EstimateFeeForTokenUseCase(
+ gaslessTransactionRepository = gaslessTransactionRepository,
+ walletManagersFacade = walletManagersFacade,
+ demoConfig = DemoConfig,
+ currenciesRepository = currenciesRepository,
+ getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase,
+ currencyChecksRepository = currencyChecksRepository,
+ )
+ }
+
+ @Provides
+ @Singleton
+ fun provideEstimateFeeForGaslessTxUseCase(
+ walletManagersFacade: WalletManagersFacade,
+ gaslessTransactionRepository: GaslessTransactionRepository,
+ currenciesRepository: CurrenciesRepository,
+ getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase,
+ estimateFeeUseCase: EstimateFeeUseCase,
+ currencyChecksRepository: CurrencyChecksRepository,
+ ): EstimateFeeForGaslessTxUseCase {
+ return EstimateFeeForGaslessTxUseCase(
+ gaslessTransactionRepository = gaslessTransactionRepository,
+ walletManagersFacade = walletManagersFacade,
+ demoConfig = DemoConfig,
+ currenciesRepository = currenciesRepository,
+ getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase,
+ estimateFeeUseCase = estimateFeeUseCase,
+ currencyChecksRepository = currencyChecksRepository,
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt
index 374af96dd0..52533057f0 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt
@@ -333,10 +333,14 @@ internal object WalletsDomainModule {
fun providesUpdateRemoteWalletsInfoUseCase(
walletsRepository: WalletsRepository,
userWalletsSyncDelegate: UserWalletsSyncDelegate,
+ userWalletsListRepository: UserWalletsListRepository,
+ generateWalletNameUseCase: GenerateWalletNameUseCase,
): UpdateRemoteWalletsInfoUseCase {
return UpdateRemoteWalletsInfoUseCase(
walletsRepository = walletsRepository,
userWalletsSyncDelegate = userWalletsSyncDelegate,
+ generateWalletNameUseCase = generateWalletNameUseCase,
+ userWalletsListRepository = userWalletsListRepository,
)
}
diff --git a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt
index 3301b9c21a..d74844c082 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.di.domain
import com.tangem.domain.blockaid.BlockAidGasEstimate
+import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.FeeRepository
@@ -14,6 +15,8 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Suppress("TooManyFunctions")
@@ -234,4 +237,18 @@ internal object YieldSupplyDomainModule {
): YieldSupplyGetAvailabilityUseCase {
return YieldSupplyGetAvailabilityUseCase(yieldSupplyRepository)
}
+
+ @Provides
+ @Singleton
+ fun provideYieldSupplyPendingProcessorUseCase(
+ yieldSupplyRepository: YieldSupplyRepository,
+ singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
+ dispatcherProvider: CoroutineDispatcherProvider,
+ ): YieldSupplyPendingTracker {
+ return YieldSupplyPendingTracker(
+ yieldSupplyRepository = yieldSupplyRepository,
+ singleNetworkStatusFetcher = singleNetworkStatusFetcher,
+ coroutineScope = CoroutineScope(SupervisorJob() + dispatcherProvider.io),
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt
index 3d66410898..382b50ad26 100644
--- a/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt
+++ b/app/src/main/java/com/tangem/tap/domain/sdk/mocks/content/WalletMockContent.kt
@@ -141,6 +141,14 @@ 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/44'/330'/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'/818'/0'/0/0") to ExtendedPublicKey(
+ publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
+ chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
+ ),
),
extendedPublicKey = ExtendedPublicKey(
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),
@@ -261,6 +269,20 @@ object WalletMockContent : MockContent {
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
+ DerivationPath("m/44'/330'/0'/0/0") to ExtendedPublicKey( // Terra
+ 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'/818'/0'/0/0") to ExtendedPublicKey( // Vechain
+ 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(
@@ -318,6 +340,20 @@ object WalletMockContent : MockContent {
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
+ DerivationPath("m/44'/330'/0'/0/0") to ExtendedPublicKey( // Terra
+ 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'/818'/0'/0/0") to ExtendedPublicKey( // Vechain
+ 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,
+ ),
),
),
@@ -371,6 +407,13 @@ object WalletMockContent : MockContent {
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
+ DerivationPath("m/44'/818'/0'/0/0") to ExtendedPublicKey( // Vechain
+ 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(
diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt
index 9ea074747f..6768c39ec9 100644
--- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt
@@ -21,9 +21,11 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.models.wallet.UserWallet
+import com.tangem.domain.notifications.ClearApplicationIdUseCase
import com.tangem.domain.notifications.GetApplicationIdUseCase
import com.tangem.domain.notifications.SendPushTokenUseCase
import com.tangem.domain.notifications.models.ApplicationId
+import com.tangem.domain.notifications.models.NotificationsError
import com.tangem.domain.onramp.FetchHotCryptoUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.promo.models.StoryContentIds
@@ -72,6 +74,7 @@ internal class MainViewModel @Inject constructor(
private val getApplicationIdUseCase: GetApplicationIdUseCase,
private val subscribeOnWalletsUseCase: GetSavedWalletsCountUseCase,
private val associateWalletsWithApplicationIdUseCase: AssociateWalletsWithApplicationIdUseCase,
+ private val clearApplicationIdUseCase: ClearApplicationIdUseCase,
private val updateRemoteWalletsInfoUseCase: UpdateRemoteWalletsInfoUseCase,
private val sendPushTokenUseCase: SendPushTokenUseCase,
private val apiConfigsManager: ApiConfigsManager,
@@ -104,7 +107,7 @@ internal class MainViewModel @Inject constructor(
launch { fetchStakingOptions() }
- launch { initPushNotifications() }
+ launch(dispatchers.default) { initPushNotifications() }
}
viewModelScope.launch { incrementAppLaunchCounterUseCase() }
@@ -398,13 +401,27 @@ internal class MainViewModel @Inject constructor(
private suspend fun initPushNotifications() {
getApplicationIdUseCase()
.onRight { applicationId ->
- sendPushTokenUseCase(applicationId = applicationId)
- associateWalletsWithApplicationId(applicationId = applicationId)
- updateRemoteWalletsInfoUseCase(applicationId = applicationId)
+ sendPushTokenUseCase(applicationId = applicationId).onLeft { error ->
+ if (error is NotificationsError.ApplicationIdNotFound) {
+ clearApplicationIdUseCase()
+ getApplicationIdUseCase().onRight { newApplicationId ->
+ associateAndUpdateWallets(applicationId = newApplicationId)
+ }
+ } else {
+ associateAndUpdateWallets(applicationId = applicationId)
+ }
+ }.onRight {
+ associateAndUpdateWallets(applicationId = applicationId)
+ }
}
.onLeft(Timber::e)
}
+ private suspend fun associateAndUpdateWallets(applicationId: ApplicationId) {
+ associateWalletsWithApplicationId(applicationId = applicationId)
+ updateRemoteWalletsInfoUseCase(applicationId = applicationId)
+ }
+
private fun associateWalletsWithApplicationId(applicationId: ApplicationId) {
subscribeOnWalletsUseCase()
.onEach { wallets ->
diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt
index 597915ff66..79a50c1403 100644
--- a/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt
+++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultAuthProvider.kt
@@ -59,6 +59,17 @@ internal class DefaultAuthProvider(
}
}
+ override fun getGaslessServiceApiKey(apiEnvironment: Provider): ProviderSuspend {
+ return ProviderSuspend {
+ when (apiEnvironment.invoke()) {
+ ApiEnvironment.DEV,
+ -> environmentConfigStorage.getConfigSync().gaslessTxApiKeyDev
+ ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().gaslessTxApiKey
+ else -> error("No gasless tx api config provided for ${apiEnvironment.invoke()}")
+ } ?: error("No gasless tx api config provided")
+ }
+ }
+
private suspend fun getWallets(): List {
return if (shouldUseNewListRepository) {
userWalletsListRepository.userWalletsSync()
diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultP2PEthPoolAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultP2PEthPoolAuthProvider.kt
index 5f54236082..93b0595647 100644
--- a/app/src/main/java/com/tangem/tap/network/auth/DefaultP2PEthPoolAuthProvider.kt
+++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultP2PEthPoolAuthProvider.kt
@@ -1,7 +1,7 @@
package com.tangem.tap.network.auth
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
-import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
+import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
import com.tangem.lib.auth.P2PEthPoolAuthProvider
internal class DefaultP2PEthPoolAuthProvider(
@@ -12,6 +12,6 @@ internal class DefaultP2PEthPoolAuthProvider(
val keys = environmentConfigStorage.getConfigSync().p2pApiKey
?: error("No P2P api keys provided")
- return if (P2PStakingConfig.USE_TESTNET) keys.hoodi else keys.mainnet
+ return if (P2PEthPoolStakingConfig.USE_TESTNET) keys.hoodi else keys.mainnet
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
index d7f3a0cc33..f182a2555b 100644
--- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
+++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
@@ -16,6 +16,9 @@ import com.tangem.features.createwalletselection.CreateWalletSelectionComponent
import com.tangem.features.createwalletstart.CreateWalletStartComponent
import com.tangem.features.details.component.DetailsComponent
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
+import com.tangem.features.feed.entry.components.FeedEntryComponent
+import com.tangem.features.feed.entry.components.FeedEntryRoute
+import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
import com.tangem.features.home.api.HomeComponent
import com.tangem.features.hotwallet.*
import com.tangem.features.kyc.KycComponent
@@ -38,15 +41,11 @@ import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.swap.SwapComponent
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
import com.tangem.features.tangempay.components.TangemPayOnboardingComponent
-import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.ContinueOnboarding
-import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.Deeplink
-import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.FromBannerOnMain
-import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.FromBannerInSettings
+import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.*
import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.wallet.WalletEntryComponent
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
-import com.tangem.features.yield.supply.api.YieldSupplyActiveComponent
-import com.tangem.features.yield.supply.api.YieldSupplyPromoComponent
+import com.tangem.features.yield.supply.api.YieldSupplyEntryComponent
import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent
import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
@@ -118,9 +117,10 @@ internal class ChildFactory @Inject constructor(
private val tangemPayDetailsContainerComponentFactory: TangemPayDetailsContainerComponent.Factory,
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
private val kycComponentFactory: KycComponent.Factory,
- private val yieldSupplyPromoComponentFactory: YieldSupplyPromoComponent.Factory,
- private val yieldSupplyActiveComponentFactory: YieldSupplyActiveComponent.Factory,
+ private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
+ private val feedEntryComponentFactory: FeedEntryComponent.Factory,
+ private val feedFeatureToggle: FeedFeatureToggle,
) {
@Suppress("LongMethod", "CyclomaticComplexMethod")
@@ -205,21 +205,39 @@ internal class ChildFactory @Inject constructor(
)
}
is AppRoute.MarketsTokenDetails -> {
- createComponentChild(
- context = context,
- params = MarketsTokenDetailsComponent.Params(
- token = route.token,
- appCurrency = route.appCurrency,
- shouldShowPortfolio = route.shouldShowPortfolio,
- analyticsParams = route.analyticsParams?.let { params ->
- MarketsTokenDetailsComponent.AnalyticsParams(
- blockchain = params.blockchain,
- source = params.source,
- )
- },
- ),
- componentFactory = marketsTokenDetailsComponentFactory,
- )
+ if (feedFeatureToggle.isFeedEnabled) {
+ createComponentChild(
+ context = context,
+ params = FeedEntryRoute.MarketTokenDetails(
+ token = route.token,
+ appCurrency = route.appCurrency,
+ shouldShowPortfolio = route.shouldShowPortfolio,
+ analyticsParams = route.analyticsParams?.let { params ->
+ FeedEntryRoute.MarketTokenDetails.AnalyticsParams(
+ blockchain = params.blockchain,
+ source = params.source,
+ )
+ },
+ ),
+ componentFactory = feedEntryComponentFactory,
+ )
+ } else {
+ createComponentChild(
+ context = context,
+ params = MarketsTokenDetailsComponent.Params(
+ token = route.token,
+ appCurrency = route.appCurrency,
+ shouldShowPortfolio = route.shouldShowPortfolio,
+ analyticsParams = route.analyticsParams?.let { params ->
+ MarketsTokenDetailsComponent.AnalyticsParams(
+ blockchain = params.blockchain,
+ source = params.source,
+ )
+ },
+ ),
+ componentFactory = marketsTokenDetailsComponentFactory,
+ )
+ }
}
is AppRoute.Onramp -> {
createComponentChild(
@@ -312,7 +330,7 @@ internal class ChildFactory @Inject constructor(
params = StakingComponent.Params(
userWalletId = route.userWalletId,
cryptoCurrency = route.cryptoCurrency,
- yieldId = route.yieldId,
+ integrationId = route.integrationId,
),
componentFactory = stakingComponentFactory,
)
@@ -687,25 +705,25 @@ internal class ChildFactory @Inject constructor(
componentFactory = kycComponentFactory,
)
}
- is AppRoute.YieldSupplyPromo -> {
+ is AppRoute.YieldSupplyEntry -> {
createComponentChild(
context = context,
- params = YieldSupplyPromoComponent.Params(
- userWalletId = route.userWalletId,
- currency = route.cryptoCurrency,
- apy = route.apy,
- ),
- componentFactory = yieldSupplyPromoComponentFactory,
- )
- }
- is AppRoute.YieldSupplyActive -> {
- createComponentChild(
- context = context,
- params = YieldSupplyActiveComponent.Params(
+ params = YieldSupplyEntryComponent.Params(
userWalletId = route.userWalletId,
cryptoCurrency = route.cryptoCurrency,
+ apy = route.apy,
),
- componentFactory = yieldSupplyActiveComponentFactory,
+ componentFactory = yieldSupplyEntryComponentFactory,
+ )
+ }
+ is AppRoute.NewsDetails -> {
+ createComponentChild(
+ context = context,
+ params = FeedEntryRoute.NewsDetail(
+ articleId = route.newsId,
+ preselectedArticlesId = listOf(route.newsId),
+ ),
+ componentFactory = feedEntryComponentFactory,
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
index 097b57b518..27c065e869 100644
--- a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
+++ b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
@@ -6,6 +6,8 @@ import com.tangem.common.routing.DeepLinkRoute
import com.tangem.common.routing.DeepLinkScheme
import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
+import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
+import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
@@ -50,6 +52,8 @@ internal class DeepLinkFactory @Inject constructor(
private val swapDeepLink: SwapDeepLinkHandler.Factory,
private val promoDeepLink: PromoDeeplinkHandler.Factory,
private val onboardVisaDeepLink: OnboardVisaDeepLinkHandler.Factory,
+ private val newsDetailsDeepLink: NewsDetailsDeepLinkHandler.Factory,
+ private val feedFeatureToggle: FeedFeatureToggle,
) {
private val permittedAppRoute = MutableStateFlow(false)
@@ -102,7 +106,7 @@ internal class DeepLinkFactory @Inject constructor(
private fun launchDeepLink(deeplinkUri: Uri, coroutineScope: CoroutineScope, isFromOnNewIntent: Boolean) {
when (deeplinkUri.scheme) {
- DeepLinkScheme.Https.scheme -> handleHttpDeepLinks(deeplinkUri)
+ DeepLinkScheme.Https.scheme -> handleHttpDeepLinks(deeplinkUri, coroutineScope)
DeepLinkScheme.Tangem.scheme -> handleTangemDeepLinks(deeplinkUri, coroutineScope, isFromOnNewIntent)
DeepLinkScheme.WalletConnect.scheme -> walletConnectDeepLink.create(deeplinkUri)
else -> {
@@ -116,10 +120,18 @@ internal class DeepLinkFactory @Inject constructor(
}
}
- private fun handleHttpDeepLinks(deeplinkUri: Uri) {
- if (deeplinkUri.host == DeepLinkRoute.PayApp.host && deeplinkUri.path?.startsWith("/pay-app") == true) {
- onboardVisaDeepLink.create(deeplinkUri)
- return
+ private fun handleHttpDeepLinks(deeplinkUri: Uri, coroutineScope: CoroutineScope) {
+ if (deeplinkUri.host == DeepLinkRoute.PayApp.host) {
+ when {
+ deeplinkUri.path?.startsWith("/pay-app") == true -> {
+ onboardVisaDeepLink.create(deeplinkUri)
+ return
+ }
+ deeplinkUri.path?.startsWith("/news") == true && feedFeatureToggle.isFeedEnabled -> {
+ newsDetailsDeepLink.create(coroutineScope, deeplinkUri)
+ return
+ }
+ }
}
}
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index d91dbb1dbe..6ec92245ac 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -16,7 +16,7 @@
diff --git a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
index ea548398d0..19782fca91 100644
--- a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
+++ b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
@@ -4,6 +4,8 @@ import android.net.Uri
import com.tangem.common.routing.AppRoute
import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
+import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
+import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
@@ -81,6 +83,12 @@ class DeepLinkFactoryTest {
private val cardSdkProvider = mockk(relaxed = true) {
every { sdk.uiVisibility() } returns MutableStateFlow(false)
}
+
+ private val newsDeeplink = mockk(relaxed = true) {
+ every { create(any(), any()) } returns mockk()
+ }
+ private val feedFeatureToggle = mockk()
+
private val mockedUri = mockk(relaxed = true)
private val isFromOnNewIntent: Boolean = false
@@ -103,6 +111,8 @@ class DeepLinkFactoryTest {
swapDeepLink = swapDeepLinkFactory,
promoDeepLink = promoDeepLinkFactory,
onboardVisaDeepLink = onboardVisaDeepLink,
+ newsDetailsDeepLink = newsDeeplink,
+ feedFeatureToggle = feedFeatureToggle,
)
@OptIn(ExperimentalCoroutinesApi::class)
diff --git a/common/routing/build.gradle.kts b/common/routing/build.gradle.kts
index 9fe3f2a5e4..b34c827c74 100644
--- a/common/routing/build.gradle.kts
+++ b/common/routing/build.gradle.kts
@@ -19,7 +19,7 @@ dependencies {
implementation(projects.domain.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
- implementation(projects.domain.staking.models)
+ implementation(projects.domain.staking)
implementation(projects.domain.markets.models)
implementation(projects.domain.onramp.models)
implementation(projects.domain.appCurrency.models)
diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
index 7242c08646..2be124d092 100644
--- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
+++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
@@ -7,6 +7,7 @@ import android.os.Bundle
import com.tangem.common.routing.bundle.RouteBundleParams
import com.tangem.common.routing.bundle.bundle
import com.tangem.common.routing.entity.InitScreenLaunchMode
+import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.feedback.models.WalletMetaInfo
@@ -209,8 +210,8 @@ sealed class AppRoute(val path: String) : Route {
data class Staking(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency,
- val yieldId: String,
- ) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrency.id.value}/$yieldId")
+ val integrationId: StakingIntegrationID,
+ ) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrency.id.value}/${integrationId.value}")
@Serializable
data class PushNotification(
@@ -456,16 +457,12 @@ sealed class AppRoute(val path: String) : Route {
data class Kyc(val userWalletId: UserWalletId) : AppRoute(path = "/kyc")
@Serializable
- data class YieldSupplyPromo(
+ data class YieldSupplyEntry(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency,
val apy: String,
- ) : AppRoute(path = "/yield_supply_promo/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
+ ) : AppRoute(path = "/yield_supply_entry/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
@Serializable
- data class YieldSupplyActive(
- val userWalletId: UserWalletId,
- val cryptoCurrency: CryptoCurrency,
- val apy: String,
- ) : AppRoute(path = "/yield_supply_active/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
+ data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId")
}
\ No newline at end of file
diff --git a/common/src/main/kotlin/com/tangem/common/TangemBlogUrlBuilder.kt b/common/src/main/kotlin/com/tangem/common/TangemBlogUrlBuilder.kt
index 0319e1c59f..1ba2efe988 100644
--- a/common/src/main/kotlin/com/tangem/common/TangemBlogUrlBuilder.kt
+++ b/common/src/main/kotlin/com/tangem/common/TangemBlogUrlBuilder.kt
@@ -31,5 +31,9 @@ object TangemBlogUrlBuilder {
data object WhatWalletToChoose : Post {
override val path: String = "mobile-wallet"
}
+
+ data object WhatIsTransactionFee : Post {
+ override val path: String = "what-is-a-transaction-fee-and-why-do-we-need-it"
+ }
}
}
\ No newline at end of file
diff --git a/common/test/src/main/java/com/tangem/common/test/data/staking/MockP2PEthPoolAccountResponseFactory.kt b/common/test/src/main/java/com/tangem/common/test/data/staking/MockP2PEthPoolAccountResponseFactory.kt
index 1e814c5d9f..4dc0b16d03 100644
--- a/common/test/src/main/java/com/tangem/common/test/data/staking/MockP2PEthPoolAccountResponseFactory.kt
+++ b/common/test/src/main/java/com/tangem/common/test/data/staking/MockP2PEthPoolAccountResponseFactory.kt
@@ -8,7 +8,7 @@ import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import java.math.BigDecimal
/**
- * Factory for creating mock P2P ETH Pool account responses for testing
+ * Factory for creating mock P2PEthPool account responses for testing
*/
object MockP2PEthPoolAccountResponseFactory {
@@ -35,7 +35,7 @@ object MockP2PEthPoolAccountResponseFactory {
availableToUnstake = stakedAmount,
availableToWithdraw = BigDecimal.ZERO,
exitQueue = P2PEthPoolExitQueueDTO(
- total = 0.0,
+ total = BigDecimal.ZERO,
requests = emptyList(),
),
)
@@ -55,7 +55,7 @@ object MockP2PEthPoolAccountResponseFactory {
availableToUnstake = BigDecimal.ZERO,
availableToWithdraw = BigDecimal.ZERO,
exitQueue = P2PEthPoolExitQueueDTO(
- total = 0.0,
+ total = BigDecimal.ZERO,
requests = emptyList(),
),
)
diff --git a/common/ui/detekt-baseline-debug.xml b/common/ui/detekt-baseline-debug.xml
index 13ac4c3486..312ce47852 100644
--- a/common/ui/detekt-baseline-debug.xml
+++ b/common/ui/detekt-baseline-debug.xml
@@ -9,20 +9,11 @@
BooleanPropertyNaming:NotificationUM.kt$NotificationUM.Error.ExceedsBalance$val mergeFeeNetworkName: Boolean = false
BooleanPropertyNaming:NotificationsFactory.kt$NotificationsFactory$val showNotification = sendingAmount + feeAmount > balance - minimumRequirement.orZero()
BooleanPropertyNaming:TokenReceiveBottomSheetConfig.kt$TokenReceiveBottomSheetConfig$val showMemoDisclaimer: Boolean
- CanBeNonNullable:AmountBlockV2.kt$onClick: (() -> Unit)? = null
- CanBeNonNullable:NavigationButtonsBlock.kt$footerText: TextReference?
- CanBeNonNullable:NavigationButtonsBlock.kt$pairButtons: Pair<NavigationButton, NavigationButton>?
- CanBeNonNullable:NavigationButtonsBlock.kt$prevButton: NavigationButton?
- CanBeNonNullable:NotificationsFactory.kt$NotificationsFactory$dustValue: BigDecimal?
- CanBeNonNullable:NotificationsFactory.kt$NotificationsFactory$minAdaValue: BigDecimal?
- CanBeNonNullable:NotificationsFactory.kt$NotificationsFactory$minimumSendAmount: BigDecimal?
- CanBeNonNullable:NotificationsFactory.kt$NotificationsFactory$rentWarning: CryptoCurrencyWarning.Rent?
MultilineLambdaItParameter:ExpressStatusItems.kt${ val itemInfo = expressTxs[it].info val (iconRes, tint) = when (itemInfo.iconState) { ExpressTransactionStateIconUM.Warning -> { R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention } ExpressTransactionStateIconUM.Error -> { R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning } ExpressTransactionStateIconUM.None -> null to null } ExpressStatusItem( title = itemInfo.title, fromTokenIconState = itemInfo.fromCurrencyIcon, toTokenIconState = itemInfo.toCurrencyIcon, fromAmount = itemInfo.fromAmount, fromSymbol = itemInfo.fromAmountSymbol, toAmount = itemInfo.toAmount, toSymbol = itemInfo.toAmountSymbol, onClick = itemInfo.onClick, infoIconRes = iconRes, infoIconTint = tint, modifier = modifier.animateItem(), ) }
MultilineLambdaItParameter:TokenItemStateConverter.kt$TokenItemStateConverter.Companion${ it.key.equals( other = token.yieldSupplyKey(), ignoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId), ) }
NoNameShadowing:NavigationButtonsBlock.kt$navigationUM
NoNameShadowing:UserWalletItem.kt$balance
NullableBooleanCheck:TokenItemStateConverter.kt$TokenItemStateConverter.Companion$cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive ?: false
- NullableToStringCall:TokenItemStateConverter.kt$TokenItemStateConverter.Companion$${id.rawCurrencyId}
ReusedModifierInstance:AddTokenContent.kt$AddButton( modifier = modifier.fillMaxWidth(), state = state.button, )
UnnecessaryEventHandlerParameter:SendDoneButtons.kt$onShareClick: (String) -> Unit
UnnecessaryLet:TokenItemStateConverter.kt$TokenItemStateConverter.Companion$let(::add)
diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountCryptoPortfolioItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountCryptoPortfolioItemStateConverter.kt
index c24dd49e89..943fb98735 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/account/AccountCryptoPortfolioItemStateConverter.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountCryptoPortfolioItemStateConverter.kt
@@ -19,7 +19,6 @@ import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
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(
@@ -41,14 +40,11 @@ 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() },
- )
- }
+ val subtitle2State = priceChangeLce?.fold(
+ ifLoading = { priceChange -> priceChange?.toSubtitle2State() ?: Subtitle2State.Loading },
+ ifError = { null },
+ ifContent = { priceChange -> priceChange.toSubtitle2State() },
+ )
return TokenItemState.Content(
id = account.accountId.toItemId(),
iconState = AccountIconItemStateConverter.convert(this),
@@ -120,7 +116,7 @@ class AccountCryptoPortfolioItemStateConverter(
)
}
- private fun AccountId.toItemId() = this.userWalletId.stringValue + this.value
+ private fun AccountId.toItemId() = this.value
private fun BigDecimal.getPriceChangeType(): PriceChangeType = PriceChangeConverter.fromBigDecimal(value = this)
diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountIconItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountIconItemStateConverter.kt
index f89a37e3db..102be5a837 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/account/AccountIconItemStateConverter.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountIconItemStateConverter.kt
@@ -20,5 +20,6 @@ object AccountIconItemStateConverter : Converter TODO("[REDACTED_JIRA]")
}
}
\ No newline at end of file
diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt
index d45ef488d1..baed1af4d7 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt
@@ -22,21 +22,24 @@ class AccountPortfolioItemUMConverter(
) : Converter {
override fun convert(value: Account): UserWalletItemUM {
- return with(value) {
- UserWalletItemUM(
- id = accountId.value,
- name = accountName.toUM().value,
- information = getInfo(account = this),
- balance = getBalanceInfo(),
- isEnabled = isEnabled,
- endIcon = endIcon,
- onClick = onClick,
- imageState = getImageState(account = this),
- )
+ return when (value) {
+ is Account.CryptoPortfolio -> with(value) {
+ UserWalletItemUM(
+ id = accountId.value,
+ name = accountName.toUM().value,
+ information = getInfo(account = this),
+ balance = getBalanceInfo(),
+ isEnabled = isEnabled,
+ endIcon = endIcon,
+ onClick = onClick,
+ imageState = getImageState(account = this),
+ )
+ }
+ is Account.Payment -> TODO("[REDACTED_JIRA]")
}
}
- private fun getInfo(account: Account): UserWalletItemUM.Information.Loaded = when (account) {
+ private fun getInfo(account: Account.CryptoPortfolio): UserWalletItemUM.Information.Loaded = when (account) {
is Account.CryptoPortfolio -> {
val text = pluralReference(
R.plurals.common_tokens_count,
@@ -47,7 +50,7 @@ class AccountPortfolioItemUMConverter(
}
}
- private fun getImageState(account: Account) = when (account) {
+ private fun getImageState(account: Account.CryptoPortfolio) = when (account) {
is Account.CryptoPortfolio -> UserWalletItemUM.ImageState.Account(
name = account.accountName.toUM().value,
icon = account.icon.toUM(),
diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt
index 137328bcf0..fcd6c07b50 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlock.kt
@@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -19,7 +20,6 @@ import androidx.compose.ui.unit.dp
import com.tangem.common.ui.account.AccountTitle
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
-import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.format.bigdecimal.crypto
@@ -66,11 +66,14 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
state = amountState.tokenIconState,
iconSize = 40.dp,
)
- ResizableText(
+ Text(
text = firstAmount,
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
+ autoSize = TextAutoSize.StepBased(
+ maxFontSize = TangemTheme.typography.h2.fontSize,
+ ),
maxLines = 1,
modifier = Modifier
.fillMaxWidth()
diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlockV2.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlockV2.kt
index 3ffd401d66..6adccd58a0 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlockV2.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/ui/AmountBlockV2.kt
@@ -4,6 +4,7 @@ import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -18,7 +19,6 @@ import com.tangem.common.ui.account.AccountTitle
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
-import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
@@ -128,21 +128,23 @@ private fun AmountBlockV2(
.padding(top = 8.dp)
.weight(1f),
) {
- ResizableText(
+ Text(
text = firstAmount,
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
maxLines = 1,
+ autoSize = TextAutoSize.StepBased(maxFontSize = TangemTheme.typography.h2.fontSize),
modifier = Modifier.testTag(BaseAmountBlockTestTags.PRIMARY_AMOUNT),
)
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
- ResizableText(
+ Text(
text = secondAmount,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
+ autoSize = TextAutoSize.StepBased(maxFontSize = TangemTheme.typography.body2.fontSize),
modifier = Modifier.testTag(BaseAmountBlockTestTags.SECONDARY_AMOUNT),
)
extraContent()
diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionsBlockState.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionsBlockState.kt
new file mode 100644
index 0000000000..870d30bc07
--- /dev/null
+++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionsBlockState.kt
@@ -0,0 +1,22 @@
+package com.tangem.common.ui.expressStatus.state
+
+import androidx.compose.runtime.Composable
+import com.tangem.common.ui.tokendetails.TokenDetailsDialogConfig
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
+import kotlinx.collections.immutable.PersistentList
+
+data class ExpressTransactionsBlockState(
+ val transactions: PersistentList,
+ val bottomSheetSlot: BottomSheetSlot?,
+ val dialogSlot: DialogSlot?,
+)
+
+data class BottomSheetSlot(
+ val config: TangemBottomSheetConfig,
+ val content: @Composable () -> Unit,
+)
+
+data class DialogSlot(
+ val config: TokenDetailsDialogConfig,
+ val content: @Composable () -> Unit,
+)
\ No newline at end of file
diff --git a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt
index 91e26bb895..e0086f9b0c 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt
@@ -1,22 +1,24 @@
package com.tangem.common.ui.news
import android.content.res.Configuration
-import androidx.compose.foundation.background
+import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CardColors
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
+import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.components.label.Label
@@ -39,23 +41,36 @@ fun ArticleCard(
modifier: Modifier = Modifier,
colors: CardColors = TangemBlockCardColors,
) {
- BlockCard(
- modifier = modifier,
- onClick = onArticleClick,
- colors = colors,
- ) {
- if (articleConfigUM.isTrending) {
- TrendingArticle(articleConfigUM = articleConfigUM)
- } else {
- DefaultArticle(articleConfigUM = articleConfigUM)
- }
+ if (articleConfigUM.isTrending) {
+ TrendingArticle(
+ articleConfigUM = articleConfigUM,
+ modifier = modifier,
+ onArticleClick = onArticleClick,
+ colors = colors,
+ )
+ } else {
+ DefaultArticle(
+ articleConfigUM = articleConfigUM,
+ modifier = modifier,
+ onArticleClick = onArticleClick,
+ colors = colors,
+ )
}
}
@Composable
-private fun TrendingArticle(articleConfigUM: ArticleConfigUM) {
+private fun TrendingArticle(
+ articleConfigUM: ArticleConfigUM,
+ onArticleClick: () -> Unit,
+ colors: CardColors,
+ modifier: Modifier = Modifier,
+) {
Column(
- modifier = Modifier.padding(vertical = 24.dp, horizontal = 16.dp),
+ modifier = modifier
+ .clip(TangemTheme.shapes.roundedCornersXMedium)
+ .background(colors.containerColor)
+ .clickable { onArticleClick() }
+ .padding(vertical = 24.dp, horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
@@ -82,6 +97,7 @@ private fun TrendingArticle(articleConfigUM: ArticleConfigUM) {
style = TangemTheme.typography.h3,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
+ textAlign = TextAlign.Center,
)
SpacerH(8.dp)
@@ -101,8 +117,54 @@ private fun TrendingArticle(articleConfigUM: ArticleConfigUM) {
}
@Composable
-private fun DefaultArticle(articleConfigUM: ArticleConfigUM) {
- Column(modifier = Modifier.padding(12.dp)) {
+fun ShowMoreArticlesCard(modifier: Modifier = Modifier, onClick: () -> Unit) {
+ BlockCard(
+ modifier = modifier,
+ onClick = onClick,
+ colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(vertical = 31.dp, horizontal = 12.dp),
+ ) {
+ Image(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_show_more_news_48),
+ contentDescription = stringResourceSafe(R.string.common_show_more),
+ )
+
+ SpacerH(16.dp)
+
+ Text(
+ text = stringResourceSafe(R.string.news_all_news),
+ style = TangemTheme.typography.subtitle1,
+ color = TangemTheme.colors.text.primary1,
+ )
+
+ Text(
+ text = stringResourceSafe(R.string.news_stay_in_the_loop),
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+ }
+}
+
+@Composable
+private fun DefaultArticle(
+ articleConfigUM: ArticleConfigUM,
+ onArticleClick: () -> Unit,
+ colors: CardColors,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier
+ .clip(TangemTheme.shapes.roundedCornersXMedium)
+ .background(colors.containerColor)
+ .clickable { onArticleClick() }
+ .padding(12.dp),
+ ) {
ArticleInfo(
score = articleConfigUM.score,
createdAt = articleConfigUM.createdAt.resolveReference(),
@@ -122,33 +184,46 @@ private fun DefaultArticle(articleConfigUM: ArticleConfigUM) {
overflow = TextOverflow.Ellipsis,
)
- SpacerH(16.dp)
+ SpacerHMax(modifier = Modifier.padding(bottom = 16.dp))
Tags(tags = articleConfigUM.tags.toImmutableList())
}
}
-@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun Tags(tags: ImmutableList, modifier: Modifier = Modifier) {
- val expandIndicator = remember {
- ContextualFlowRowOverflow.expandIndicator {
- val remainingItems = tags.size - shownItemCount
- Label(state = LabelUM(TextReference.Str("${StringsSigns.PLUS}$remainingItems")))
- }
- }
- ContextualFlowRow(
- itemCount = tags.size,
+ FlowRow(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
+ maxItemsInEachRow = MAX_TAGS_COUNT_TO_SHOW + 1,
maxLines = 1,
- overflow = expandIndicator,
- ) { index ->
- Label(state = tags[index])
+ itemVerticalAlignment = Alignment.Bottom,
+ ) {
+ val displayedTags = if (tags.size > MAX_TAGS_COUNT_TO_SHOW) {
+ tags.take(MAX_TAGS_COUNT_TO_SHOW)
+ } else {
+ tags
+ }
+
+ displayedTags.forEach { tag ->
+ Label(state = tag)
+ }
+
+ if (tags.size > MAX_TAGS_COUNT_TO_SHOW) {
+ val remainingItems = tags.size - MAX_TAGS_COUNT_TO_SHOW
+ Label(
+ state = LabelUM(
+ text = TextReference.Str("${StringsSigns.PLUS}$remainingItems"),
+ maxLines = 1,
+ ),
+ )
+ }
}
}
+private const val MAX_TAGS_COUNT_TO_SHOW = 3
+
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
diff --git a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleInfo.kt b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleInfo.kt
index f7cc656eae..a2834e2afb 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleInfo.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleInfo.kt
@@ -1,7 +1,10 @@
package com.tangem.common.ui.news
import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -9,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.res.TangemTheme
@@ -49,6 +53,8 @@ internal fun ArticleInfo(score: Float, createdAt: String, modifier: Modifier = M
Text(
text = createdAt,
style = TangemTheme.typography.subtitle2,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors.text.secondary,
)
}
diff --git a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleLoadingCard.kt b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleLoadingCard.kt
index 4ef5f1ce75..300d41f104 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleLoadingCard.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleLoadingCard.kt
@@ -41,8 +41,9 @@ fun TrendingLoadingArticle(modifier: Modifier = Modifier) {
}
@Composable
-fun DefaultLoadingArticle() {
+fun DefaultLoadingArticle(modifier: Modifier = Modifier) {
BlockCard(
+ modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
Column(modifier = Modifier.padding(12.dp)) {
diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt
index 668943b4c7..1cedf7d5b3 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt
@@ -21,7 +21,7 @@ import com.tangem.utils.extensions.isZero
import com.tangem.utils.extensions.orZero
import java.math.BigDecimal
-@Suppress("LargeClass")
+@Suppress("LargeClass", "CanBeNonNullable")
object NotificationsFactory {
fun MutableList.addFeeUnreachableNotification(
@@ -314,12 +314,14 @@ object NotificationsFactory {
}
}
+ @Suppress("LongParameterList")
fun MutableList.addExceedsBalanceNotification(
cryptoCurrencyWarning: CryptoCurrencyWarning?,
cryptoCurrencyStatus: CryptoCurrencyStatus,
shouldMergeFeeNetworkName: Boolean,
onClick: (CryptoCurrency) -> Unit,
onAnalyticsEvent: (CryptoCurrency) -> Unit,
+ onResetAnalyticsEvent: (CryptoCurrency) -> Unit,
) {
when (cryptoCurrencyWarning) {
is CryptoCurrencyWarning.BalanceNotEnoughForFee -> {
@@ -357,7 +359,7 @@ object NotificationsFactory {
)
onAnalyticsEvent(cryptoCurrencyWarning.currency)
}
- else -> Unit
+ else -> onResetAnalyticsEvent(cryptoCurrencyStatus.currency)
}
}
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt b/common/ui/src/main/java/com/tangem/common/ui/tokendetails/TokenDetailsDialogConfig.kt
similarity index 95%
rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt
rename to common/ui/src/main/java/com/tangem/common/ui/tokendetails/TokenDetailsDialogConfig.kt
index 7df750dad1..8681f259a6 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsDialogConfig.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/tokendetails/TokenDetailsDialogConfig.kt
@@ -1,9 +1,9 @@
-package com.tangem.feature.tokendetails.presentation.tokendetails.state.components
+package com.tangem.common.ui.tokendetails
+import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
-import com.tangem.features.tokendetails.impl.R
/**
* Wallet bottom sheet config
@@ -12,7 +12,7 @@ import com.tangem.features.tokendetails.impl.R
* @property onDismissRequest lambda be invoked when bottom sheet is dismissed
* @property content content config
*/
-internal data class TokenDetailsDialogConfig(
+data class TokenDetailsDialogConfig(
val isShow: Boolean,
val onDismissRequest: () -> Unit,
val content: DialogContentConfig,
@@ -28,7 +28,7 @@ internal data class TokenDetailsDialogConfig(
data class ButtonConfig(
val text: TextReference,
val onClick: () -> Unit,
- val warning: Boolean = false,
+ val hasWarning: Boolean = false,
)
data class ConfirmHideConfig(
@@ -51,7 +51,7 @@ internal data class TokenDetailsDialogConfig(
override val confirmButtonConfig: ButtonConfig = ButtonConfig(
text = TextReference.Res(R.string.token_details_hide_alert_hide),
onClick = onConfirmClick,
- warning = true,
+ hasWarning = true,
)
}
diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt
index 06cfd97a8a..6211063758 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt
@@ -23,7 +23,8 @@ import com.tangem.domain.models.currency.yieldSupplyKey
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingOption
-import com.tangem.domain.staking.model.stakekit.Yield
+import com.tangem.domain.staking.model.common.RewardInfo
+import com.tangem.domain.staking.model.common.RewardType
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.StringsSigns.DASH_SIGN
@@ -45,7 +46,7 @@ class TokenItemStateConverter(
private val appCurrency: AppCurrency,
private val yieldModuleApyMap: Map = emptyMap(),
private val stakingApyMap: Map = emptyMap(),
- private val yieldSupplyPromoBannerKey: String? = null,
+ private val promoCryptoCurrencyStatus: CryptoCurrencyStatus? = null,
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
CryptoCurrencyToIconStateConverter().convert(it)
},
@@ -74,7 +75,7 @@ class TokenItemStateConverter(
createPromoBannerState(
status = status,
yieldModuleApyMap = yieldModuleApyMap,
- yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKey,
+ promoCryptoCurrencyStatus = promoCryptoCurrencyStatus,
onApyLabelClick = onApyLabelClick,
onYieldPromoCloseClick = onYieldPromoCloseClick,
onYieldPromoShown = onYieldPromoShown,
@@ -251,9 +252,9 @@ class TokenItemStateConverter(
stakingApyMap = stakingApyMap,
)
val rewardTypeRes = when (stakingInfo.rewardType) {
- Yield.RewardType.APR -> R.string.staking_apr_earn_badge
- Yield.RewardType.UNKNOWN,
- Yield.RewardType.APY,
+ RewardType.APR -> R.string.staking_apr_earn_badge
+ RewardType.UNKNOWN,
+ RewardType.APY,
null,
-> R.string.yield_module_earn_badge
}
@@ -283,12 +284,14 @@ class TokenItemStateConverter(
val stakingBalance = currencyStatus.value.stakingBalance as? StakingBalance.Data
val stakeKitBalance = stakingBalance as? StakingBalance.Data.StakeKit
+ val p2pEthPoolBalance = stakingBalance as? StakingBalance.Data.P2PEthPool
val rateInfo = when (val stakingOptions = stakingAvailability.option) {
- is StakingOption.P2P -> {
- // P2P or no balance: use preferred validators
- // TODO add p2p logic
- null
+ is StakingOption.P2PEthPool -> {
+ RewardInfo(
+ rate = stakingOptions.apy,
+ type = RewardType.APY,
+ )
}
is StakingOption.StakeKit -> if (stakeKitBalance != null) {
val validatorsByAddress = stakingOptions.yield.validators.associateBy { it.address }
@@ -314,7 +317,7 @@ class TokenItemStateConverter(
return StakingLocalInfo(
rate = rateInfo?.rate,
- isActive = stakeKitBalance != null, // todo add p2p check
+ isActive = stakeKitBalance != null || p2pEthPoolBalance != null,
rewardType = rateInfo?.type,
)
}
@@ -401,7 +404,7 @@ class TokenItemStateConverter(
private fun createPromoBannerState(
status: CryptoCurrencyStatus,
yieldModuleApyMap: Map,
- yieldSupplyPromoBannerKey: String?,
+ promoCryptoCurrencyStatus: CryptoCurrencyStatus?,
onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?,
onYieldPromoCloseClick: (() -> Unit)?,
onYieldPromoShown: ((cryptoCurrency: CryptoCurrency) -> Unit)?,
@@ -411,13 +414,15 @@ class TokenItemStateConverter(
if (status.value !is CryptoCurrencyStatus.Loaded) {
return TokenItemState.PromoBannerState.Empty
}
- if (yieldSupplyPromoBannerKey == null || yieldSupplyPromoBannerKey != token.yieldSupplyKey() ||
- yieldModuleApyMap[token.yieldSupplyKey()] == null
- ) {
+ if (promoCryptoCurrencyStatus == null || status.currency.id != promoCryptoCurrencyStatus.currency.id) {
return TokenItemState.PromoBannerState.Empty
}
- val yieldSupplyApy =
- yieldModuleApyMap[token.yieldSupplyKey()] ?: return TokenItemState.PromoBannerState.Empty
+ val yieldSupplyApy = yieldModuleApyMap.entries.firstOrNull { (key, _) ->
+ key.equals(
+ other = token.yieldSupplyKey(),
+ ignoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId),
+ )
+ }?.value ?: return TokenItemState.PromoBannerState.Empty
return TokenItemState.PromoBannerState.Content(
title = resourceReference(
@@ -469,7 +474,7 @@ class TokenItemStateConverter(
private data class StakingLocalInfo(
val rate: BigDecimal?,
val isActive: Boolean,
- val rewardType: Yield.RewardType?,
+ val rewardType: RewardType?,
)
private data class EarnApyInfo(
diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt
index b8f7104b03..f5643c66c3 100644
--- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt
+++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt
@@ -35,6 +35,7 @@ class UserWalletItemUMConverter(
private val isClickableIfLocked: Boolean = false,
private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None,
artwork: UserWalletItemUM.ImageState? = null,
+ private val mode: InfoField = InfoField.Devices,
) : Converter {
private val artwork = artwork ?: UserWalletItemUM.ImageState.Loading
@@ -61,17 +62,29 @@ class UserWalletItemUMConverter(
private fun getInfo(userWallet: UserWallet): UserWalletItemUM.Information.Loaded {
val text = when (userWallet) {
is UserWallet.Cold -> {
- val cardCount = userWallet.getCardsCount() ?: 1
- TextReference.PluralRes(
- id = R.plurals.card_label_card_count,
- count = cardCount,
- formatArgs = wrappedList(cardCount),
- )
+ when (mode) {
+ is InfoField.Devices -> {
+ val cardCount = userWallet.getCardsCount() ?: 1
+ TextReference.PluralRes(
+ id = R.plurals.card_label_card_count,
+ count = cardCount,
+ formatArgs = wrappedList(cardCount),
+ )
+ }
+ is InfoField.Tokens -> {
+ TextReference.PluralRes(
+ id = R.plurals.common_tokens_count,
+ count = mode.tokensCount,
+ formatArgs = wrappedList(mode.tokensCount),
+ )
+ }
+ }
}
is UserWallet.Hot -> {
TextReference.Res(R.string.hw_mobile_wallet)
}
}
+
return UserWalletItemUM.Information.Loaded(text)
}
@@ -103,4 +116,9 @@ class UserWalletItemUMConverter(
}
}
}
+
+ sealed interface InfoField {
+ data object Devices : InfoField
+ data class Tokens(val tokensCount: Int) : InfoField
+ }
}
\ No newline at end of file
diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt
index 9488b9c840..837e466433 100644
--- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt
+++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt
@@ -1,5 +1,7 @@
package com.tangem.core.analytics.models
+const val IS_NOT_HTTP_ERROR = "Is not http error"
+
sealed class AnalyticsParam {
sealed class CardBalanceState(val value: String) {
@@ -77,7 +79,9 @@ sealed class AnalyticsParam {
data object Backup : ScreensSources("Backup")
data object Onboarding : ScreensSources("Onboarding")
data object LongTap : ScreensSources("Long Tap")
+ data object Market : ScreensSources("Market")
data object Markets : ScreensSources("Markets")
+ data object MarketPulse : ScreensSources("Market Pulse")
data object TangemPay : ScreensSources("Tangem Pay")
data object WalletSettings : ScreensSources("Wallet Settings")
data object Upgrade : ScreensSources("Upgrade")
@@ -86,6 +90,9 @@ sealed class AnalyticsParam {
data object CreateWalletIntro : ScreensSources("Create Wallet Intro")
data object AddNewWallet : ScreensSources("Add New Wallet")
data object CreateWallet : ScreensSources("Create Wallet")
+ data object NewsList : ScreensSources("News List")
+ data object NewsLink : ScreensSources("News Link")
+ data object NewsPage : ScreensSources("News Page")
}
sealed class TxSentFrom(val value: String) {
@@ -93,24 +100,28 @@ sealed class AnalyticsParam {
override val blockchain: String,
override val token: String,
override val feeType: FeeType,
+ override val feeToken: String,
) : TxSentFrom("Send"), TxData
data class Swap(
override val blockchain: String,
override val token: String,
override val feeType: FeeType,
+ override val feeToken: String,
) : TxSentFrom("Swap"), TxData
data class Staking(
override val blockchain: String,
override val token: String,
override val feeType: FeeType,
+ override val feeToken: String,
) : TxSentFrom("Staking"), TxData
data class Approve(
override val blockchain: String,
override val token: String,
override val feeType: FeeType,
+ override val feeToken: String,
val permissionType: String,
) : TxSentFrom("Approve"), TxData
@@ -118,6 +129,7 @@ sealed class AnalyticsParam {
override val blockchain: String,
override val token: String,
override val feeType: FeeType?,
+ override val feeToken: String,
) : TxSentFrom("WalletConnect"), TxData
data object Sell : TxSentFrom("Sell")
@@ -126,24 +138,28 @@ sealed class AnalyticsParam {
override val blockchain: String,
override val token: String,
override val feeType: FeeType,
+ override val feeToken: String,
) : TxSentFrom("NFT"), TxData
data class SendWithSwap(
override val blockchain: String,
override val token: String,
override val feeType: FeeType,
+ override val feeToken: String,
) : TxSentFrom("Send&Swap"), TxData
data class Earning(
override val blockchain: String,
override val token: String,
override val feeType: FeeType,
+ override val feeToken: String,
) : TxSentFrom("Earning"), TxData
}
sealed interface TxData {
val blockchain: String
val token: String
+ val feeToken: String
val feeType: FeeType?
}
@@ -267,5 +283,6 @@ sealed class AnalyticsParam {
const val ENS = "ENS"
const val ENS_ADDRESS = "ENS Address"
const val ACCOUNT_DERIVATION_FROM = "Account Derivation (from)"
+ const val FEE_TOKEN = "Fee Token"
}
}
\ No newline at end of file
diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt
index 7a67705605..3e71b47241 100644
--- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt
+++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt
@@ -87,6 +87,7 @@ sealed class Basic(
sentFrom.feeType?.value?.let {
this[AnalyticsParam.Key.FEE_TYPE] = it
}
+ this[AnalyticsParam.Key.FEE_TOKEN] = sentFrom.feeToken
}
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
this[AnalyticsParam.Key.PERMISSION_TYPE] = sentFrom.permissionType
diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/OneTimePerSessionEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/OneTimePerSessionEvent.kt
new file mode 100644
index 0000000000..a94df878eb
--- /dev/null
+++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/OneTimePerSessionEvent.kt
@@ -0,0 +1,22 @@
+package com.tangem.core.analytics.models
+
+/**
+ * Marker interface for analytics events that should be sent only once per session.
+ *
+ * Events implementing this interface will be tracked by their [oneTimeEventId] to ensure
+ * they are not sent multiple times during the same application session. Once an event
+ * with a specific [oneTimeEventId] has been sent, subsequent attempts to send an event
+ * with the same ID will be ignored.
+ *
+ * @see Analytics.send
+ */
+interface OneTimePerSessionEvent {
+ /**
+ * Unique identifier for the one-time event.
+ *
+ * This ID is used to track whether the event has already been sent in the current session.
+ * Events with the same [oneTimeEventId] will only be sent once, even if they are
+ * different instances of the same event class.
+ */
+ val oneTimeEventId: String
+}
\ No newline at end of file
diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt
index 9703947954..c5630c4906 100644
--- a/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt
+++ b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt
@@ -6,6 +6,7 @@ import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.api.*
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
+import com.tangem.core.analytics.models.OneTimePerSessionEvent
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
@@ -31,6 +32,7 @@ object Analytics : GlobalAnalyticsEventHandler {
private val handlers = mutableMapOf()
private val paramsInterceptors = ConcurrentHashMap()
+ private val oneEventsPerSession = ConcurrentHashMap()
private val analyticsFilters = mutableSetOf()
private val analyticsMutex = Mutex()
@@ -85,6 +87,11 @@ object Analytics : GlobalAnalyticsEventHandler {
override fun send(event: AnalyticsEvent) {
analyticsScope.launch {
+ if (event is OneTimePerSessionEvent &&
+ oneEventsPerSession.putIfAbsent(event.oneTimeEventId, true) != null
+ ) {
+ return@launch
+ }
event.params = applyParamsInterceptors(event)
val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) }
diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/api/ResettableOneTimeEventSender.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/ResettableOneTimeEventSender.kt
new file mode 100644
index 0000000000..3518dacfd8
--- /dev/null
+++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/ResettableOneTimeEventSender.kt
@@ -0,0 +1,19 @@
+package com.tangem.core.analytics.api
+
+import com.tangem.core.analytics.models.AnalyticsEvent
+import java.util.concurrent.ConcurrentHashMap
+
+class ResettableOneTimeEventSender(private val analyticsEventHandler: AnalyticsEventHandler) {
+
+ private val sentEvents = ConcurrentHashMap()
+
+ fun sendEventOnce(key: String, event: AnalyticsEvent) {
+ if (sentEvents.putIfAbsent(key, event) == null) {
+ analyticsEventHandler.send(event)
+ }
+ }
+
+ fun reset(key: String) {
+ sentEvents.remove(key)
+ }
+}
\ No newline at end of file
diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json
index abff08deb7..1c24b1d40c 100644
--- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json
+++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json
@@ -59,16 +59,32 @@
"name": "YIELD_SUPPLY_FEATURE_ENABLED",
"version": "5.30.0"
},
+ {
+ "name": "YIELD_SUPPLY_PENDING_TRANSACTIONS_ENABLED",
+ "version": "5.33.0"
+ },
{
"name": "NEW_ONRAMP_MAIN_ENABLED",
"version": "5.31.0"
},
{
"name": "ACCOUNTS_FEATURE_ENABLED",
- "version": "undefined"
+ "version": "5.33.0"
},
{
"name": "FEED_ENABLED",
+ "version": "5.33.0"
+ },
+ {
+ "name": "APP_REDESIGN_ENABLED",
+ "version": "undefined"
+ },
+ {
+ "name": "GASLESS_TRANSACTIONS_ENABLED",
+ "version": "5.33.0"
+ },
+ {
+ "name": "SWAP_MARKET_LIST_ENABLED",
"version": "undefined"
}
]
diff --git a/core/datasource/detekt-baseline-debug.xml b/core/datasource/detekt-baseline-debug.xml
index 7d14c576a1..08b13f32ec 100644
--- a/core/datasource/detekt-baseline-debug.xml
+++ b/core/datasource/detekt-baseline-debug.xml
@@ -12,6 +12,5 @@
NestedScopeFunctions:RetrofitApiBuilder.kt$RetrofitApiBuilder$let { withWriteTimeout(timeout = it.duration, unit = it.unit) }
UnreachableCode:MockApiConfigsManager.kt$MockApiConfigsManager$apiConfigs + (apiConfig to environment)
UnreachableCode:MockApiConfigsManager.kt$MockApiConfigsManager$val apiConfig = apiConfigs.keys.firstOrNull { it.id.name == id } ?: error("Api config with id [$id] not found. Check that ApiConfig with id [$id] was provided into DI")
- UnusedImports:NetworkModule.kt$import com.tangem.datasource.api.common.config.MoonPay
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt
index 002f8656ed..75cf5c12bd 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/AuthProvider.kt
@@ -18,6 +18,8 @@ interface AuthProvider {
fun getApiKey(apiEnvironment: Provider): ProviderSuspend
+ fun getGaslessServiceApiKey(apiEnvironment: Provider): ProviderSuspend
+
/**
* Returns map where keys(cardId) associated with cardPublicKey
*/
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiConfig.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiConfig.kt
index 8e0a661871..515a93b2f8 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiConfig.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/ApiConfig.kt
@@ -31,6 +31,7 @@ sealed class ApiConfig {
YieldSupply,
MoonPay,
News,
+ GaslessTxService,
}
private fun initializeId(): ID {
@@ -39,12 +40,13 @@ sealed class ApiConfig {
is TangemTech -> ID.TangemTech
is StakeKit -> ID.StakeKit
is P2PEthPool -> ID.P2PEthPool
- is TangemPay -> ID.TangemPay
- is TangemPayAuth -> ID.TangemPayAuth
+ is TangemPay.Bff -> ID.TangemPay
+ is TangemPay.Auth -> ID.TangemPayAuth
is BlockAid -> ID.BlockAid
is YieldSupply -> ID.YieldSupply
is MoonPay -> ID.MoonPay
is News -> ID.News
+ is GaslessTxService -> ID.GaslessTxService
}
}
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/GaslessTxService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/GaslessTxService.kt
new file mode 100644
index 0000000000..35935f0c9d
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/GaslessTxService.kt
@@ -0,0 +1,66 @@
+package com.tangem.datasource.api.common.config
+
+import com.tangem.datasource.BuildConfig
+import com.tangem.datasource.api.common.AuthProvider
+import com.tangem.datasource.utils.RequestHeader
+import com.tangem.utils.Provider
+import com.tangem.utils.ProviderSuspend
+import com.tangem.utils.info.AppInfoProvider
+import com.tangem.utils.version.AppVersionProvider
+
+/**
+ * Gasless transactions [ApiConfig]
+ */
+internal class GaslessTxService(
+ private val authProvider: AuthProvider,
+ private val appVersionProvider: AppVersionProvider,
+ private val appInfoProvider: AppInfoProvider,
+) : ApiConfig() {
+
+ override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
+
+ override val environmentConfigs: List = listOf(
+ createProdEnvironment(),
+ createDevEnvironment(),
+ )
+
+ private fun getInitialEnvironment(): ApiEnvironment {
+ return when (BuildConfig.BUILD_TYPE) {
+ MOCKED_BUILD_TYPE,
+ DEBUG_BUILD_TYPE,
+ -> ApiEnvironment.DEV
+ INTERNAL_BUILD_TYPE,
+ EXTERNAL_BUILD_TYPE,
+ RELEASE_BUILD_TYPE,
+ -> ApiEnvironment.PROD
+ else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
+ }
+ }
+
+ private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
+ environment = ApiEnvironment.PROD,
+ baseUrl = PROD_BASE_URL,
+ headers = createHeaders(ApiEnvironment.PROD),
+ )
+
+ private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
+ environment = ApiEnvironment.DEV,
+ baseUrl = DEV_BASE_URL,
+ headers = createHeaders(ApiEnvironment.DEV),
+ )
+
+ private fun createHeaders(environment: ApiEnvironment) = buildMap {
+ putAll(RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
+ put(
+ key = "Authorization",
+ value = ProviderSuspend {
+ "Bearer ${authProvider.getGaslessServiceApiKey(Provider { environment }).invoke()}"
+ },
+ )
+ }
+
+ private companion object {
+ private const val PROD_BASE_URL = "https://gasless.tangem.org/"
+ private const val DEV_BASE_URL = "[REDACTED_ENV_URL]"
+ }
+}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/News.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/News.kt
index 23ff32ba22..fc57478ec5 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/News.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/News.kt
@@ -4,12 +4,16 @@ import com.tangem.datasource.BuildConfig
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.utils.RequestHeader
import com.tangem.utils.Provider
+import com.tangem.utils.info.AppInfoProvider
+import com.tangem.utils.version.AppVersionProvider
/**
* News [ApiConfig]
[REDACTED_AUTHOR]
*/
internal class News(
+ private val appVersionProvider: AppVersionProvider,
+ private val appInfoProvider: AppInfoProvider,
private val authProvider: AuthProvider,
) : ApiConfig() {
@@ -52,6 +56,7 @@ internal class News(
apiEnvironment = Provider { environment },
).values,
)
+ putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
}
private companion object {
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/P2PEthPool.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/P2PEthPool.kt
index aa1b28c629..0dbc3bb4bb 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/P2PEthPool.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/P2PEthPool.kt
@@ -1,7 +1,7 @@
package com.tangem.datasource.api.common.config
import com.tangem.datasource.BuildConfig
-import com.tangem.domain.staking.model.ethpool.P2PStakingConfig
+import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
import com.tangem.lib.auth.P2PEthPoolAuthProvider
import com.tangem.utils.ProviderSuspend
@@ -23,7 +23,7 @@ internal class P2PEthPool(
private fun getInitialEnvironment(): ApiEnvironment {
return when (BuildConfig.BUILD_TYPE) {
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
- else -> if (P2PStakingConfig.USE_TESTNET) ApiEnvironment.DEV else ApiEnvironment.PROD
+ else -> if (P2PEthPoolStakingConfig.USE_TESTNET) ApiEnvironment.DEV else ApiEnvironment.PROD
}
}
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemPay.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemPay.kt
index c0a5c07775..b494cbc70c 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemPay.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemPay.kt
@@ -1,11 +1,13 @@
package com.tangem.datasource.api.common.config
import com.tangem.datasource.BuildConfig
+import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.version.AppVersionProvider
-internal class TangemPay(
+internal sealed class TangemPay(
private val appVersionProvider: AppVersionProvider,
+ private val environmentConfigStorage: EnvironmentConfigStorage,
) : ApiConfig() {
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
@@ -16,6 +18,8 @@ internal class TangemPay(
createProdEnvironment(),
)
+ protected abstract fun getBaseUrl(apiEnvironment: ApiEnvironment): String
+
private fun getInitialEnvironment(): ApiEnvironment {
return when (BuildConfig.BUILD_TYPE) {
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
@@ -31,24 +35,75 @@ internal class TangemPay(
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.DEV,
- baseUrl = "https://api.dev.us.paera.com/bff-v2/",
- headers = createHeaders(),
+ baseUrl = getBaseUrl(ApiEnvironment.DEV),
+ headers = createHeaders(ApiEnvironment.DEV),
)
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.MOCK,
- baseUrl = "[REDACTED_ENV_URL]",
- headers = createHeaders(),
+ baseUrl = getBaseUrl(ApiEnvironment.MOCK),
+ headers = createHeaders(ApiEnvironment.MOCK),
)
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.PROD,
- baseUrl = "https://api.us.paera.com/bff-v2/",
- headers = createHeaders(),
+ baseUrl = getBaseUrl(ApiEnvironment.PROD),
+ headers = createHeaders(ApiEnvironment.PROD),
)
- private fun createHeaders() = mapOf(
+ private fun createHeaders(apiEnvironment: ApiEnvironment) = mapOf(
"version" to ProviderSuspend { appVersionProvider.versionName },
"platform" to ProviderSuspend { "Android" },
+ "X-API-KEY" to ProviderSuspend { getBffStaticToken(apiEnvironment) },
)
+
+ private fun getBffStaticToken(apiEnvironment: ApiEnvironment): String {
+ return when (apiEnvironment) {
+ ApiEnvironment.MOCK,
+ ApiEnvironment.DEV,
+ -> environmentConfigStorage.getConfigSync().bffStaticTokenDev
+ ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().bffStaticToken
+ ApiEnvironment.STAGE,
+ ApiEnvironment.STAGE_2,
+ ApiEnvironment.DEV_2,
+ ApiEnvironment.DEV_3,
+ -> null
+ } ?: error("BffStaticToken is not provided for $apiEnvironment")
+ }
+
+ class Bff(
+ appVersionProvider: AppVersionProvider,
+ environmentConfigStorage: EnvironmentConfigStorage,
+ ) : TangemPay(appVersionProvider, environmentConfigStorage) {
+ override fun getBaseUrl(apiEnvironment: ApiEnvironment): String {
+ return when (apiEnvironment) {
+ ApiEnvironment.DEV -> "https://api.dev.us.paera.com/bff-v2/"
+ ApiEnvironment.MOCK -> "[REDACTED_ENV_URL]"
+ ApiEnvironment.PROD -> "https://api.us.paera.com/bff-v2/"
+ ApiEnvironment.DEV_2,
+ ApiEnvironment.DEV_3,
+ ApiEnvironment.STAGE,
+ ApiEnvironment.STAGE_2,
+ -> error("Unknown environment: $apiEnvironment")
+ }
+ }
+ }
+
+ class Auth(
+ appVersionProvider: AppVersionProvider,
+ environmentConfigStorage: EnvironmentConfigStorage,
+ ) : TangemPay(appVersionProvider, environmentConfigStorage) {
+ override fun getBaseUrl(apiEnvironment: ApiEnvironment): String {
+ return when (apiEnvironment) {
+ ApiEnvironment.DEV -> "https://api.dev.us.paera.com/"
+ ApiEnvironment.MOCK -> "[REDACTED_ENV_URL]"
+ ApiEnvironment.PROD -> "https://api.us.paera.com/"
+ ApiEnvironment.DEV_2,
+ ApiEnvironment.DEV_3,
+ ApiEnvironment.STAGE,
+ ApiEnvironment.STAGE_2,
+ -> error("Unknown environment: $apiEnvironment")
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemPayAuth.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemPayAuth.kt
deleted file mode 100644
index cbbc29459d..0000000000
--- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemPayAuth.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.tangem.datasource.api.common.config
-
-import com.tangem.datasource.BuildConfig
-import com.tangem.utils.ProviderSuspend
-import com.tangem.utils.version.AppVersionProvider
-
-internal class TangemPayAuth(
- private val appVersionProvider: AppVersionProvider,
-) : ApiConfig() {
- override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
-
- override val environmentConfigs = listOf(
- createDevEnvironment(),
- createMockedEnvironment(),
- createProdEnvironment(),
- )
-
- private fun getInitialEnvironment(): ApiEnvironment {
- return when (BuildConfig.BUILD_TYPE) {
- MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
- DEBUG_BUILD_TYPE,
- INTERNAL_BUILD_TYPE,
- -> ApiEnvironment.DEV
- EXTERNAL_BUILD_TYPE,
- RELEASE_BUILD_TYPE,
- -> ApiEnvironment.PROD
- else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
- }
- }
-
- private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
- environment = ApiEnvironment.DEV,
- baseUrl = "https://api.dev.us.paera.com/",
- headers = createHeaders(),
- )
-
- private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
- environment = ApiEnvironment.MOCK,
- baseUrl = "[REDACTED_ENV_URL]",
- headers = createHeaders(),
- )
-
- private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
- environment = ApiEnvironment.PROD,
- baseUrl = "https://api.us.paera.com/",
- headers = createHeaders(),
- )
-
- private fun createHeaders() = mapOf(
- "version" to ProviderSuspend { appVersionProvider.versionName },
- "platform" to ProviderSuspend { "Android" },
- )
-}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/P2PEthPoolApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/P2PEthPoolApi.kt
index cf3ab2f3e5..f76e0a7bfa 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/P2PEthPoolApi.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/P2PEthPoolApi.kt
@@ -2,9 +2,7 @@ package com.tangem.datasource.api.ethpool
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolBroadcastRequest
-import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolDepositRequest
-import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolUnstakeRequest
-import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolWithdrawRequest
+import com.tangem.datasource.api.ethpool.models.request.P2PEthPoolTransactionRequest
import com.tangem.datasource.api.ethpool.models.response.*
import retrofit2.http.*
@@ -31,13 +29,13 @@ interface P2PEthPoolApi {
* Create unsigned transaction for depositing ETH into a vault.
*
* @param network Ethereum pool network: "mainnet" or "hoodi"
- * @param body Deposit parameters (delegator address, vault address, amount)
+ * @param body Transaction parameters (delegator address, vault address, amount)
*/
@POST("api/v1/staking/pool/{network}/staking/deposit")
suspend fun createDepositTransaction(
@Path("network") network: String,
- @Body body: P2PEthPoolDepositRequest,
- ): ApiResponse>
+ @Body body: P2PEthPoolTransactionRequest,
+ ): ApiResponse>
/**
* Prepare unstake transaction
@@ -45,13 +43,13 @@ interface P2PEthPoolApi {
* Create unsigned transaction to initiate unstaking process.
*
* @param network Ethereum pool network: "mainnet" or "hoodi"
- * @param body Unstake parameters (staker public key, stake transaction hash)
+ * @param body Transaction parameters (delegator address, vault address, amount)
*/
@POST("api/v1/staking/pool/{network}/staking/unstake")
suspend fun createUnstakeTransaction(
@Path("network") network: String,
- @Body body: P2PEthPoolUnstakeRequest,
- ): ApiResponse>
+ @Body body: P2PEthPoolTransactionRequest,
+ ): ApiResponse>
/**
* Prepare withdrawal transaction
@@ -59,13 +57,13 @@ interface P2PEthPoolApi {
* Create unsigned transaction to withdraw available funds from exit queue.
*
* @param network Ethereum pool network: "mainnet" or "hoodi"
- * @param body Withdrawal parameters (staker address)
+ * @param body Transaction parameters (delegator address, vault address, amount)
*/
@POST("api/v1/staking/pool/{network}/staking/withdraw")
suspend fun createWithdrawTransaction(
@Path("network") network: String,
- @Body body: P2PEthPoolWithdrawRequest,
- ): ApiResponse>
+ @Body body: P2PEthPoolTransactionRequest,
+ ): ApiResponse>
/**
* Broadcast signed transaction
@@ -96,22 +94,4 @@ interface P2PEthPoolApi {
@Path("delegatorAddress") delegatorAddress: String,
@Path("vaultAddress") vaultAddress: String,
): ApiResponse>
-
- /**
- * Get rewards history
- *
- * Retrieve historical rewards data for a specific account and vault.
- *
- * @param network Ethereum pool network: "mainnet" or "hoodi"
- * @param delegatorAddress Account address that initiated staking
- * @param vaultAddress Ethereum address of the vault
- * @param period Optional period filter (30, 60, or 90 days)
- */
- @GET("api/v1/staking/pool/{network}/account/{delegatorAddress}/vault/{vaultAddress}/rewards")
- suspend fun getRewards(
- @Path("network") network: String,
- @Path("delegatorAddress") delegatorAddress: String,
- @Path("vaultAddress") vaultAddress: String,
- @Query("period") period: Int? = null,
- ): ApiResponse>
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolDepositRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolDepositRequest.kt
deleted file mode 100644
index e4b3200add..0000000000
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolDepositRequest.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.tangem.datasource.api.ethpool.models.request
-
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-
-/**
- * Request body for creating deposit transaction
- *
- * Used in: POST /api/v1/staking/pool/{network}/staking/deposit
- */
-@JsonClass(generateAdapter = true)
-data class P2PEthPoolDepositRequest(
- @Json(name = "delegatorAddress")
- val delegatorAddress: String,
- @Json(name = "vaultAddress")
- val vaultAddress: String,
- @Json(name = "amount")
- val amount: Double,
-)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolTransactionRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolTransactionRequest.kt
new file mode 100644
index 0000000000..500fc969be
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolTransactionRequest.kt
@@ -0,0 +1,23 @@
+package com.tangem.datasource.api.ethpool.models.request
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import java.math.BigDecimal
+
+/**
+ * Unified request body for creating staking transactions (deposit, unstake, withdraw)
+ *
+ * Used in:
+ * - POST /api/v1/staking/pool/{network}/staking/deposit
+ * - POST /api/v1/staking/pool/{network}/staking/unstake
+ * - POST /api/v1/staking/pool/{network}/staking/withdraw
+ */
+@JsonClass(generateAdapter = true)
+data class P2PEthPoolTransactionRequest(
+ @Json(name = "delegatorAddress")
+ val delegatorAddress: String,
+ @Json(name = "vaultAddress")
+ val vaultAddress: String,
+ @Json(name = "amount")
+ val amount: BigDecimal,
+)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolUnstakeRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolUnstakeRequest.kt
deleted file mode 100644
index 0e56cd5112..0000000000
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolUnstakeRequest.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.tangem.datasource.api.ethpool.models.request
-
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-
-/**
- * Request body for creating unstake transaction
- *
- * Used in: POST /api/v1/staking/pool/{network}/staking/unstake
- *
- * Note: Documentation seems to contain Bitcoin-related fields (possibly copy-paste error).
- * Using as-is per specification.
- */
-@JsonClass(generateAdapter = true)
-data class P2PEthPoolUnstakeRequest(
- @Json(name = "stakerPublicKey")
- val stakerPublicKey: String,
- @Json(name = "stakeTransactionHash")
- val stakeTransactionHash: String,
-)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolWithdrawRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolWithdrawRequest.kt
deleted file mode 100644
index d0a7aac5cf..0000000000
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/request/P2PEthPoolWithdrawRequest.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.tangem.datasource.api.ethpool.models.request
-
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-
-/**
- * Request body for creating withdrawal transaction
- *
- * Used in: POST /api/v1/staking/pool/{network}/staking/withdraw
- */
-@JsonClass(generateAdapter = true)
-data class P2PEthPoolWithdrawRequest(
- @Json(name = "stakerAddress")
- val stakerAddress: String,
-)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolAccountResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolAccountResponse.kt
index 526c3c7d16..647750c927 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolAccountResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolAccountResponse.kt
@@ -34,7 +34,7 @@ data class P2PEthPoolStakeDTO(
@JsonClass(generateAdapter = true)
data class P2PEthPoolExitQueueDTO(
@Json(name = "total")
- val total: Double,
+ val total: BigDecimal,
@Json(name = "requests")
val requests: List,
)
@@ -44,11 +44,11 @@ data class P2PEthPoolExitRequestDTO(
@Json(name = "ticket")
val ticket: String,
@Json(name = "totalAssets")
- val totalAssets: Double,
+ val totalAssets: BigDecimal,
@Json(name = "timestamp")
val timestamp: Long,
@Json(name = "withdrawalTimestamp")
- val withdrawalTimestamp: Long,
+ val withdrawalTimestamp: Long?,
@Json(name = "isClaimable")
val isClaimable: Boolean,
)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolBroadcastResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolBroadcastResponse.kt
index 20ec7b67f8..1970814c8c 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolBroadcastResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolBroadcastResponse.kt
@@ -29,7 +29,7 @@ data class P2PEthPoolBroadcastResponse(
)
/**
- * Transaction status from P2P API
+ * Transaction status from P2PEthPool API
*/
@JsonClass(generateAdapter = false)
enum class P2PEthPoolTxStatusDTO {
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolDepositResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolDepositResponse.kt
deleted file mode 100644
index ed922bf827..0000000000
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolDepositResponse.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.tangem.datasource.api.ethpool.models.response
-
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-import org.joda.time.DateTime
-
-/**
- * Response for POST /api/v1/staking/pool/{network}/staking/deposit
- */
-@JsonClass(generateAdapter = true)
-data class P2PEthPoolDepositResponse(
- @Json(name = "amount")
- val amount: Double,
- @Json(name = "vaultAddress")
- val vaultAddress: String,
- @Json(name = "delegatorAddress")
- val delegatorAddress: String,
- @Json(name = "unsignedTransaction")
- val unsignedTransaction: P2PEthPoolUnsignedTxDTO,
- @Json(name = "createdAt")
- val createdAt: DateTime,
-)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolErrorResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolErrorResponse.kt
index 88235092a1..c99f726d91 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolErrorResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolErrorResponse.kt
@@ -4,9 +4,9 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
- * Error response structure for P2P.org API
+ * Error response structure for P2P.org eth pooled API
*
- * All P2P API endpoints return errors in this format
+ * All P2PEthPool API endpoints return errors in this format
*/
@JsonClass(generateAdapter = true)
data class P2PEthPoolErrorResponse(
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolResponse.kt
index e9fe22fd51..d871ebbea8 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolResponse.kt
@@ -6,7 +6,7 @@ import com.squareup.moshi.JsonClass
/**
* Unified response wrapper for all P2P.org API responses
*
- * All P2P API endpoints return responses in this format:
+ * All P2PEthPool API endpoints return responses in this format:
* ```json
* {
* "error": null | { code, message, name, errors },
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolRewardsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolRewardsResponse.kt
deleted file mode 100644
index ef4dd03ff0..0000000000
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolRewardsResponse.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.tangem.datasource.api.ethpool.models.response
-
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-import org.joda.time.DateTime
-import java.math.BigDecimal
-
-/**
- * Response for GET /api/v1/staking/pool/{network}/account/{delegatorAddress}/vault/{vaultAddress}/rewards
- */
-@JsonClass(generateAdapter = true)
-data class P2PEthPoolRewardsResponse(
- @Json(name = "delegatorAddress")
- val delegatorAddress: String,
- @Json(name = "vaultAddress")
- val vaultAddress: String,
- @Json(name = "rewards")
- val rewards: List,
-)
-
-@JsonClass(generateAdapter = true)
-data class P2PEthPoolRewardDTO(
- @Json(name = "date")
- val date: DateTime,
- @Json(name = "apy")
- val apy: Double,
- @Json(name = "balance")
- val balance: BigDecimal,
- @Json(name = "rewards")
- val rewards: BigDecimal,
-)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolWithdrawResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolTransactionResponse.kt
similarity index 57%
rename from core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolWithdrawResponse.kt
rename to core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolTransactionResponse.kt
index 1d5ff5bb4a..9c44476b5c 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolWithdrawResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolTransactionResponse.kt
@@ -3,14 +3,20 @@ package com.tangem.datasource.api.ethpool.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.joda.time.DateTime
+import java.math.BigDecimal
/**
- * Response for POST /api/v1/staking/pool/{network}/staking/withdraw
+ * Unified response for staking transactions (deposit, unstake, withdraw)
+ *
+ * Response for:
+ * - POST /api/v1/staking/pool/{network}/staking/deposit
+ * - POST /api/v1/staking/pool/{network}/staking/unstake
+ * - POST /api/v1/staking/pool/{network}/staking/withdraw
*/
@JsonClass(generateAdapter = true)
-data class P2PEthPoolWithdrawResponse(
+data class P2PEthPoolTransactionResponse(
@Json(name = "amount")
- val amount: Double,
+ val amount: BigDecimal,
@Json(name = "vaultAddress")
val vaultAddress: String,
@Json(name = "delegatorAddress")
@@ -20,5 +26,5 @@ data class P2PEthPoolWithdrawResponse(
@Json(name = "createdAt")
val createdAt: DateTime,
@Json(name = "tickets")
- val tickets: List,
+ val tickets: List? = null,
)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolUnstakeResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolUnstakeResponse.kt
deleted file mode 100644
index a3e2391339..0000000000
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolUnstakeResponse.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.tangem.datasource.api.ethpool.models.response
-
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-
-/**
- * Response for POST /api/v1/staking/pool/{network}/staking/unstake
- *
- * Note: Contains Bitcoin-related fields (likely documentation error).
- * Using as-is per specification.
- */
-@JsonClass(generateAdapter = true)
-data class P2PEthPoolUnstakeResponse(
- @Json(name = "stakerPublicKey")
- val stakerPublicKey: String,
- @Json(name = "stakeTransactionHash")
- val stakeTransactionHash: String,
- @Json(name = "unstakeTransactionHex")
- val unstakeTransactionHex: String, // unsigned
- @Json(name = "unstakeFee")
- val unstakeFee: Double,
-)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolVaultsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolVaultsResponse.kt
index 5c825c6346..8a8eaf6167 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolVaultsResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/ethpool/models/response/P2PEthPoolVaultsResponse.kt
@@ -2,6 +2,7 @@ package com.tangem.datasource.api.ethpool.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
+import java.math.BigDecimal
/**
* Response for GET /api/v1/staking/pool/{network}/vaults
@@ -15,7 +16,7 @@ data class P2PEthPoolVaultsResponse(
)
/**
- * Network identifier in P2P API
+ * Network identifier in P2PEthPool API
*/
@JsonClass(generateAdapter = false)
enum class P2PEthPoolNetworkDTO {
@@ -33,15 +34,15 @@ data class P2PEthPoolVaultDTO(
@Json(name = "displayName")
val displayName: String,
@Json(name = "apy")
- val apy: Double,
+ val apy: BigDecimal,
@Json(name = "baseApy")
- val baseApy: Double,
+ val baseApy: BigDecimal,
@Json(name = "capacity")
- val capacity: Double,
+ val capacity: BigDecimal,
@Json(name = "totalAssets")
- val totalAssets: Double,
+ val totalAssets: BigDecimal,
@Json(name = "feePercent")
- val feePercent: Double,
+ val feePercent: BigDecimal,
@Json(name = "isPrivate")
val isPrivate: Boolean,
@Json(name = "isGenesis")
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt
index 2ef036c212..4712dadadc 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt
@@ -32,6 +32,9 @@ data class ExchangeProvider(
@Json(name = "slippage")
val slippage: BigDecimal?,
+
+ @Json(name = "exchangeOnlyWithinSingleAddress")
+ val isExchangeOnlyWithinSingleAddress: Boolean = false,
)
@JsonClass(generateAdapter = false)
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/gasless/GaslessTxServiceApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/GaslessTxServiceApi.kt
new file mode 100644
index 0000000000..514dc9f20e
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/GaslessTxServiceApi.kt
@@ -0,0 +1,21 @@
+package com.tangem.datasource.api.gasless
+
+import com.tangem.datasource.api.common.response.ApiResponse
+import com.tangem.datasource.api.gasless.models.*
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+
+interface GaslessTxServiceApi {
+
+ @GET("api/v1/tokens")
+ suspend fun getSupportedTokens(): ApiResponse>
+
+ @POST("api/v1/transaction/sign")
+ suspend fun signGaslessTransaction(
+ @Body transaction: GaslessTransactionRequest,
+ ): ApiResponse>
+
+ @GET("api/v1/config/fee-recipient")
+ suspend fun getFeeRecipient(): ApiResponse>
+}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessFeeRecipient.kt b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessFeeRecipient.kt
new file mode 100644
index 0000000000..f66c161faf
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessFeeRecipient.kt
@@ -0,0 +1,9 @@
+package com.tangem.datasource.api.gasless.models
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class GaslessFeeRecipient(
+ @Json(name = "feeRecipientAddress") val address: String,
+)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessServiceResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessServiceResponse.kt
new file mode 100644
index 0000000000..a439b9e05a
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessServiceResponse.kt
@@ -0,0 +1,11 @@
+package com.tangem.datasource.api.gasless.models
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class GaslessServiceResponse(
+ @Json(name = "result") val result: T,
+ @Json(name = "success") val isSuccess: Boolean,
+ @Json(name = "timestamp") val timestamp: String,
+)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessSignedTransactionResultDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessSignedTransactionResultDTO.kt
new file mode 100644
index 0000000000..8184f49c1c
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessSignedTransactionResultDTO.kt
@@ -0,0 +1,14 @@
+package com.tangem.datasource.api.gasless.models
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * Result of gasless transaction signing.
+ * Contains the signed transaction data and gas parameters.
+ */
+@JsonClass(generateAdapter = true)
+data class GaslessSignedTransactionResultDTO(
+ @Json(name = "txHash")
+ val txHash: String,
+)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessSupportedTokens.kt b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessSupportedTokens.kt
new file mode 100644
index 0000000000..21ef002c64
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessSupportedTokens.kt
@@ -0,0 +1,9 @@
+package com.tangem.datasource.api.gasless.models
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class GaslessSupportedTokens(
+ @Json(name = "tokens") val tokens: List,
+)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessTokenDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessTokenDTO.kt
new file mode 100644
index 0000000000..e107fdcc83
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessTokenDTO.kt
@@ -0,0 +1,14 @@
+package com.tangem.datasource.api.gasless.models
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class GaslessTokenDTO(
+ @Json(name = "tokenAddress") val tokenAddress: String,
+ @Json(name = "tokenSymbol") val tokenSymbol: String,
+ @Json(name = "tokenName") val tokenName: String,
+ @Json(name = "decimals") val decimals: Int,
+ @Json(name = "chainId") val chainId: Int,
+ @Json(name = "chain") val chain: String,
+)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessTransactionRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessTransactionRequest.kt
new file mode 100644
index 0000000000..034bb21097
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/gasless/models/GaslessTransactionRequest.kt
@@ -0,0 +1,96 @@
+package com.tangem.datasource.api.gasless.models
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * Request body for gasless transaction submission.
+ * Represents complete transaction with fee delegation metadata.
+ */
+@JsonClass(generateAdapter = true)
+data class GaslessTransactionRequest(
+ @Json(name = "gaslessTransaction")
+ val gaslessTransaction: GaslessTransactionData,
+
+ @Json(name = "signature")
+ val signature: String,
+
+ @Json(name = "userAddress")
+ val userAddress: String,
+
+ @Json(name = "chainId")
+ val chainId: Int,
+
+ @Json(name = "eip7702auth")
+ val eip7702Auth: Eip7702AuthorizationDTO? = null,
+)
+
+@JsonClass(generateAdapter = true)
+data class GaslessTransactionData(
+ @Json(name = "transaction")
+ val transaction: TransactionData,
+
+ @Json(name = "fee")
+ val fee: FeeData,
+
+ @Json(name = "nonce")
+ val nonce: String,
+)
+
+@JsonClass(generateAdapter = true)
+data class TransactionData(
+ @Json(name = "to")
+ val to: String,
+
+ @Json(name = "value")
+ val value: String,
+
+ @Json(name = "data")
+ val data: String,
+)
+
+@JsonClass(generateAdapter = true)
+data class FeeData(
+ @Json(name = "feeToken")
+ val feeToken: String,
+
+ @Json(name = "maxTokenFee")
+ val maxTokenFee: String,
+
+ @Json(name = "coinPriceInToken")
+ val coinPriceInToken: String,
+
+ @Json(name = "feeTransferGasLimit")
+ val feeTransferGasLimit: String,
+
+ @Json(name = "baseGas")
+ val baseGas: String,
+
+ @Json(name = "feeReceiver")
+ val feeReceiver: String,
+)
+
+/**
+ * EIP-7702 authorization for account abstraction.
+ * Optional field, used only when EOA delegation is required.
+ */
+@JsonClass(generateAdapter = true)
+data class Eip7702AuthorizationDTO(
+ @Json(name = "chainId")
+ val chainId: Int,
+
+ @Json(name = "address")
+ val address: String,
+
+ @Json(name = "nonce")
+ val nonce: String,
+
+ @Json(name = "yParity")
+ val yParity: Int,
+
+ @Json(name = "r")
+ val r: String,
+
+ @Json(name = "s")
+ val s: String,
+)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsArticleDto.kt b/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsArticleDto.kt
index 292b3d4559..ef9e77a8f0 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsArticleDto.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsArticleDto.kt
@@ -27,15 +27,4 @@ data class NewsRelatedTokenDto(
@Json(name = "id") val id: String,
@Json(name = "symbol") val symbol: String,
@Json(name = "name") val name: String,
-)
-
-@JsonClass(generateAdapter = true)
-data class NewsOriginalArticleDto(
- @Json(name = "id") val id: Int,
- @Json(name = "title") val title: String,
- @Json(name = "sourceName") val sourceName: String,
- @Json(name = "language") val language: String,
- @Json(name = "publishedAt") val publishedAt: String,
- @Json(name = "url") val url: String,
- @Json(name = "imageUrl") val imageUrl: String? = null,
)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsDetailsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsDetailsResponse.kt
index 230c3f42fb..dd3ccb7001 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsDetailsResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/news/models/response/NewsDetailsResponse.kt
@@ -16,5 +16,22 @@ data class NewsDetailsResponse(
@Json(name = "newsUrl") val newsUrl: String,
@Json(name = "shortContent") val shortContent: String,
@Json(name = "content") val content: String,
- @Json(name = "originalArticles") val originalArticles: List,
+ @Json(name = "relatedArticles") val relatedArticles: List,
+)
+
+@JsonClass(generateAdapter = true)
+data class NewsRelatedArticleDto(
+ @Json(name = "id") val id: Int,
+ @Json(name = "title") val title: String,
+ @Json(name = "media") val media: Media,
+ @Json(name = "language") val language: String,
+ @Json(name = "publishedAt") val publishedAt: String,
+ @Json(name = "url") val url: String,
+ @Json(name = "imageUrl") val imageUrl: String? = null,
+)
+
+@JsonClass(generateAdapter = true)
+data class Media(
+ @Json(name = "id") val id: Int,
+ @Json(name = "name") val name: String,
)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt
index 773dbb7b72..60484ac83b 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt
@@ -25,7 +25,6 @@ interface TangemPayApi {
@GET("v1/customer/wallets/{customer_wallet_id}")
suspend fun checkCustomerWalletId(
- @Header("X-API-KEY") authHeader: String,
@Path("customer_wallet_id") customerWalletId: String,
): ApiResponse
diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt
index 7bf7ad5ce5..20d4c632d5 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/di/ApiConfigsModule.kt
@@ -60,7 +60,15 @@ internal object ApiConfigsModule {
@Provides
@IntoSet
- fun provideNewsConfig(authProvider: AuthProvider): ApiConfig = News(authProvider = authProvider)
+ fun provideNewsConfig(
+ appVersionProvider: AppVersionProvider,
+ authProvider: AuthProvider,
+ appInfoProvider: AppInfoProvider,
+ ): ApiConfig = News(
+ appVersionProvider = appVersionProvider,
+ appInfoProvider = appInfoProvider,
+ authProvider = authProvider,
+ )
@Provides
@IntoSet
@@ -78,13 +86,17 @@ internal object ApiConfigsModule {
@Provides
@IntoSet
- fun provideTangemVisaConfig(appVersionProvider: AppVersionProvider): ApiConfig = TangemPay(appVersionProvider)
+ fun provideTangemPayBffConfig(
+ appVersionProvider: AppVersionProvider,
+ environmentConfigStorage: EnvironmentConfigStorage,
+ ): ApiConfig = TangemPay.Bff(appVersionProvider, environmentConfigStorage)
@Provides
@IntoSet
- fun provideTangemPayAuthConfig(appVersionProvider: AppVersionProvider): ApiConfig = TangemPayAuth(
- appVersionProvider,
- )
+ fun provideTangemPayAuthConfig(
+ appVersionProvider: AppVersionProvider,
+ environmentConfigStorage: EnvironmentConfigStorage,
+ ): ApiConfig = TangemPay.Auth(appVersionProvider, environmentConfigStorage)
@Provides
@IntoSet
@@ -97,4 +109,18 @@ internal object ApiConfigsModule {
fun provideMoonPayConfig(): ApiConfig {
return MoonPay()
}
+
+ @Provides
+ @IntoSet
+ fun provideGaslessServiceConfig(
+ appVersionProvider: AppVersionProvider,
+ authProvider: AuthProvider,
+ appInfoProvider: AppInfoProvider,
+ ): ApiConfig {
+ return GaslessTxService(
+ authProvider = authProvider,
+ appVersionProvider = appVersionProvider,
+ appInfoProvider = appInfoProvider,
+ )
+ }
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt
index 511a2dab3d..25833cc27c 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt
@@ -15,6 +15,7 @@ import com.tangem.datasource.api.moonpay.MoonPayApi
import com.tangem.datasource.api.news.NewsApi
import com.tangem.datasource.api.onramp.OnrampApi
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
+import com.tangem.datasource.api.gasless.GaslessTxServiceApi
import com.tangem.datasource.api.pay.TangemPayApi
import com.tangem.datasource.api.pay.TangemPayAuthApi
import com.tangem.datasource.api.stakekit.StakeKitApi
@@ -36,8 +37,11 @@ import javax.inject.Singleton
internal object NetworkModule {
private const val TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS = 60L
+ private const val TANGEM_GASLESS_SERVICE_TIMEOUT_SECONDS = 60L
private const val STAKE_KIT_API_TIMEOUT_SECONDS = 60L
+ private const val P2P_ETH_POOL_API_TIMEOUT_SECONDS = 60L
+
@Provides
@Singleton
fun provideApiConfigManager(
@@ -82,6 +86,12 @@ internal object NetworkModule {
return retrofitApiBuilder.build(
apiConfigId = ApiConfig.ID.P2PEthPool,
applyTimeoutAnnotations = false,
+ timeouts = Timeouts(
+ callTimeoutSeconds = P2P_ETH_POOL_API_TIMEOUT_SECONDS,
+ connectTimeoutSeconds = P2P_ETH_POOL_API_TIMEOUT_SECONDS,
+ readTimeoutSeconds = P2P_ETH_POOL_API_TIMEOUT_SECONDS,
+ writeTimeoutSeconds = P2P_ETH_POOL_API_TIMEOUT_SECONDS,
+ ),
)
}
@@ -180,4 +190,19 @@ internal object NetworkModule {
applyTimeoutAnnotations = false,
)
}
+
+ @Provides
+ @Singleton
+ fun provideGaslessTxServiceApi(retrofitApiBuilder: RetrofitApiBuilder): GaslessTxServiceApi {
+ return retrofitApiBuilder.build(
+ apiConfigId = ApiConfig.ID.GaslessTxService,
+ applyTimeoutAnnotations = false,
+ timeouts = Timeouts(
+ callTimeoutSeconds = TANGEM_GASLESS_SERVICE_TIMEOUT_SECONDS,
+ connectTimeoutSeconds = TANGEM_GASLESS_SERVICE_TIMEOUT_SECONDS,
+ readTimeoutSeconds = TANGEM_GASLESS_SERVICE_TIMEOUT_SECONDS,
+ writeTimeoutSeconds = TANGEM_GASLESS_SERVICE_TIMEOUT_SECONDS,
+ ),
+ )
+ }
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NewsStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NewsStoreModule.kt
index fa1dbd3792..22b046ee0c 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/di/NewsStoreModule.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/di/NewsStoreModule.kt
@@ -3,8 +3,12 @@ package com.tangem.datasource.di
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.datasource.local.news.details.DefaultNewsDetailsStore
import com.tangem.datasource.local.news.details.NewsDetailsStore
+import com.tangem.datasource.local.news.liked.DefaultNewsLikedStore
+import com.tangem.datasource.local.news.liked.NewsLikedStore
import com.tangem.datasource.local.news.trending.DefaultTrendingNewsStore
import com.tangem.datasource.local.news.trending.TrendingNewsStore
+import com.tangem.datasource.local.news.viewed.DefaultNewsViewedStore
+import com.tangem.datasource.local.news.viewed.NewsViewedStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -26,4 +30,16 @@ internal object NewsStoreModule {
fun provideTrendingNewsStore(): TrendingNewsStore {
return DefaultTrendingNewsStore(store = RuntimeSharedStore())
}
+
+ @Provides
+ @Singleton
+ fun provideNewsViewedStore(): NewsViewedStore {
+ return DefaultNewsViewedStore(store = RuntimeSharedStore())
+ }
+
+ @Provides
+ @Singleton
+ fun provideNewsLikedStore(): NewsLikedStore {
+ return DefaultNewsLikedStore(store = RuntimeSharedStore())
+ }
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt
index aeb4d1b8d9..d4bd6b3e65 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt
@@ -80,7 +80,7 @@ internal object StakingStoreModule {
@Provides
@Singleton
- fun provideP2PBalancesPersistenceStore(
+ fun provideP2PEthPoolBalancesPersistenceStore(
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
dispatchers: CoroutineDispatcherProvider,
@@ -91,7 +91,7 @@ internal object StakingStoreModule {
types = mapWithStringKeyTypes(valueTypes = setTypes()),
defaultValue = emptyMap(),
),
- produceFile = { context.dataStoreFile(fileName = "p2p_balances") },
+ produceFile = { context.dataStoreFile(fileName = "p2p_eth_pool_balances") },
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
)
}
diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/accounts/AccountTokenMigrationStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/accounts/AccountTokenMigrationStore.kt
index c527ef5dec..914144ea5a 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/local/accounts/AccountTokenMigrationStore.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/local/accounts/AccountTokenMigrationStore.kt
@@ -1,13 +1,14 @@
package com.tangem.datasource.local.accounts
+import com.tangem.domain.models.account.AccountName
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.Flow
interface AccountTokenMigrationStore {
- fun get(userWalletId: UserWalletId): Flow?>
+ fun get(userWalletId: UserWalletId): Flow?>
- suspend fun store(userWalletId: UserWalletId, value: Pair)
+ suspend fun store(userWalletId: UserWalletId, value: Pair)
suspend fun remove(userWalletId: UserWalletId)
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/accounts/DefaultAccountTokenMigrationStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/accounts/DefaultAccountTokenMigrationStore.kt
index 920b4ed166..6eba77722f 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/local/accounts/DefaultAccountTokenMigrationStore.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/local/accounts/DefaultAccountTokenMigrationStore.kt
@@ -1,19 +1,20 @@
package com.tangem.datasource.local.accounts
import com.tangem.datasource.local.datastore.RuntimeStateStore
+import com.tangem.domain.models.account.AccountName
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
internal class DefaultAccountTokenMigrationStore(
- private val runtimeStateStore: RuntimeStateStore