diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000..25a2f7c928
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+[*.{kt,kts}]
+ktlint_ignore_back_ticked_identifier = true
+
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+
+ij_kotlin_allow_trailing_comma = true
+ij_kotlin_allow_trailing_comma_on_call_site = true
+
+ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
+ij_kotlin_packages_to_use_import_on_demand = java.util.*, kotlinx.android.synthetic.**
+
+max_line_length = 120
diff --git a/.gitignore b/.gitignore
index 4cc00a7e1f..dd64e70b22 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,8 @@ local.properties
# User-specific configurations
.idea
+!.idea/codeStyles
+!.idea/inspectionProfiles
# LayoutInspector files
/captures
@@ -26,4 +28,4 @@ local.properties
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
-**/fastlane/test_output
\ No newline at end of file
+**/fastlane/test_output
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000000..3ab743df69
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,204 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000000..79ee123c2b
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000000..ba5af7a4ce
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index d51ad6b9a6..c37a4da509 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -6,13 +6,13 @@ apply plugin: 'com.google.firebase.crashlytics'
apply from: '../dependencies.gradle'
android {
- compileSdkVersion 31
+ compileSdkVersion 32
defaultConfig {
applicationId "com.tangem.wallet"
versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 1
versionName project.hasProperty('versionName') ? project.property('versionName') : "1.0.0.-SNAPSHOT"
minSdkVersion 21
- targetSdkVersion 31
+ targetSdkVersion 32
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false'
@@ -83,20 +83,18 @@ dependencies {
implementation implementation(project(path: ':network'))
implementation implementation(project(path: ':common'))
- implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.fragment:fragment-ktx:1.4.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.google.android.material:material:1.5.0'
- implementation "androidx.core:core-ktx:1.7.0"
implementation 'com.google.android.play:core:1.10.3'
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
- implementation 'com.tangem:blockchain:AND-1939_tron_fix_release-97'
-// implementation 'com.tangem:blockchain:0.0.1'
- implementation 'com.tangem.tangem-sdk-kotlin:core:develop-154'
- implementation 'com.tangem.tangem-sdk-kotlin:android:develop-154'
+ implementation "com.tangem:blockchain:${versions.tangem_blockchain_sdk}"
+ implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tangem_card_sdk}"
+ implementation "com.tangem.tangem-sdk-kotlin:android:${versions.tangem_card_sdk}"
// WebView
implementation "androidx.browser:browser:1.3.0"
@@ -145,8 +143,11 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")
-//
-// //Crypto
+ // Support
+ implementation("com.zendesk:chat:3.3.5")
+ implementation("com.zendesk:messaging:5.2.4")
+
+ //Crypto
implementation "com.madgag.spongycastle:core:1.58.0.0"
// animation
@@ -162,14 +163,14 @@ dependencies {
implementation 'com.google.android.gms:play-services-wallet:19.1.0'
//Compose
- implementation 'androidx.activity:activity-compose:1.4.0'
+ implementation 'androidx.activity:activity-compose:1.5.0'
implementation 'androidx.compose.material:material:1.1.1'
implementation 'androidx.compose.animation:animation:1.1.1'
- implementation 'androidx.compose.foundation:foundation:1.1.1'
- implementation 'androidx.compose.foundation:foundation-layout:1.1.1'
+ implementation 'androidx.compose.foundation:foundation:1.2.0-rc03'
implementation 'androidx.compose.ui:ui:1.1.1'
implementation 'androidx.compose.ui:ui-tooling:1.1.1'
implementation "com.google.accompanist:accompanist-appcompat-theme:0.23.0"
+ implementation "com.google.accompanist:accompanist-systemuicontroller:0.23.0"
implementation "com.github.skydoves:androidveil:1.1.2"
diff --git a/app/libs/walletconnect-1.5.5.aar b/app/libs/walletconnect-1.5.5.aar
new file mode 100644
index 0000000000..4e2f1b4fb7
Binary files /dev/null and b/app/libs/walletconnect-1.5.5.aar differ
diff --git a/app/libs/walletconnect.aar b/app/libs/walletconnect.aar
deleted file mode 100644
index 129de60a79..0000000000
Binary files a/app/libs/walletconnect.aar and /dev/null differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4dfca87b23..b460cf581a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,14 +28,15 @@
@@ -46,7 +47,7 @@
@@ -127,6 +128,21 @@
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config
index bf673f7f88..f33f020823 160000
--- a/app/src/main/assets/tangem-app-config
+++ b/app/src/main/assets/tangem-app-config
@@ -1 +1 @@
-Subproject commit bf673f7f88ed561cd8827447c4886f80f30b7e23
+Subproject commit f33f020823031f1c532cf5a9d5cf3fbecadcf050
diff --git a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt
new file mode 100644
index 0000000000..806769df5f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt
@@ -0,0 +1,46 @@
+package com.tangem.tap
+
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
+import android.os.Bundle
+import java.util.WeakHashMap
+import kotlin.reflect.KClass
+
+class ForegroundActivityObserver {
+ private val activities = WeakHashMap, Activity>()
+
+ val foregroundActivity: Activity?
+ get() = activities.entries
+ .filterNot { it.value.isDestroyed }
+ .firstOrNull()
+ ?.value
+
+ val callbacks get() = Callbacks()
+
+ inner class Callbacks : ActivityLifecycleCallbacks {
+ override fun onActivityResumed(activity: Activity) {
+ activities[activity::class] = activity
+ }
+
+ override fun onActivityDestroyed(activity: Activity) {
+ activities.remove(activity::class)
+ }
+
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
+ }
+ override fun onActivityStarted(activity: Activity) {
+ }
+ override fun onActivityPaused(activity: Activity) {
+ }
+ override fun onActivityStopped(activity: Activity) {
+ }
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
+ }
+ }
+}
+
+fun ForegroundActivityObserver.withForegroundActivity(
+ block: (Activity) -> Unit
+) {
+ foregroundActivity?.let { block(it) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt
index a663e66e7a..4e1c24d3c9 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -5,6 +5,8 @@ import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsControllerCompat
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.snackbar.Snackbar
import com.tangem.TangemSdk
@@ -57,7 +59,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
systemActions()
- store.state.globalState.feedbackManager?.updateActivity(this)
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
tangemSdk = TangemSdk.init(this, TangemSdkManager.config)
@@ -84,8 +85,17 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
}
private fun systemActions() {
- // makes the status bar text dark
- window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
+ val windowInsetsController = WindowInsetsControllerCompat(window, binding.root)
+ windowInsetsController.isAppearanceLightStatusBars = true
+ windowInsetsController.isAppearanceLightNavigationBars = true
+
+ supportFragmentManager.registerFragmentLifecycleCallbacks(
+ NavBarInsetsFragmentLifecycleCallback(),
+ true
+ )
+
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
diff --git a/app/src/main/java/com/tangem/tap/NavBarInsetsFragmentLifecycleCallback.kt b/app/src/main/java/com/tangem/tap/NavBarInsetsFragmentLifecycleCallback.kt
new file mode 100644
index 0000000000..2239eacd70
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/NavBarInsetsFragmentLifecycleCallback.kt
@@ -0,0 +1,39 @@
+package com.tangem.tap
+
+import android.os.Bundle
+import android.view.View
+import androidx.compose.ui.platform.ComposeView
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updatePadding
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks
+
+class NavBarInsetsFragmentLifecycleCallback : FragmentLifecycleCallbacks() {
+ override fun onFragmentViewCreated(
+ fm: FragmentManager,
+ f: Fragment,
+ v: View,
+ savedInstanceState: Bundle?,
+ ) {
+ if (v is ComposeView) return
+
+ ViewCompat.setOnApplyWindowInsetsListener(v) { view, windowInsets ->
+ val statusBarInsets = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars())
+ val navigationBarInsets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
+
+ if (view.fitsSystemWindows) {
+ view.updatePadding(
+ top = statusBarInsets.top,
+ bottom = navigationBarInsets.bottom,
+ )
+ } else {
+ view.updatePadding(
+ bottom = navigationBarInsets.bottom,
+ )
+ }
+ windowInsets
+ }
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt
index 2e6c7b534b..0c69e5de88 100644
--- a/app/src/main/java/com/tangem/tap/TapApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TapApplication.kt
@@ -8,11 +8,15 @@ import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import com.tangem.Log
+import com.tangem.LogFormat
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.domain.DomainLayer
import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
+import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
+import com.tangem.tap.common.feedback.FeedbackManager
import com.tangem.tap.common.images.createCoilImageLoader
+import com.tangem.tap.common.log.TangemLogCollector
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.common.redux.global.GlobalAction
@@ -24,9 +28,6 @@ import com.tangem.tap.domain.configurable.warningMessage.RemoteWarningLoader
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
-import com.tangem.tap.features.feedback.AdditionalEmailInfo
-import com.tangem.tap.features.feedback.FeedbackManager
-import com.tangem.tap.features.feedback.TangemLogCollector
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.persistence.PreferencesStorage
import com.tangem.wallet.BuildConfig
@@ -40,6 +41,8 @@ val store = Store(
)
val logConfig = LogConfig()
+lateinit var foregroundActivityObserver: ForegroundActivityObserver
+
lateinit var preferencesStorage: PreferencesStorage
lateinit var currenciesRepository: CurrenciesRepository
lateinit var walletConnectRepository: WalletConnectRepository
@@ -68,6 +71,8 @@ class TapApplication : Application(), ImageLoaderFactory {
)
walletConnectRepository = WalletConnectRepository(this)
+ foregroundActivityObserver = ForegroundActivityObserver()
+ registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
initFeedbackManager()
loadConfigs()
@@ -85,22 +90,51 @@ class TapApplication : Application(), ImageLoaderFactory {
val localLoader = FeaturesLocalLoader(this, moshi)
val remoteLoader = FeaturesRemoteLoader(moshi)
val configManager = ConfigManager(localLoader, remoteLoader)
- configManager.load {
+ configManager.load { config ->
store.dispatch(GlobalAction.SetConfigManager(configManager))
- shopService = TangemShopService(this, configManager.config.shopify!!)
+ shopService = TangemShopService(
+ application = this,
+ shopifyShop = config.shopify!!
+ )
+ store.state.globalState.feedbackManager?.initChat(
+ context = this,
+ zendeskConfig = config.zendesk!!
+ )
}
val warningsManager = WarningMessagesManager(RemoteWarningLoader(moshi))
warningsManager.load { store.dispatch(GlobalAction.SetWarningManager(warningsManager)) }
}
private fun initFeedbackManager() {
- val infoHolder = AdditionalEmailInfo()
+ val infoHolder = AdditionalFeedbackInfo()
infoHolder.setAppVersion(this)
- val logWriter = TangemLogCollector()
+ val logLevels = listOf(
+ Log.Level.ApduCommand,
+ Log.Level.Apdu,
+ Log.Level.Tlv,
+ Log.Level.Nfc,
+ Log.Level.Command,
+ Log.Level.Session,
+ Log.Level.View,
+ Log.Level.Network,
+ Log.Level.Error,
+ )
+ val logWriter = TangemLogCollector(
+ levels = logLevels,
+ messageFormatter = LogFormat.StairsFormatter(),
+ )
Log.addLogger(logWriter)
- store.dispatch(GlobalAction.SetFeedbackManager(FeedbackManager(infoHolder, logWriter)))
+ store.dispatch(
+ GlobalAction.SetFeedbackManager(
+ FeedbackManager(
+ infoHolder = infoHolder,
+ logCollector = logWriter,
+ preferencesStorage = preferencesStorage,
+ ),
+ ),
+ )
}
private fun initAppsFlyer() {
@@ -115,4 +149,5 @@ class TapApplication : Application(), ImageLoaderFactory {
data class LogConfig(
val coil: Boolean = BuildConfig.DEBUG,
val storeAction: Boolean = BuildConfig.DEBUG,
+ val zendesk: Boolean = BuildConfig.DEBUG,
)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
index 9af6c88b29..96cf1b874b 100644
--- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
@@ -4,12 +4,14 @@ import android.app.Dialog
import android.content.Context
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.global.GlobalState
+import com.tangem.tap.common.ui.SimpleAlertDialog
+import com.tangem.tap.common.ui.SimpleCancelableAlertDialog
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
import com.tangem.tap.features.details.ui.walletconnect.dialogs.ApproveWcSessionDialog
import com.tangem.tap.features.details.ui.walletconnect.dialogs.BnbTransactionDialog
+import com.tangem.tap.features.details.ui.walletconnect.dialogs.ChooseNetworkDialog
import com.tangem.tap.features.details.ui.walletconnect.dialogs.ClipboardOrScanQrDialog
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialog
-import com.tangem.tap.features.details.ui.walletconnect.dialogs.SimpleAlertDialog
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionDialog
import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
@@ -19,7 +21,7 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AddMoreBack
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInProgressDialog
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
-import com.tangem.tap.features.wallet.redux.WalletDialog
+import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendBottomSheetDialog
import com.tangem.tap.features.wallet.ui.dialogs.ChooseTradeActionBottomSheetDialog
import com.tangem.tap.features.wallet.ui.dialogs.RussianCardholdersWarningBottomSheetDialog
@@ -73,22 +75,33 @@ class DialogManager : StoreSubscriber {
messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet,
context = context
)
+ is WalletConnectDialog.AddNetwork ->
+ SimpleAlertDialog.create(
+ titleRes = R.string.wallet_connect,
+ message = context.getString(
+ R.string.wallet_connect_network_not_found_format,
+ state.dialog.network
+ ),
+ context = context
+ )
is WalletConnectDialog.OpeningSessionRejected -> {
SimpleAlertDialog.create(
titleRes = R.string.wallet_connect,
messageRes = R.string.wallet_connect_same_wcuri,
- context = context
+ context = context,
)
}
is WalletConnectDialog.SessionTimeout -> {
SimpleAlertDialog.create(
titleRes = R.string.wallet_connect,
messageRes = R.string.wallet_connect_error_timeout,
- context = context
+ context = context,
)
}
is WalletConnectDialog.ApproveWcSession ->
- ApproveWcSessionDialog.create(state.dialog.session, context)
+ ApproveWcSessionDialog.create(state.dialog.session, state.dialog.networks, context)
+ is WalletConnectDialog.ChooseNetwork ->
+ ChooseNetworkDialog.create(state.dialog.session, state.dialog.networks, context)
is WalletConnectDialog.ClipboardOrScanQr ->
ClipboardOrScanQrDialog.create(state.dialog.clipboardUri, context)
is WalletConnectDialog.RequestTransaction ->
@@ -102,6 +115,12 @@ class DialogManager : StoreSubscriber {
sessionId = state.dialog.sessionId,
cardId = state.dialog.cardId,
dAppName = state.dialog.dAppName,
+ context = context,
+ )
+ is WalletConnectDialog.UnsupportedNetwork ->
+ SimpleAlertDialog.create(
+ titleRes = R.string.wallet_connect,
+ messageRes = R.string.wallet_connect_scanner_error_unsupported_network,
context = context
)
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
@@ -116,6 +135,20 @@ class DialogManager : StoreSubscriber {
AmountToSendBottomSheetDialog(context, state.dialog)
is WalletDialog.SignedHashesMultiWalletDialog ->
SignedHashesWarningDialog.create(context)
+ is WalletDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create(
+ title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol),
+ message = context.getString(
+ state.dialog.messageRes, state.dialog.currencySymbol, state.dialog.currencyTitle
+ ),
+ context = context,
+ )
+ is WalletDialog.RemoveWalletDialog -> SimpleCancelableAlertDialog.create(
+ title = context.getString(state.dialog.titleRes, state.dialog.currencyTitle),
+ messageRes = state.dialog.messageRes,
+ context = context,
+ primaryButtonRes = state.dialog.primaryButtonRes,
+ primaryButtonAction = state.dialog.onOk
+ )
is WalletDialog.RussianCardholdersWarningDialog ->
RussianCardholdersWarningBottomSheetDialog(context)
else -> null
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/AutoSizeText.kt b/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt
similarity index 93%
rename from app/src/main/java/com/tangem/tap/features/home/compose/AutoSizeText.kt
rename to app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt
index 2bb6c5b8d5..324fd3877e 100644
--- a/app/src/main/java/com/tangem/tap/features/home/compose/AutoSizeText.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt
@@ -1,6 +1,5 @@
-package com.tangem.tap.features.home.compose
+package com.tangem.tap.common.compose
-import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -12,7 +11,6 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
-@OptIn(ExperimentalAnimationApi::class)
@Composable
fun TextAutoSize(
modifier: Modifier = Modifier,
diff --git a/app/src/main/java/com/tangem/tap/common/compose/Spacer.kt b/app/src/main/java/com/tangem/tap/common/compose/Spacer.kt
index b31a15c4ae..ce0b190598 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/Spacer.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/Spacer.kt
@@ -38,6 +38,11 @@ fun SpacerH24(modifier: Modifier = Modifier) {
SpacerH(24.dp, modifier)
}
+@Composable
+fun SpacerH32(modifier: Modifier = Modifier) {
+ SpacerH(32.dp, modifier)
+}
+
// ***************************** Vertical
@Composable
fun SpacerW(width: Dp, modifier: Modifier = Modifier) {
@@ -64,6 +69,11 @@ fun SpacerW24(modifier: Modifier = Modifier) {
SpacerW(24.dp, modifier)
}
+@Composable
+fun SpacerW32(modifier: Modifier = Modifier) {
+ SpacerW(32.dp, modifier)
+}
+
// ***************************** Size
@Composable
fun SpacerS(size: Dp, modifier: Modifier = Modifier) {
@@ -88,4 +98,9 @@ fun SpacerS16(modifier: Modifier = Modifier) {
@Composable
fun SpacerS24(modifier: Modifier = Modifier) {
SpacerS(24.dp, modifier)
+}
+
+@Composable
+fun SpacerS32(modifier: Modifier = Modifier) {
+ SpacerS(32.dp, modifier)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/TangemTypography.kt b/app/src/main/java/com/tangem/tap/common/compose/TangemTypography.kt
new file mode 100644
index 0000000000..88b088fa6a
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/compose/TangemTypography.kt
@@ -0,0 +1,71 @@
+package com.tangem.tap.common.compose
+
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.unit.sp
+import com.tangem.wallet.R
+
+object TangemTypography {
+
+ val body1 = TextStyle(
+ fontFamily = FontFamily(Font(R.font.roboto)),
+ fontSize = 16.0.sp,
+ letterSpacing = 0.5.sp,
+ lineHeight = 24.0.sp,
+ )
+ val body2 = TextStyle(
+ fontFamily = FontFamily(Font(R.font.roboto)),
+ fontSize = 14.0.sp,
+ letterSpacing = 0.25.sp,
+ lineHeight = 20.0.sp,
+ )
+ val button = TextStyle(
+ fontFamily = FontFamily(Font(R.font.roboto_medium)),
+ fontSize = 14.0.sp,
+ letterSpacing = 0.10000000149011612.sp,
+ lineHeight = 20.0.sp,
+ )
+ val caption = TextStyle(
+ fontFamily = FontFamily(Font(R.font.roboto)),
+ fontSize = 12.0.sp,
+ letterSpacing = 0.4000000059604645.sp,
+ lineHeight = 16.0.sp,
+ )
+ val headline1 = TextStyle(
+ fontFamily = FontFamily(Font(R.font.roboto)),
+ fontSize = 34.0.sp,
+ letterSpacing = 0.0.sp,
+ lineHeight = 44.0.sp,
+ )
+ val headline2 = TextStyle(
+ fontFamily = FontFamily(Font(R.font.roboto_medium)),
+ fontSize = 24.0.sp,
+ letterSpacing = 0.18000000715255737.sp,
+ lineHeight = 32.0.sp,
+ )
+ val headline3 = TextStyle(
+ fontFamily = FontFamily(Font(R.font.roboto_medium)),
+ fontSize = 20.0.sp,
+ letterSpacing = 0.15000000596046448.sp,
+ lineHeight = 24.0.sp,
+ )
+ val overline = TextStyle(
+ fontFamily = FontFamily(Font(R.font.roboto_medium)),
+ fontSize = 10.0.sp,
+ letterSpacing = 1.5.sp,
+ lineHeight = 16.0.sp,
+ )
+ val subtitle1 = TextStyle(
+ fontFamily = FontFamily(Font(R.font.roboto_medium)),
+ fontSize = 16.0.sp,
+ letterSpacing = 0.15000000596046448.sp,
+ lineHeight = 24.0.sp,
+ )
+ val subtitle2 = TextStyle(
+ fontFamily = FontFamily(Font(R.font.roboto_medium)),
+ fontSize = 14.0.sp,
+ letterSpacing = 0.10000000149011612.sp,
+ lineHeight = 24.0.sp,
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/AnimatedValue.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/AnimatedValue.kt
new file mode 100644
index 0000000000..1952c6a058
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/AnimatedValue.kt
@@ -0,0 +1,50 @@
+package com.tangem.tap.common.compose.extensions
+
+import androidx.compose.animation.core.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+
+/**
+[REDACTED_AUTHOR]
+ */
+typealias AnimatedValue = Pair
+
+@Composable
+fun AnimatedValue.toAnimatable(
+ isPaused: Boolean,
+ duration: Int,
+ easing: Easing = LinearEasing,
+): Animatable {
+ return animatable(
+ values = this,
+ isPaused = isPaused,
+ duration = duration,
+ easing = easing,
+ )
+}
+
+@Composable
+fun animatable(
+ values: AnimatedValue,
+ duration: Int,
+ isPaused: Boolean = false,
+ easing: Easing = LinearEasing,
+): Animatable {
+ val animatable = remember { Animatable(values.first) }
+
+ LaunchedEffect(isPaused) {
+ if (isPaused) {
+ animatable.stop()
+ } else {
+ animatable.animateTo(
+ targetValue = values.second,
+ animationSpec = tween(
+ durationMillis = duration,
+ easing = easing
+ )
+ )
+ }
+ }
+ return animatable
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/Dp.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/Dp.kt
new file mode 100644
index 0000000000..42acdcf306
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/Dp.kt
@@ -0,0 +1,19 @@
+package com.tangem.tap.common.compose.extensions
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
+
+/**
+[REDACTED_AUTHOR]
+ */
+@Composable
+fun Dp.toPx(): Float {
+ val currentDp = this
+ return with(LocalDensity.current) { currentDp.toPx() }
+}
+
+fun DpSize.halfWidth(): Dp = this.width / 2
+
+fun DpSize.halfHeight(): Dp = this.height / 2
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt
new file mode 100644
index 0000000000..1692b4c51f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/ImageBitmap.kt
@@ -0,0 +1,19 @@
+package com.tangem.tap.common.compose.extensions
+
+import androidx.annotation.DrawableRes
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalContext
+import androidx.core.graphics.drawable.toBitmap
+
+/**
+[REDACTED_AUTHOR]
+ */
+@Composable
+fun asImageBitmap(@DrawableRes drawableId: Int): ImageBitmap {
+ val drawable = AppCompatResources.getDrawable(LocalContext.current, drawableId)
+ ?: throw NullPointerException()
+ return drawable.toBitmap().asImageBitmap()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/MutableState.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/MutableState.kt
new file mode 100644
index 0000000000..459086b9c3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/MutableState.kt
@@ -0,0 +1,14 @@
+package com.tangem.tap.common.compose.extensions
+
+import androidx.compose.runtime.MutableState
+
+/**
+[REDACTED_AUTHOR]
+ */
+fun MutableState>.addAndNotify(value: T) {
+ this.value = this.value.toMutableList().apply { add(value) }
+}
+
+fun MutableState>.removeAndNotify(value: T) {
+ this.value = this.value.toMutableList().apply { remove(value) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/Painter.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/Painter.kt
new file mode 100644
index 0000000000..fa91934a30
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/Painter.kt
@@ -0,0 +1,24 @@
+package com.tangem.tap.common.compose.extensions
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import com.tangem.tangem_sdk_new.extensions.dpToPx
+import com.tangem.tangem_sdk_new.extensions.pxToDp
+
+/**
+[REDACTED_AUTHOR]
+ */
+@Composable
+fun Painter.dpSize(): DpSize = DpSize(
+ intrinsicSize.width.pxToDp().dp,
+ intrinsicSize.height.pxToDp().dp,
+)
+
+@Composable
+private fun Float.dpToPx(): Float = LocalContext.current.dpToPx(this)
+
+@Composable
+private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/AnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/extensions/AnalyticsHandler.kt
new file mode 100644
index 0000000000..f2897e2b63
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/AnalyticsHandler.kt
@@ -0,0 +1,51 @@
+package com.tangem.tap.common.extensions
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics
+import com.tangem.blockchain.common.BlockchainError
+import com.tangem.blockchain.common.BlockchainSdkError
+import com.tangem.common.card.Card
+import com.tangem.common.core.TangemSdkError
+import com.tangem.tap.common.analytics.Analytics
+import com.tangem.tap.common.analytics.AnalyticsHandler
+import com.tangem.tap.common.analytics.AnalyticsParam
+import com.tangem.tap.features.demo.DemoTransactionSender
+
+/**
+[REDACTED_AUTHOR]
+ */
+fun AnalyticsHandler.logSendTransactionError(
+ error: BlockchainError,
+ action: Analytics.ActionToLog,
+ parameters: Map? = mapOf(),
+ card: Card? = null,
+) {
+ when (val blockchainSdkError = (error as BlockchainSdkError)) {
+ is BlockchainSdkError.WrappedTangemError -> {
+ val tangemSdkError = (blockchainSdkError.tangemError as? TangemSdkError) ?: return
+
+ logCardSdkError(
+ error = tangemSdkError,
+ actionToLog = action,
+ parameters = parameters,
+ card = card,
+ )
+ }
+ else -> {
+ when {
+ blockchainSdkError.customMessage.contains(DemoTransactionSender.ID) -> return
+ else -> {
+ val params = parameters?.toMutableMap() ?: mutableMapOf()
+ params[AnalyticsParam.ACTION] = action.key
+ params[AnalyticsParam.ERROR_CODE] = error.code.toString()
+ params[AnalyticsParam.ERROR_DESCRIPTION] = "${error.javaClass.simpleName}: ${error.customMessage}"
+ params[AnalyticsParam.ERROR_KEY] = "BlockchainSdkError"
+
+ FirebaseCrashlytics.getInstance().apply {
+ params.forEach { setCustomKey(it.key.param, it.value) }
+ recordException(error)
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
index 3cb5aa96c8..e2490e7886 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
@@ -27,6 +27,7 @@ fun Blockchain.getRoundIconRes(): Int {
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_round
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_round
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_round
+ Blockchain.Gnosis -> R.drawable.ic_gnosis_round
else -> R.drawable.ic_tangem_logo
}
}
@@ -53,6 +54,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_no_color
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color
+ Blockchain.Gnosis -> R.drawable.ic_gnosis_no_color
else -> R.drawable.ic_tangem_logo
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
index 89d9327ed6..a9504e668a 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
@@ -7,11 +7,13 @@ import androidx.fragment.app.FragmentManager
import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
-import com.tangem.tap.features.details.ui.DetailsConfirmFragment
-import com.tangem.tap.features.details.ui.DetailsFragment
-import com.tangem.tap.features.details.ui.DetailsSecurityFragment
+import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment
+import com.tangem.tap.features.details.ui.cardsettings.CardSettingsFragment
+import com.tangem.tap.features.details.ui.details.DetailsFragment
+import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment
+import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment
import com.tangem.tap.features.details.ui.walletconnect.QrScanFragment
-import com.tangem.tap.features.details.ui.walletconnect.WalletConnectSessionsFragment
+import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
import com.tangem.tap.features.home.HomeFragment
import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
@@ -61,7 +63,11 @@ fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) {
}
fun FragmentActivity.getPreviousScreen(): AppScreen? {
- val indexOfLastFragment = this.supportFragmentManager.backStackEntryCount - 1
+ val indexOfLastFragment = if (this.supportFragmentManager.backStackEntryCount > 0) {
+ this.supportFragmentManager.backStackEntryCount - 1
+ } else {
+ 0
+ }
val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount)
this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name
else null
@@ -88,13 +94,15 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.Wallet -> WalletFragment()
AppScreen.Send -> SendFragment()
AppScreen.Details -> DetailsFragment()
- AppScreen.DetailsConfirm -> DetailsConfirmFragment()
- AppScreen.DetailsSecurity -> DetailsSecurityFragment()
+ AppScreen.DetailsSecurity -> SecurityModeFragment()
+ AppScreen.CardSettings -> CardSettingsFragment()
+ AppScreen.AppSettings -> AppSettingsFragment()
+ AppScreen.ResetToFactory -> ResetCardFragment()
AppScreen.Disclaimer -> DisclaimerFragment()
AppScreen.AddTokens -> AddTokensFragment()
AppScreen.AddCustomToken -> AddCustomTokenFragment()
AppScreen.WalletDetails -> WalletDetailsFragment()
- AppScreen.WalletConnectSessions -> WalletConnectSessionsFragment()
+ AppScreen.WalletConnectSessions -> WalletConnectFragment()
AppScreen.QrScan -> QrScanFragment()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
index 219e9339da..795bc8e268 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
@@ -116,6 +116,8 @@ fun BigDecimal.formatAmountAsSpannedString(
val integer = amount.substringBefore('.')
val reminder = amount.substringAfter('.')
+ // test formatter log Log.e("TEST ", BigDecimal("1234567890987654321.1234567890987654321").formatWithSpaces())
+
return buildSpannedString {
append(integer)
append('.')
@@ -136,7 +138,7 @@ fun BigDecimal.formatWithSpaces(): String {
var index: Int = integerStr.length
while (0 < index) {
if (index <= 3) {
- packets.add(0, integerStr)
+ packets.add(integerStr)
break
}
index -= 3
@@ -145,7 +147,7 @@ fun BigDecimal.formatWithSpaces(): String {
}
return buildString {
- packets.forEachIndexed { index, packet ->
+ packets.reversed().forEachIndexed { index, packet ->
append(packet)
if (index != packets.lastIndex) append(' ')
}
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/StringBuilder.kt b/app/src/main/java/com/tangem/tap/common/extensions/StringBuilder.kt
index 3f8fa992ff..961e77e8f7 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/StringBuilder.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/StringBuilder.kt
@@ -19,4 +19,6 @@ fun StringBuilder.appendIfNotNull(value: String?, prefix: String? = null, postfi
fun String.appendIfNotNull(value: String?, prefix: String? = null, postfix: String? = null): String {
return StringBuilder(this).apply { appendIfNotNull(value, prefix, postfix) }.toString()
-}
\ No newline at end of file
+}
+
+fun StringBuilder.breakLine(count: Int = 1): StringBuilder = append("\n".repeat(count))
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
index 8c2184ae50..617f69c36d 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
@@ -93,32 +93,39 @@ fun View.invisible(invisible: Boolean = true, invokeBeforeStateChanged: (() -> U
}
fun Context.dpToPixels(dp: Int): Int =
- TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics
- ).toInt()
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics
+ ).toInt()
fun Context.pixelsToDp(pixels: Int): Int {
return (pixels.toFloat() /
- (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT))
+ (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT))
.toInt()
}
+fun Context.dpToPixels(dp: Float): Float =
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this.resources.displayMetrics)
+
+
+fun Context.pixelsToDp(pixels: Float): Float =
+ (pixels / (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT))
+
tailrec fun Context?.getActivity(): Activity? = this as? Activity
- ?: (this as? ContextWrapper)?.baseContext?.getActivity()
+ ?: (this as? ContextWrapper)?.baseContext?.getActivity()
fun MaterialCardView.setMargins(
- marginLeftDp: Int = 16,
- marginTopDp: Int = 8,
- marginRightDp: Int = 16,
- marginBottomDp: Int = 8
+ marginLeftDp: Int = 16,
+ marginTopDp: Int = 8,
+ marginRightDp: Int = 16,
+ marginBottomDp: Int = 8
) {
val params = this.layoutParams
(params as ViewGroup.MarginLayoutParams).setMargins(
- context.dpToPixels(marginLeftDp),
- context.dpToPixels(marginTopDp),
- context.dpToPixels(marginRightDp),
- context.dpToPixels(marginBottomDp)
+ context.dpToPixels(marginLeftDp),
+ context.dpToPixels(marginTopDp),
+ context.dpToPixels(marginRightDp),
+ context.dpToPixels(marginBottomDp)
)
this.layoutParams = params
}
@@ -128,11 +135,11 @@ fun Activity.setSystemBarTextColor(setTextDark: Boolean) {
val flags = this.window.decorView.systemUiVisibility
// Update the SystemUiVisibility dependening on whether we want a Light or Dark theme.
this.window.decorView.systemUiVisibility =
- if (setTextDark) {
- flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
- } else {
- flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
- }
+ if (setTextDark) {
+ flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
+ } else {
+ flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ }
}
}
@@ -150,7 +157,7 @@ fun Context.copyToClipboard(value: Any, label: String = "") {
fun Context.getFromClipboard(default: CharSequence? = null): CharSequence? {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
- ?: return default
+ ?: return default
val clipData = clipboard.primaryClip ?: return default
if (clipData.itemCount == 0) return default
@@ -172,10 +179,10 @@ fun Fragment.shareText(text: String) {
}
fun Context.safeStartActivity(
- intent: Intent,
- options: Bundle? = null,
- fallback: ((ActivityNotFoundException) -> Unit)? = null,
- finally: VoidCallback? = null
+ intent: Intent,
+ options: Bundle? = null,
+ fallback: ((ActivityNotFoundException) -> Unit)? = null,
+ finally: VoidCallback? = null
) {
try {
this.startActivity(intent, options)
@@ -187,7 +194,7 @@ fun Context.safeStartActivity(
}
fun View.getString(resId: Int, vararg formatArgs: Any?): String {
- return context.getString(resId, formatArgs)
+ return context.getString(resId, *formatArgs)
}
fun View.animateVisibility(
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
index 40acdd1b7d..311932f7c1 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
@@ -8,7 +8,7 @@ import com.tangem.tap.common.TestActions
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.getFirstToken
-import com.tangem.tap.features.demo.isDemoWallet
+import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
import com.tangem.tap.network.NetworkConnectivity
@@ -21,7 +21,9 @@ import timber.log.Timber
[REDACTED_AUTHOR]
*/
suspend fun WalletManager.safeUpdate(): Result = try {
- if (isDemoWallet() || TestActions.testAmountInjectionForWalletManagerEnabled) {
+ val scanResponse = store.state.globalState.scanResponse
+
+ if (scanResponse?.isDemoCard() == true || TestActions.testAmountInjectionForWalletManagerEnabled) {
delay(500)
TestActions.testAmountInjectionForWalletManagerEnabled = false
Result.Success(wallet)
@@ -49,11 +51,10 @@ suspend fun WalletManager.safeUpdate(): Result = try {
fun WalletManager?.getToUpUrl(): String? {
val globalState = store.state.globalState
- val exchangeManager = globalState.exchangeManager ?: return null
val wallet = this?.wallet ?: return null
val defaultAddress = wallet.address
- return exchangeManager.getUrl(
+ return globalState.exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Buy,
blockchain = wallet.blockchain,
cryptoCurrencyName = wallet.blockchain.currency,
diff --git a/app/src/main/java/com/tangem/tap/common/feature/Feature.kt b/app/src/main/java/com/tangem/tap/common/feature/Feature.kt
new file mode 100644
index 0000000000..ce1313c772
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/feature/Feature.kt
@@ -0,0 +1,8 @@
+package com.tangem.tap.common.feature
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface Feature {
+ fun featureIsSwitchedOn():Boolean
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt
new file mode 100644
index 0000000000..ddc7a41b23
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt
@@ -0,0 +1,127 @@
+package com.tangem.tap.common.feedback
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.domain.common.ScanResponse
+import com.tangem.tap.common.extensions.stripZeroPlainString
+
+class AdditionalFeedbackInfo {
+ class EmailWalletInfo(
+ var blockchain: Blockchain = Blockchain.Unknown,
+ var address: String = "",
+ var explorerLink: String = "",
+ var host: String = "",
+ var derivationPath: String = "",
+ )
+
+ var appVersion: String = ""
+
+ // card
+ var cardId: String = ""
+ var cardFirmwareVersion: String = ""
+ var cardIssuer: String = ""
+ var cardBlockchain: String = ""
+
+ // wallets
+ internal val walletsInfo = mutableListOf()
+ internal val tokens = mutableMapOf>()
+ internal var onSendErrorWalletInfo: EmailWalletInfo? = null
+ var signedHashesCount: String = ""
+
+ // device
+ var phoneModel: String = Build.MODEL
+ var osVersion: String = Build.VERSION.SDK_INT.toString()
+
+ // send error
+ var destinationAddress: String = ""
+ var amount: String = ""
+ var fee: String = ""
+ var token: String = ""
+
+ fun setAppVersion(context: Context) {
+ try {
+ val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
+ appVersion = pInfo.versionName
+ } catch (e: PackageManager.NameNotFoundException) {
+ e.printStackTrace()
+ }
+ }
+
+ fun setCardInfo(data: ScanResponse) {
+ cardId = data.card.cardId
+ cardBlockchain = data.walletData?.blockchain ?: ""
+ cardFirmwareVersion = data.card.firmwareVersion.stringValue
+ cardIssuer = data.card.issuer.name
+ signedHashesCount = data.card.wallets
+ .joinToString("; ") { "${it.curve.curve} - ${it.totalSignedHashes}" }
+ }
+
+ fun setWalletsInfo(walletManagers: List) {
+ walletsInfo.clear()
+ tokens.clear()
+ walletManagers.forEach { manager ->
+ walletsInfo.add(
+ EmailWalletInfo(
+ blockchain = manager.wallet.blockchain,
+ address = getAddress(manager.wallet),
+ explorerLink = getExploreUri(manager.wallet),
+ host = manager.currentHost,
+ derivationPath = manager.wallet.publicKey.derivationPath?.rawPath ?: ""
+ )
+ )
+ if (manager.cardTokens.isNotEmpty()) {
+ tokens[manager.wallet.blockchain] = manager.cardTokens
+ }
+ }
+ }
+
+ fun updateOnSendError(
+ wallet: Wallet,
+ host: String,
+ amountToSend: Amount,
+ feeAmount: Amount,
+ destinationAddress: String,
+ ) {
+ onSendErrorWalletInfo = EmailWalletInfo(
+ blockchain = wallet.blockchain,
+ address = getAddress(wallet),
+ explorerLink = getExploreUri(wallet),
+ host = host,
+ derivationPath = wallet.publicKey.derivationPath?.rawPath ?: ""
+ )
+
+ this.destinationAddress = destinationAddress
+ amount = amountToSend.value?.stripZeroPlainString() ?: "0"
+ fee = feeAmount.value?.stripZeroPlainString() ?: "0"
+ token = if (amountToSend.type is AmountType.Token) amountToSend.currencySymbol else ""
+ }
+
+ private fun getAddress(wallet: Wallet): String {
+ return if (wallet.addresses.size == 1) {
+ wallet.address
+ } else {
+ val addresses = wallet.addresses.joinToString(", ") {
+ "${it.type.javaClass.simpleName} - ${it.value}"
+ }
+ "Multiple address: $addresses"
+ }
+ }
+
+ private fun getExploreUri(wallet: Wallet): String {
+ return if (wallet.addresses.size == 1) {
+ wallet.getExploreUrl(wallet.address)
+ } else {
+ val links = wallet.addresses.joinToString(", ") {
+ "${it.type.javaClass.simpleName} - ${wallet.getExploreUrl(it.value)}"
+ }
+ "Multiple explorers links: $links"
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt
new file mode 100644
index 0000000000..6578b97356
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt
@@ -0,0 +1,109 @@
+package com.tangem.tap.common.feedback
+
+import android.content.Context
+import com.tangem.domain.common.TapWorkarounds
+import com.tangem.tap.common.extensions.breakLine
+import com.tangem.wallet.R
+
+interface FeedbackData {
+ val subjectResId: Int
+ val mainMessageResId: Int
+
+ fun getDataCollectionMessageResId(): Int = R.string.feedback_data_collection_message
+
+ fun prepare(infoHolder: AdditionalFeedbackInfo) {}
+
+ fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String
+
+ fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String {
+ return StringBuilder().apply {
+ append(context.getString(mainMessageResId))
+ breakLine(3)
+ append(context.getString(getDataCollectionMessageResId()))
+ breakLine()
+ append(createOptionalMessage(infoHolder))
+ }.toString()
+ }
+}
+
+class RateCanBeBetterEmail : FeedbackData {
+ override val subjectResId: Int = R.string.feedback_subject_rate_negative
+ override val mainMessageResId: Int = R.string.feedback_preface_rate_negative
+
+ override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
+ .appendCardInfo()
+ .breakLine()
+ .appendPhoneInfo()
+ .build()
+}
+
+class ScanFailsEmail : FeedbackData {
+
+ override val subjectResId: Int = R.string.feedback_subject_scan_failed
+ override val mainMessageResId: Int = R.string.feedback_preface_scan_failed
+
+ override fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String = StringBuilder().apply {
+ append(context.getString(mainMessageResId))
+ breakLine(4)
+ append(createOptionalMessage(infoHolder))
+ }.toString()
+
+ override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
+ .appendPhoneInfo()
+ .build()
+}
+
+class SendTransactionFailedEmail(
+ val error: String,
+) : FeedbackData {
+
+ override val subjectResId: Int = R.string.feedback_subject_tx_failed
+ override val mainMessageResId: Int = R.string.feedback_preface_tx_failed
+
+ override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
+ .appendCardInfo()
+ .appendDelimiter()
+ .appendTxFailedBlockchainInfo(error)
+ .breakLine()
+ .appendPhoneInfo()
+ .build()
+}
+
+class FeedbackEmail : FeedbackData {
+ override val subjectResId: Int
+ get() = if (isS2CCard) s2cSubject else tangemSubject
+ override val mainMessageResId: Int
+ get() = if (isS2CCard) s2cMainMessage else tangemMainMessage
+
+ private val tangemSubject: Int = R.string.feedback_subject_support_tangem
+ private val tangemMainMessage: Int = R.string.feedback_preface_support
+
+ private val s2cSubject: Int = R.string.feedback_subject_support
+ private val s2cMainMessage: Int = R.string.feedback_preface_support
+
+ private var isS2CCard = false
+
+ override fun prepare(infoHolder: AdditionalFeedbackInfo) {
+ isS2CCard = TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)
+ }
+
+ override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
+ .appendCardInfo()
+ .appendWalletsInfo()
+ .breakLine()
+ .appendPhoneInfo()
+ .build()
+}
+
+class SupportInfo : FeedbackData {
+ override val subjectResId: Int = R.string.details_ask_a_question
+ override val mainMessageResId: Int = R.string.details_ask_a_question
+
+ override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String {
+ return FeedbackDataBuilder(infoHolder)
+ .appendCardInfo()
+ .appendDelimiter()
+ .appendPhoneInfo()
+ .build()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt
new file mode 100644
index 0000000000..a3a9ee5575
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt
@@ -0,0 +1,79 @@
+package com.tangem.tap.common.feedback
+
+import com.tangem.tap.common.extensions.breakLine
+
+class FeedbackDataBuilder(
+ private val infoHolder: AdditionalFeedbackInfo
+) {
+ val builder = StringBuilder()
+
+ fun appendDelimiter(): FeedbackDataBuilder {
+ builder.appendDelimiter()
+ return this
+ }
+
+ fun breakLine(count: Int = 1): FeedbackDataBuilder {
+ builder.breakLine(count)
+ return this
+ }
+
+ fun appendCardInfo(): FeedbackDataBuilder {
+ builder.appendKeyValue("Card ID", infoHolder.cardId)
+ builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
+ builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain)
+ builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
+ return this
+ }
+
+ fun appendWalletsInfo(): FeedbackDataBuilder {
+ infoHolder.walletsInfo.forEach {
+ builder.appendDelimiter()
+ builder.appendKeyValue("Blockchain", it.blockchain.fullName)
+ builder.appendKeyValue("Host", it.host)
+ builder.appendKeyValue("Wallet address", it.address)
+ builder.appendKeyValue("Derivation path", it.derivationPath)
+ builder.appendKeyValue("Explorer link", it.explorerLink)
+
+ infoHolder.tokens[it.blockchain]?.let { tokens ->
+ builder.append("Tokens:")
+ breakLine()
+ tokens.forEach { token ->
+ builder.appendKeyValue("Name", token.name)
+ builder.appendKeyValue("ID", token.id ?: "[custom token]")
+ builder.appendKeyValue("Contract address", token.contractAddress)
+ }
+ }
+ }
+ return this
+ }
+
+ fun appendTxFailedBlockchainInfo(error: String): FeedbackDataBuilder {
+ val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalFeedbackInfo.EmailWalletInfo()
+ builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
+ builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
+ builder.appendKeyValue("Host", walletInfo.host)
+ builder.appendKeyValue("Token", infoHolder.token)
+ builder.appendKeyValue("Error", error)
+ builder.appendDelimiter()
+ builder.appendKeyValue("Source address", walletInfo.address)
+ builder.appendKeyValue("Destination address", infoHolder.destinationAddress)
+ builder.appendKeyValue("Amount", infoHolder.amount)
+ builder.appendKeyValue("Fee", infoHolder.fee)
+ return this
+ }
+
+ fun appendPhoneInfo(): FeedbackDataBuilder {
+ builder.appendKeyValue("Phone model", infoHolder.phoneModel)
+ builder.appendKeyValue("OS version", infoHolder.osVersion)
+ builder.appendKeyValue("App version", infoHolder.appVersion)
+ return this
+ }
+
+ fun build(): String = builder.toString()
+}
+
+private fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder {
+ return if (value.isNotBlank()) this.append("$key: $value\n") else this
+}
+
+private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n")
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt
new file mode 100644
index 0000000000..ddd4e6ff6d
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt
@@ -0,0 +1,143 @@
+package com.tangem.tap.common.feedback
+
+import android.content.Context
+import android.os.Build
+import com.tangem.domain.common.TapWorkarounds
+import com.tangem.tap.common.extensions.sendEmail
+import com.tangem.tap.common.log.TangemLogCollector
+import com.tangem.tap.common.zendesk.ZendeskConfig
+import com.tangem.tap.foregroundActivityObserver
+import com.tangem.tap.logConfig
+import com.tangem.tap.persistence.PreferencesStorage
+import com.tangem.tap.withForegroundActivity
+import com.tangem.wallet.R
+import com.zendesk.logger.Logger
+import timber.log.Timber
+import zendesk.chat.Chat
+import zendesk.chat.ChatConfiguration
+import zendesk.chat.ChatEngine
+import zendesk.chat.ChatProvidersConfiguration
+import zendesk.chat.VisitorInfo
+import zendesk.configurations.Configuration
+import zendesk.messaging.MessagingActivity
+import java.io.File
+import java.io.FileWriter
+import java.io.StringWriter
+
+/**
+[REDACTED_AUTHOR]
+ */
+class FeedbackManager(
+ val infoHolder: AdditionalFeedbackInfo,
+ private val logCollector: TangemLogCollector,
+ private val preferencesStorage: PreferencesStorage,
+) {
+ fun initChat(
+ context: Context,
+ zendeskConfig: ZendeskConfig,
+ ) {
+ Chat.INSTANCE.init(
+ context,
+ zendeskConfig.accountKey,
+ zendeskConfig.appId
+ )
+
+ // Zendesk logs
+ Logger.setLoggable(logConfig.zendesk)
+ }
+
+ fun sendEmail(feedbackData: FeedbackData, onFail: ((Exception) -> Unit)? = null) {
+ feedbackData.prepare(infoHolder)
+ foregroundActivityObserver.withForegroundActivity { activity ->
+ val fileLog = if (feedbackData is ScanFailsEmail) createLogFile(activity) else null
+ activity.sendEmail(
+ email = getSupportEmail(),
+ subject = activity.getString(feedbackData.subjectResId),
+ message = feedbackData.joinTogether(activity, infoHolder),
+ file = fileLog,
+ onFail = onFail
+ )
+ }
+ }
+
+ fun openChat(feedbackData: FeedbackData) {
+ feedbackData.prepare(infoHolder)
+ foregroundActivityObserver.withForegroundActivity { activity ->
+ setChatVisitorInfo()
+ setChatVisitorNote(activity, feedbackData)
+ showMessagingActivity(activity)
+ }
+ }
+
+ private fun createLogFile(context: Context): File? {
+ return try {
+ val file = File(context.filesDir, "logs.txt")
+ file.delete()
+ file.createNewFile()
+
+ val stringWriter = StringWriter()
+ logCollector.getLogs().forEach { stringWriter.append(it) }
+ val fileWriter = FileWriter(file)
+ fileWriter.write(stringWriter.toString())
+ fileWriter.close()
+ logCollector.clearLogs()
+ file
+ } catch (ex: Exception) {
+ Timber.e(ex, "Can't create the logs file")
+ null
+ }
+ }
+
+ private fun setChatVisitorInfo() {
+ if (preferencesStorage.chatFirstLaunchTime == null) {
+ preferencesStorage.chatFirstLaunchTime = System.currentTimeMillis()
+ }
+ val chatUserId = (preferencesStorage.chatFirstLaunchTime.toString() + Build.MODEL).hashCode()
+ val visitorInfo = VisitorInfo.builder()
+ .withName("User $chatUserId")
+ .build()
+
+ Chat.INSTANCE.chatProvidersConfiguration = ChatProvidersConfiguration.builder()
+ .withVisitorInfo(visitorInfo)
+ .build()
+ }
+
+ private fun setChatVisitorNote(
+ context: Context,
+ feedbackData: FeedbackData,
+ ) {
+ Chat.INSTANCE.providers()
+ ?.profileProvider()
+ ?.setVisitorNote(feedbackData.joinTogether(context, infoHolder))
+ }
+
+ private fun showMessagingActivity(context: Context) {
+ MessagingActivity.builder()
+ .withMultilineResponseOptionsEnabled(false)
+ .withBotLabelStringRes(R.string.chat_bot_name)
+ .withBotAvatarDrawable(R.mipmap.ic_launcher)
+ .withEngines(ChatEngine.engine())
+ .show(context, buildChatConfig())
+ }
+
+ private fun buildChatConfig(): Configuration {
+ return ChatConfiguration.builder()
+ .withOfflineFormEnabled(true)
+ .withAgentAvailabilityEnabled(true)
+ .withPreChatFormEnabled(false)
+ .build()
+ }
+
+ private fun getSupportEmail(): String {
+ return if (TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)) {
+ S2C_SUPPORT_EMAIL
+ } else {
+ DEFAULT_SUPPORT_EMAIL
+ }
+ }
+
+ companion object {
+ const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com"
+ const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/log/TangemLogCollector.kt b/app/src/main/java/com/tangem/tap/common/log/TangemLogCollector.kt
new file mode 100644
index 0000000000..54a56e17c3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/log/TangemLogCollector.kt
@@ -0,0 +1,31 @@
+package com.tangem.tap.common.log
+
+import com.tangem.Log
+import com.tangem.LogFormat
+import com.tangem.TangemSdkLogger
+import java.text.SimpleDateFormat
+import java.util.*
+
+class TangemLogCollector(
+ private val levels: List,
+ private val messageFormatter: LogFormat,
+) : TangemSdkLogger {
+
+ private val dateFormatter = SimpleDateFormat("HH:mm:ss.SSS")
+ private val logs = mutableListOf()
+ private val mutex = Object()
+
+ override fun log(message: () -> String, level: Log.Level) {
+ if (!levels.contains(level)) return
+
+ synchronized(mutex) {
+ val formattedMessage = messageFormatter.format(message, level)
+ val logMessage = "${dateFormatter.format(Date())}: $formattedMessage"
+ logs.add("$logMessage\n")
+ }
+ }
+
+ fun getLogs(): List = synchronized(mutex) { logs.toList() }
+
+ fun clearLogs() = synchronized(mutex) { logs.clear() }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
index 591a20e896..89d609b32c 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
@@ -2,8 +2,8 @@ package com.tangem.tap.common.redux
import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.TestAction
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.AddressData
-import com.tangem.tap.features.wallet.redux.Currency
/**
[REDACTED_AUTHOR]
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
index a82d5ba94b..4927405ccd 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
@@ -7,14 +7,18 @@ import com.tangem.common.core.TangemError
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
import com.tangem.tap.common.entities.FiatCurrency
-import com.tangem.tap.common.redux.*
+import com.tangem.tap.common.feedback.FeedbackData
+import com.tangem.tap.common.feedback.FeedbackManager
+import com.tangem.tap.common.redux.DebugErrorAction
+import com.tangem.tap.common.redux.ErrorAction
+import com.tangem.tap.common.redux.NotificationAction
+import com.tangem.tap.common.redux.StateDialog
+import com.tangem.tap.common.redux.ToastNotificationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.features.details.redux.SecurityOption
-import com.tangem.tap.features.feedback.EmailData
-import com.tangem.tap.features.feedback.FeedbackManager
import org.rekotlin.Action
sealed class GlobalAction : Action {
@@ -73,7 +77,8 @@ sealed class GlobalAction : Action {
data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction()
data class SetAnanlyticHandlers(val analyticsHandlers: GlobalAnalyticsHandler) : GlobalAction()
- data class SendFeedback(val emailData: EmailData) : GlobalAction()
+ data class SendEmail(val feedbackData: FeedbackData) : GlobalAction()
+ data class OpenChat(val feedbackData: FeedbackData) : GlobalAction()
data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction()
object ExchangeManager : GlobalAction() {
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
index 344a11cc29..4d227fcd92 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
@@ -15,6 +15,7 @@ import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.network.exchangeServices.CardExchangeRules
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoApi
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
@@ -23,11 +24,11 @@ import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
-import java.util.Locale
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
+import java.util.*
class GlobalMiddleware {
companion object {
@@ -62,9 +63,11 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
}
}
is GlobalAction.RestoreAppCurrency -> {
- store.dispatch(GlobalAction.RestoreAppCurrency.Success(
- preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency()
- ))
+ store.dispatch(
+ GlobalAction.RestoreAppCurrency.Success(
+ preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency(),
+ ),
+ )
}
is GlobalAction.HideWarningMessage -> {
store.state.globalState.warningManager?.let {
@@ -79,8 +82,11 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
}
}
}
- is GlobalAction.SendFeedback -> {
- store.state.globalState.feedbackManager?.send(action.emailData)
+ is GlobalAction.SendEmail -> {
+ store.state.globalState.feedbackManager?.sendEmail(action.feedbackData)
+ }
+ is GlobalAction.OpenChat -> {
+ store.state.globalState.feedbackManager?.openChat(action.feedbackData)
}
is GlobalAction.UpdateWalletSignedHashes -> {
store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures))
@@ -104,7 +110,13 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
secret = mercuryoSecret,
)
val sellService = MoonPayService(moonPayKey, moonPaySecretKey)
- val exchangeManager = CurrencyExchangeManager(buyService, sellService)
+ val cardProvider = { store.state.globalState.scanResponse?.card }
+
+ val exchangeManager = CurrencyExchangeManager(
+ buyService = buyService,
+ sellService = sellService,
+ primaryRules = CardExchangeRules(cardProvider),
+ )
store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
}
@@ -124,7 +136,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
store.state.globalState.analyticsHandlers,
currenciesRepository,
action.additionalBlockchainsToDerive,
- action.messageResId
+ action.messageResId,
)
withMainContext {
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
@@ -147,15 +159,15 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
is Result.Success -> {
store.dispatchOnMain(
GlobalAction.FetchUserCountry.Success(
- countryCode = result.data.code.lowercase()
- )
+ countryCode = result.data.code.lowercase(),
+ ),
)
}
is Result.Failure -> {
store.dispatchOnMain(
GlobalAction.FetchUserCountry.Success(
- countryCode = Locale.getDefault().country.lowercase()
- )
+ countryCode = Locale.getDefault().country.lowercase(),
+ ),
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
index 8588256411..1fed436b47 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
@@ -3,12 +3,12 @@ package com.tangem.tap.common.redux.global
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.common.entities.FiatCurrency
+import com.tangem.tap.common.feedback.FeedbackManager
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
-import com.tangem.tap.features.feedback.FeedbackManager
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import org.rekotlin.StateType
@@ -25,7 +25,7 @@ data class GlobalState(
val appCurrency: FiatCurrency = FiatCurrency.Default,
val scanCardFailsCounter: Int = 0,
val dialog: StateDialog? = null,
- val exchangeManager: CurrencyExchangeManager? = null,
+ val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
val resources: AndroidResources = AndroidResources(),
val analyticsHandlers: AnalyticsHandler? = null,
val userCountryCode: String? = null,
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt
index 214a034270..0579c7f651 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.common.redux.navigation
+import android.net.Uri
import androidx.fragment.app.FragmentActivity
import org.rekotlin.Action
import java.lang.ref.WeakReference
@@ -15,6 +16,8 @@ sealed class NavigationAction : Action {
data class OpenUrl(val url: String) : NavigationAction()
+ data class OpenDocument(val url: Uri) : NavigationAction()
+
data class Share(val data: String) : NavigationAction()
data class ActivityCreated(val activity: WeakReference) : NavigationAction()
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
index e8e0168455..8a2f5c5ac1 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.common.redux.navigation
+import android.content.Intent
import com.tangem.tap.common.CustomTabsManager
import com.tangem.tap.common.extensions.openFragment
import com.tangem.tap.common.extensions.popBackTo
@@ -32,6 +33,11 @@ val navigationMiddleware: Middleware = { dispatch, state ->
CustomTabsManager().openUrl(action.url, it)
}
}
+ is NavigationAction.OpenDocument -> {
+ val intent = Intent(Intent.ACTION_VIEW)
+ intent.data = action.url
+ navState?.activity?.get()?.startActivity(intent)
+ }
is NavigationAction.Share -> {
navState?.activity?.get()?.let {
it.shareText(action.data)
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
index 1fc6832148..9c6468c049 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt
@@ -16,7 +16,7 @@ enum class AppScreen {
OnboardingNote, OnboardingWallet, OnboardingTwins, OnboardingOther,
Wallet, WalletDetails,
Send,
- Details, DetailsConfirm, DetailsSecurity,
+ Details, DetailsSecurity, CardSettings, AppSettings, ResetToFactory,
AddTokens, AddCustomToken,
WalletConnectSessions,
QrScan
diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt
index 3da94dc568..f5281cc796 100644
--- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt
+++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt
@@ -5,6 +5,7 @@ import android.content.Intent
import com.google.android.gms.wallet.PaymentData
import com.shopify.buy3.Storefront
import com.tangem.tap.common.analytics.AnalyticsHandler
+import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.common.shop.data.ProductType
import com.tangem.tap.common.shop.data.TangemProduct
import com.tangem.tap.common.shop.data.TotalSum
@@ -21,7 +22,6 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
private val shopifyService = ShopifyService(application, shopifyShop)
- private lateinit var product: Storefront.Product
private val checkouts = mutableMapOf()
private val variants = mutableMapOf()
@@ -31,22 +31,19 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
suspend fun getProducts(): Result> {
val result = shopifyService.getProducts()
- result.onSuccess {
- product = it.first {
- val variantsSku = it.variants.edges.map { it.node.sku }
- variantsSku.contains(ProductType.WALLET_2_CARDS.sku) && variantsSku.contains(
- ProductType.WALLET_3_CARDS.sku
- )
- }
- product.variants.edges.map { it.node }
- .forEach { variant ->
- if (variant.sku == ProductType.WALLET_2_CARDS.sku) {
- variants[ProductType.WALLET_2_CARDS] = variant
- } else if (variant.sku == ProductType.WALLET_3_CARDS.sku) {
- variants[ProductType.WALLET_3_CARDS] = variant
- }
- }
+ return result.mapCatching { product ->
+ val availableVariants = product
+ .flatMap { it.variants.edges.map { it.node } }
+ .filter { SKUS_TO_DISPLAY.contains(it.sku) }
+ .associateBy { ProductType.fromSku(it.sku) }
+ .filterNotNull()
+ variants.putAll(availableVariants)
+ if (variants.size < SKUS_TO_DISPLAY.size) {
+ return Result.failure(Exception(
+ "Shopify: products are missing, " +
+ "\nproducts available: ${variants.keys.map { it.sku }}"))
+ }
val twoCardsProduct = TangemProduct(
type = ProductType.WALLET_2_CARDS,
@@ -65,7 +62,6 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
createCheckouts()
return Result.success(listOf(twoCardsProduct, threeCardsProduct))
}
- return Result.failure(result.exceptionOrNull()!!)
}
private suspend fun createCheckouts() {
@@ -214,6 +210,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
companion object {
const val TANGEM_WALLET_2_CARDS_SKU = "TG115x2"
const val TANGEM_WALLET_3_CARDS_SKU = "TG115x3"
+ val SKUS_TO_DISPLAY = listOf(TANGEM_WALLET_2_CARDS_SKU, TANGEM_WALLET_3_CARDS_SKU)
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/shop/data/ProductType.kt b/app/src/main/java/com/tangem/tap/common/shop/data/ProductType.kt
index 89116f8096..d8db451147 100644
--- a/app/src/main/java/com/tangem/tap/common/shop/data/ProductType.kt
+++ b/app/src/main/java/com/tangem/tap/common/shop/data/ProductType.kt
@@ -4,5 +4,15 @@ import com.tangem.tap.common.shop.TangemShopService
enum class ProductType(val sku: String) {
WALLET_2_CARDS(TangemShopService.TANGEM_WALLET_2_CARDS_SKU),
- WALLET_3_CARDS(TangemShopService.TANGEM_WALLET_3_CARDS_SKU)
+ WALLET_3_CARDS(TangemShopService.TANGEM_WALLET_3_CARDS_SKU);
+
+ companion object {
+ fun fromSku(sku: String): ProductType? {
+ return when (sku) {
+ WALLET_2_CARDS.sku -> WALLET_2_CARDS
+ WALLET_3_CARDS.sku -> WALLET_3_CARDS
+ else -> null
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt b/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt
new file mode 100644
index 0000000000..8cd6e7e5ba
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt
@@ -0,0 +1,59 @@
+package com.tangem.tap.common.ui
+
+import android.content.Context
+import androidx.appcompat.app.AlertDialog
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.store
+import com.tangem.wallet.R
+
+class SimpleAlertDialog {
+ companion object {
+ fun create(
+ titleRes: Int? = null,
+ messageRes: Int? = null,
+ title: String? = null,
+ message: String? = null,
+ primaryButtonRes: Int = R.string.common_ok,
+ context: Context,
+ ): AlertDialog {
+ return SimpleCancelableAlertDialog.create(
+ titleRes = titleRes,
+ messageRes = messageRes,
+ title = title,
+ message = message,
+ primaryButtonRes = primaryButtonRes,
+ secondaryButtonRes = null,
+ context = context
+ )
+ }
+ }
+}
+
+class SimpleCancelableAlertDialog {
+ companion object {
+ fun create(
+ titleRes: Int? = null,
+ messageRes: Int? = null,
+ title: String? = null,
+ message: String? = null,
+ primaryButtonRes: Int = R.string.common_ok,
+ secondaryButtonRes: Int? = R.string.common_cancel,
+ primaryButtonAction: () -> Unit = {},
+ secondaryButtonAction: () -> Unit = {},
+ context: Context,
+ ): AlertDialog {
+
+ return AlertDialog.Builder(context).apply {
+ setTitle(titleRes?.let { context.getString(it) } ?: title )
+ setMessage(messageRes?.let { context.getString(it) } ?: message)
+ setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() }
+ if (secondaryButtonRes != null) {
+ setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction()}
+ }
+ setOnDismissListener {
+ store.dispatch(GlobalAction.HideDialog)
+ }
+ }.create()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/zendesk/ZendeskConfig.kt b/app/src/main/java/com/tangem/tap/common/zendesk/ZendeskConfig.kt
new file mode 100644
index 0000000000..66569ee1b7
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/zendesk/ZendeskConfig.kt
@@ -0,0 +1,18 @@
+package com.tangem.tap.common.zendesk
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class ZendeskConfig(
+ @Json(name = "zendeskApiKey")
+ val apiKey: String,
+ @Json(name = "zendeskAppId")
+ val appId: String,
+ @Json(name = "zendeskClientId")
+ val clientId: String,
+ @Json(name = "zendeskAccountKey")
+ val accountKey: String,
+ @Json(name = "zendeskUrl")
+ val url: String,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt
index 4c9cafbdee..51252c819c 100644
--- a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt
@@ -3,7 +3,7 @@ package com.tangem.tap.domain
import com.tangem.common.services.Result
import com.tangem.domain.common.ThrottlerWithValues
import com.tangem.network.api.tangemTech.TangemTechService
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.store
import java.math.BigDecimal
diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
index 61dbbe7f32..4833079ad0 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
@@ -19,6 +19,7 @@ import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.domain.common.ScanResponse
import com.tangem.operations.CommandResponse
+import com.tangem.operations.ScanTask
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.pins.CheckUserCodesCommand
@@ -144,6 +145,12 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
initialMessage = Message(context.getString(R.string.initial_message_tap_header)))
}
+ suspend fun scanCard(): CompletionResult {
+ return runTaskAsyncReturnOnMain(
+ ScanTask(),
+ initialMessage = Message(context.getString(R.string.initial_message_tap_header)))
+ }
+
suspend fun runTaskAsync(
runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null,
): CompletionResult =
diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
index 20d1eda08c..9e22833682 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
@@ -5,44 +5,25 @@ import com.tangem.TangemSdk
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
-import com.tangem.tap.domain.tasks.SignHashTask
+import com.tangem.common.card.Card
import com.tangem.tap.domain.tasks.SignHashesTask
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class TangemSigner(
+ private val card: Card,
private val tangemSdk: TangemSdk,
private val initialMessage: Message,
private val signerCallback: (TangemSignerResponse) -> Unit,
) : TransactionSigner {
- override suspend fun sign(hash: ByteArray, cardId: String, publicKey: Wallet.PublicKey): CompletionResult {
+ override suspend fun sign(
+ hashes: List,
+ publicKey: Wallet.PublicKey
+ ): CompletionResult> {
return suspendCoroutine { continuation ->
- val command = SignHashTask(hash, publicKey)
- tangemSdk.startSessionWithRunnable(
- runnable = command,
- cardId = cardId,
- initialMessage = initialMessage,
- ) { result ->
- when (result) {
- is CompletionResult.Success -> {
- signerCallback(
- TangemSignerResponse(
- result.data.totalSignedHashes,
- result.data.remainingSignatures
- )
- )
- continuation.resume(CompletionResult.Success(result.data.signature))
- }
- is CompletionResult.Failure ->
- continuation.resume(CompletionResult.Failure(result.error))
- }
- }
- }
- }
+ val cardId = if (card.backupStatus?.isActive == true) null else card.cardId
- override suspend fun sign(hashes: List, cardId: String, publicKey: Wallet.PublicKey): CompletionResult> {
- return suspendCoroutine { continuation ->
val task = SignHashesTask(hashes, publicKey)
tangemSdk.startSessionWithRunnable(
runnable = task,
@@ -65,6 +46,21 @@ class TangemSigner(
}
}
}
+
+ override suspend fun sign(
+ hash: ByteArray,
+ publicKey: Wallet.PublicKey
+ ): CompletionResult {
+ val result = sign(
+ hashes = listOf(hash),
+ publicKey = publicKey
+ )
+
+ return when (result) {
+ is CompletionResult.Success -> CompletionResult.Success(result.data.first())
+ is CompletionResult.Failure -> CompletionResult.Failure(result.error)
+ }
+ }
}
data class TangemSignerResponse(
diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
index 0c38cd5a46..9d3cc79eee 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
@@ -39,21 +39,22 @@ sealed class TapError(
object DustChange : TapError(R.string.send_error_dust_change)
data class CreateAccountUnderfunded(override val args: List) : TapError(R.string.send_error_no_target_account)
- sealed class XmlError {
- object AssetAccountNotCreated : TapError(R.string.send_error_no_account_xlm)
- }
+ data class UnsupportedState(
+ val stateError: String,
+ val customMessage: String = "Unsupported state:"
+ ) : TapError(R.string.common_custom_string, listOf("$customMessage $stateError"))
sealed class WalletManager {
- object CreationError: CustomError("Can't create wallet manager")
- class NoAccountError(amountToCreateAccount: String): CustomError(amountToCreateAccount)
- class InternalError(message: String): CustomError(message)
- object BlockchainIsUnreachable: TapError(R.string.wallet_balance_blockchain_unreachable)
- object BlockchainIsUnreachableTryLater: TapError(R.string.wallet_balance_blockchain_unreachable_try_later)
+ object CreationError : CustomError("Can't create wallet manager")
+ class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)
+ class InternalError(message: String) : CustomError(message)
+ object BlockchainIsUnreachable : TapError(R.string.wallet_balance_blockchain_unreachable)
+ object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later)
}
data class ValidateTransactionErrors(
- override val errorList: List,
- override val builder: (List) -> String
+ override val errorList: List,
+ override val builder: (List) -> String
) : TapError(-1), MultiMessageError
}
diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
index cce3a27c15..8bd8cf8ef7 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
@@ -1,6 +1,12 @@
package com.tangem.tap.domain
-import com.tangem.blockchain.common.*
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.BlockchainSdkConfig
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.common.WalletManagerFactory
+import com.tangem.common.card.Card
import com.tangem.common.services.Result
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
@@ -26,7 +32,7 @@ import kotlinx.coroutines.withContext
class TapWalletManager {
val walletManagerFactory: WalletManagerFactory
- by lazy { WalletManagerFactory(blockchainSdkConfig) }
+ by lazy { WalletManagerFactory(blockchainSdkConfig) }
val rates: RatesRepository = RatesRepository()
@@ -34,10 +40,10 @@ class TapWalletManager {
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
}
- private val walletManagersThrottler = ThrottlerWithValues>(10000)
+ private val walletManagersThrottler =
+ ThrottlerWithValues>(10000)
suspend fun loadWalletData(walletManager: WalletManager) {
- val blockchain = walletManager.wallet.blockchain
val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
val result = if (walletManagersThrottler.isStillThrottled(blockchainNetwork)) {
walletManagersThrottler.geValue(blockchainNetwork)!!
@@ -54,17 +60,21 @@ class TapWalletManager {
is Result.Failure -> {
when (result.error) {
is TapError.WalletManager.NoAccountError -> {
- dispatchOnMain(WalletAction.LoadWallet.NoAccount(
- walletManager.wallet,
- blockchainNetwork,
- (result.error as TapError.WalletManager.NoAccountError).customMessage
- ))
+ dispatchOnMain(
+ WalletAction.LoadWallet.NoAccount(
+ walletManager.wallet,
+ blockchainNetwork,
+ (result.error as TapError.WalletManager.NoAccountError).customMessage
+ )
+ )
}
else -> {
- dispatchOnMain(WalletAction.LoadWallet.Failure(
- walletManager.wallet,
- result.error.localizedMessage
- ))
+ dispatchOnMain(
+ WalletAction.LoadWallet.Failure(
+ walletManager.wallet,
+ result.error.localizedMessage
+ )
+ )
}
}
}
@@ -77,10 +87,16 @@ class TapWalletManager {
updateConfigManager(data)
withMainContext {
- store.dispatch(WalletAction.ResetState)
+ store.dispatch(WalletAction.ResetState(data.card.cardId))
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard))
store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
+ store.dispatch(
+ WalletAction.MultiWallet.ShowWalletBackupWarning(
+ show = data.card.settings.isBackupAllowed
+ && data.card.backupStatus == Card.BackupStatus.NoBackup
+ )
+ )
loadData(data)
}
}
@@ -109,6 +125,37 @@ class TapWalletManager {
return
}
+ if (data.card.isMultiwalletAllowed) {
+ loadMultiWalletData(data)
+ } else {
+ loadSingleWalletData(data)
+ }
+
+ dispatchOnMain(WalletAction.LoadWallet())
+ dispatchOnMain(WalletAction.LoadFiatRate())
+ }
+
+ private suspend fun loadMultiWalletData(
+ scanResponse: ScanResponse
+ ) {
+ val savedCurrencies = currenciesRepository.loadSavedCurrencies(
+ scanResponse.card.cardId, scanResponse.card.settings.isHDWalletAllowed
+ )
+ if (savedCurrencies.isEmpty()) return
+
+ val walletManagers =
+ walletManagerFactory.makeWalletManagersForApp(scanResponse, savedCurrencies)
+ dispatchOnMain(
+ WalletAction.MultiWallet.AddBlockchains(savedCurrencies, walletManagers),
+ )
+ savedCurrencies.map {
+ if (it.tokens.isNotEmpty()) {
+ dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it))
+ }
+ }
+ }
+
+ private suspend fun loadSingleWalletData(data: ScanResponse) {
val blockchain = data.getBlockchain()
val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
@@ -120,87 +167,12 @@ class TapWalletManager {
primaryWalletManager.addToken(primaryToken)
dispatchOnMain(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
}
- if (data.card.isMultiwalletAllowed) {
- loadMultiWalletData(data, blockchain, primaryWalletManager)
- } else {
- dispatchOnMain(WalletAction.MultiWallet.AddBlockchains(
+ dispatchOnMain(
+ WalletAction.MultiWallet.AddBlockchains(
listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)),
listOf(primaryWalletManager)
- ))
- }
- } else {
- if (data.card.isMultiwalletAllowed) {
- loadMultiWalletData(data, blockchain, null)
- }
- }
- dispatchOnMain(WalletAction.LoadWallet())
- dispatchOnMain(WalletAction.LoadFiatRate())
- }
-
- private suspend fun loadMultiWalletData(
- scanResponse: ScanResponse,
- primaryBlockchain: Blockchain?,
- primaryWalletManager: WalletManager?
- ) {
- val primaryTokens = primaryWalletManager?.cardTokens?.toList() ?: emptyList()
- val savedCurrencies = currenciesRepository.loadSavedCurrencies(
- scanResponse.card.cardId, scanResponse.card.settings.isHDWalletAllowed
- )
-
- if (savedCurrencies.isEmpty()) {
- if (primaryBlockchain != null && primaryWalletManager != null) {
- val blockchainNetwork = BlockchainNetwork.fromWalletManager(primaryWalletManager)
- dispatchOnMain(
- WalletAction.MultiWallet.SaveCurrencies(listOf(blockchainNetwork)),
- WalletAction.MultiWallet.AddBlockchains(
- listOf(blockchainNetwork),
- listOf(primaryWalletManager)
- ),
- WalletAction.MultiWallet.AddTokens(primaryTokens.toList(), blockchainNetwork)
)
-
- } else {
- val blockchainNetworks = listOf(
- BlockchainNetwork(Blockchain.Bitcoin, scanResponse.card),
- BlockchainNetwork(Blockchain.Ethereum, scanResponse.card)
- )
-
- val walletManagers = walletManagerFactory.makeWalletManagersForApp(
- scanResponse,
- blockchainNetworks
- )
-
- dispatchOnMain(
- WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks),
- WalletAction.MultiWallet.AddBlockchains(blockchainNetworks, walletManagers),
- )
- }
-// dispatchOnMain(
-// WalletAction.MultiWallet.FindBlockchainsInUse,
-// WalletAction.MultiWallet.FindTokensInUse,
-// )
- } else {
- val walletManagers = if (
- primaryTokens.isNotEmpty() &&
- primaryWalletManager != null &&
- primaryBlockchain != null && primaryBlockchain != Blockchain.Unknown
- ) {
- val blockchainsWithoutPrimary = savedCurrencies.filterNot { it.blockchain == primaryBlockchain }
- walletManagerFactory.makeWalletManagersForApp(
- scanResponse,
- blockchainsWithoutPrimary
- ).plus(primaryWalletManager)
- } else {
- walletManagerFactory.makeWalletManagersForApp(scanResponse, savedCurrencies)
- }
- dispatchOnMain(
- WalletAction.MultiWallet.AddBlockchains(savedCurrencies, walletManagers),
)
- savedCurrencies.map {
- if (it.tokens.isNotEmpty()) {
- dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it))
- }
- }
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
index bd99957d6e..bb94004636 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
@@ -1,8 +1,8 @@
package com.tangem.tap.domain.configurable.config
import com.tangem.blockchain.common.BlockchainSdkConfig
-import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.shop.shopify.ShopifyShop
+import com.tangem.tap.common.zendesk.ZendeskConfig
import com.tangem.tap.domain.configurable.Loader
/**
@@ -20,7 +20,8 @@ data class Config(
val isTopUpEnabled: Boolean = false,
@Deprecated("Not relevant since version 3.23")
val isCreatingTwinCardsAllowed: Boolean = false,
- val shopify: ShopifyShop? = null
+ val shopify: ShopifyShop? = null,
+ val zendesk: ZendeskConfig? = null,
)
class ConfigManager(
@@ -33,11 +34,11 @@ class ConfigManager(
private var defaultConfig = Config()
- fun load(onComplete: VoidCallback? = null) {
- localLoader.load { config ->
- setupFeature(config.features)
- setupKey(config.configValues)
- onComplete?.invoke()
+ fun load(onComplete: ((config: Config) -> Unit)? = null) {
+ localLoader.load { configModel ->
+ setupFeature(configModel.features)
+ setupKey(configModel.configValues)
+ onComplete?.invoke(config)
}
// Uncomment to enable remote config
// remoteLoader.load { config ->
@@ -95,6 +96,7 @@ class ConfigManager(
),
appsFlyerDevKey = values.appsFlyerDevKey,
shopify = values.shopifyShop,
+ zendesk = values.zendesk,
)
defaultConfig = defaultConfig.copy(
coinMarketCapKey = values.coinMarketCapKey,
@@ -110,6 +112,7 @@ class ConfigManager(
),
appsFlyerDevKey = values.appsFlyerDevKey,
shopify = values.shopifyShop,
+ zendesk = values.zendesk,
)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
index 542a006a8e..6e119fc5e6 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.domain.configurable.config
import com.tangem.tap.common.shop.shopify.ShopifyShop
+import com.tangem.tap.common.zendesk.ZendeskConfig
/**
[REDACTED_AUTHOR]
@@ -25,6 +26,7 @@ class ConfigValueModel(
val infuraProjectId: String?,
val appsFlyerDevKey: String,
val shopifyShop: ShopifyShop?,
+ val zendesk: ZendeskConfig?,
val tronGridApiKey: String,
)
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
index ac2d922c0d..10dbcb2cbb 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
@@ -6,7 +6,6 @@ import com.tangem.tap.common.extensions.containsAny
import com.tangem.tap.common.extensions.removeBy
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
-import java.util.*
/**
[REDACTED_AUTHOR]
@@ -188,31 +187,6 @@ class WarningMessagesManager(
WarningMessage.Origin.Local
)
- fun restoreFundsWarning(): WarningMessage = WarningMessage(
- title = "",
- message = "",
- type = WarningMessage.Type.Temporary,
- priority = WarningMessage.Priority.Warning,
- listOf(WarningMessage.Location.MainScreen),
- blockchains = null,
- titleResId = R.string.alert_title,
- messageResId = R.string.alert_funds_restoration_message,
- origin = WarningMessage.Origin.Local,
- buttonTextId = R.string.warning_button_learn_more
- )
-
const val REMAINING_SIGNATURES_WARNING = 10
- private const val RESTORE_FUNDS_GUIDE_URL_RU =
- "https://tangem.com/ru/kak-vosstanovit-tokeny-otpravlennye-ne-na-tot-adres-v-tangem-wallet"
- private const val RESTORE_FUNDS_GUIDE_URL_EN =
- "https://tangem.com/en/how-to-recover-crypto-sent-to-the-wrong-address-in-tangem-wallet"
-
- fun getRestoreFundsGuideUrl(locale: String): String {
- return if (locale == Locale("ru").language) {
- RESTORE_FUNDS_GUIDE_URL_RU
- } else {
- RESTORE_FUNDS_GUIDE_URL_EN
- }
- }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt
index 0dc1819018..c627a7de91 100644
--- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt
+++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt
@@ -6,8 +6,8 @@ import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
-import com.tangem.domain.common.TapWorkarounds
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
+import com.tangem.domain.common.TapWorkarounds.isTangemNote
import com.tangem.domain.common.TwinCardNumber
import com.tangem.domain.common.getTwinCardNumber
import com.tangem.domain.common.isTangemTwin
@@ -15,7 +15,6 @@ import com.tangem.operations.attestation.CardVerifyAndGetInfo
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.features.wallet.redux.Artwork
-
val Card.remainingSignatures: Int?
get() = this.getSingleWallet()?.remainingSignatures
@@ -24,7 +23,7 @@ val Card.isWalletDataSupported: Boolean
val Card.isMultiwalletAllowed: Boolean
get() {
- return !isTangemTwin() && !isStart2Coin && !TapWorkarounds.isTangemNote(this)
+ return !isTangemTwin() && !isStart2Coin && !isTangemNote()
&& (firmwareVersion >= FirmwareVersion.MultiWalletAvailable ||
getSingleWallet()?.curve == EllipticCurve.Secp256k1)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
index bff4c0c3b7..34b8289884 100644
--- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
+++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
@@ -11,7 +11,7 @@ import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
fun WalletManagerFactory.makeWalletManagerForApp(
scanResponse: ScanResponse,
@@ -32,9 +32,10 @@ fun WalletManagerFactory.makeWalletManagerForApp(
return when {
scanResponse.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> {
makeTwinWalletManager(
- card.cardId,
- wallet.publicKey, scanResponse.secondTwinPublicKey!!.hexToBytes(),
- environmentBlockchain, wallet.curve
+ walletPublicKey = wallet.publicKey,
+ pairPublicKey = scanResponse.secondTwinPublicKey!!.hexToBytes(),
+ blockchain = environmentBlockchain,
+ curve = wallet.curve
)
}
seedKey != null && derivationParams != null -> {
@@ -47,7 +48,6 @@ fun WalletManagerFactory.makeWalletManagerForApp(
?: return null
makeWalletManager(
- cardId = card.cardId,
blockchain = environmentBlockchain,
seedKey = wallet.publicKey,
derivedKey = derivedKey,
@@ -56,7 +56,6 @@ fun WalletManagerFactory.makeWalletManagerForApp(
}
else -> {
makeWalletManager(
- cardId = card.cardId,
blockchain = environmentBlockchain,
walletPublicKey = wallet.publicKey,
curve = wallet.curve
diff --git a/app/src/main/java/com/tangem/tap/domain/statePrinter/WalletStateConverter.kt b/app/src/main/java/com/tangem/tap/domain/statePrinter/WalletStateConverter.kt
index 07ac4886bb..57762aaf51 100644
--- a/app/src/main/java/com/tangem/tap/domain/statePrinter/WalletStateConverter.kt
+++ b/app/src/main/java/com/tangem/tap/domain/statePrinter/WalletStateConverter.kt
@@ -57,7 +57,6 @@ class WalletStateConverter : StringStateConverter {
walletMap["publicKey"] = publicKeyMap
walletMap["amounts"] = amounts
walletMap["addresses"] = wallet.addresses.toString()
- walletMap["cardId"] = wallet.cardId
return walletMap
}
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
index d57336841e..e54c3744cb 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
@@ -14,10 +14,14 @@ import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toHexString
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
-import com.tangem.domain.common.*
+import com.tangem.domain.common.ProductType
+import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isExcluded
import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease
+import com.tangem.domain.common.TapWorkarounds.isTangemNote
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
+import com.tangem.domain.common.TwinsHelper
+import com.tangem.domain.common.isTangemTwins
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.operations.ScanTask
@@ -56,7 +60,7 @@ class ScanProductTask(
}
val commandProcessor = when {
- TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor()
+ card.isTangemNote() -> ScanNoteProcessor()
card.isTangemTwins() -> ScanTwinProcessor()
else -> ScanWalletProcessor(currenciesRepository, additionalBlockchainsToDerive)
}
@@ -64,16 +68,15 @@ class ScanProductTask(
when (processorResult) {
is CompletionResult.Success -> ScanTask().run(session) { scanTaskResult ->
when (scanTaskResult) {
- is CompletionResult.Success -> callback(
- CompletionResult.Success(
- processorResult.data
+ is CompletionResult.Success -> {
+ // it need because processorResult.data.card doesn't contains attestation result
+ // and CardWallet.derivedKeys
+ val processorScanResponseWithNewCard = processorResult.data.copy(
+ card = scanTaskResult.data
)
- )
- is CompletionResult.Failure -> callback(
- CompletionResult.Failure(
- scanTaskResult.error
- )
- )
+ callback(CompletionResult.Success(processorScanResponseWithNewCard))
+ }
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(scanTaskResult.error))
}
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(processorResult.error))
@@ -98,11 +101,11 @@ private class ScanNoteProcessor : ProductCommandProcessor {
callback(
CompletionResult.Success(
ScanResponse(
- card,
- ProductType.Note,
- session.environment.walletData
- )
- )
+ card = card,
+ productType = ProductType.Note,
+ walletData = session.environment.walletData,
+ ),
+ ),
)
}
}
@@ -113,19 +116,17 @@ private class ScanWalletProcessor(
) : ProductCommandProcessor {
var primaryCard: PrimaryCard? = null
-
override fun proceed(
card: Card,
session: CardSession,
- callback: (result: CompletionResult) -> Unit
+ callback: (result: CompletionResult) -> Unit,
) {
createMissingWalletsIfNeeded(card, session, callback)
}
-
private fun createMissingWalletsIfNeeded(
card: Card,
session: CardSession,
- callback: (result: CompletionResult) -> Unit
+ callback: (result: CompletionResult) -> Unit,
) {
if (card.wallets.isEmpty() || card.firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
startLinkingForBackupIfNeeded(card, session, callback)
@@ -142,18 +143,12 @@ private class ScanWalletProcessor(
when (result) {
is CompletionResult.Success -> {
PreflightReadTask(
- PreflightReadMode.FullCardRead,
- card.cardId
+ readMode = PreflightReadMode.FullCardRead,
+ cardId = card.cardId
).run(session) { readResult ->
when (readResult) {
- is CompletionResult.Success -> {
- startLinkingForBackupIfNeeded(card, session, callback)
- }
- is CompletionResult.Failure -> callback(
- CompletionResult.Failure(
- readResult.error
- )
- )
+ is CompletionResult.Success -> startLinkingForBackupIfNeeded(card, session, callback)
+ is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error))
}
}
}
@@ -161,18 +156,14 @@ private class ScanWalletProcessor(
}
}
}
-
private fun startLinkingForBackupIfNeeded(
card: Card,
session: CardSession,
- callback: (result: CompletionResult) -> Unit
+ callback: (result: CompletionResult) -> Unit,
) {
- val activationIsFinished =
- preferencesStorage.usedCardsPrefStorage.isActivationFinished(card.cardId)
+ val activationIsFinished = preferencesStorage.usedCardsPrefStorage.isActivationFinished(card.cardId)
- if (card.backupStatus == Card.BackupStatus.NoBackup &&
- !activationIsFinished && card.wallets.isNotEmpty()
- ) {
+ if (card.backupStatus == Card.BackupStatus.NoBackup && !activationIsFinished && card.wallets.isNotEmpty()) {
StartPrimaryCardLinkingTask().run(session) { linkingResult ->
when (linkingResult) {
is CompletionResult.Success -> {
@@ -188,11 +179,10 @@ private class ScanWalletProcessor(
deriveKeysIfNeeded(card, session, callback)
}
}
-
private fun deriveKeysIfNeeded(
card: Card,
session: CardSession,
- callback: (result: CompletionResult) -> Unit
+ callback: (result: CompletionResult) -> Unit,
) {
scope.launch {
val derivations = collectDerivations(card)
@@ -231,12 +221,13 @@ private class ScanWalletProcessor(
private suspend fun getBlockchainsToDerive(card: Card): List {
val currenciesRepository = currenciesRepository ?: return emptyList()
- val cardCurrencies = currenciesRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed).toMutableList()
+ val cardCurrencies = currenciesRepository
+ .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed).toMutableList()
val blockchainsToDerive = cardCurrencies.ifEmpty {
mutableListOf(
BlockchainNetwork(Blockchain.Bitcoin, card),
- BlockchainNetwork(Blockchain.Ethereum, card)
+ BlockchainNetwork(Blockchain.Ethereum, card),
)
}
@@ -244,7 +235,7 @@ private class ScanWalletProcessor(
blockchainsToDerive.addAll(
listOf(
BlockchainNetwork(Blockchain.Ethereum, card),
- BlockchainNetwork(Blockchain.EthereumTestnet, card)
+ BlockchainNetwork(Blockchain.EthereumTestnet, card),
)
)
}
@@ -301,52 +292,44 @@ private class ScanTwinProcessor : ProductCommandProcessor {
is CompletionResult.Success -> {
val publicKey = card.getSingleWallet()?.publicKey
if (publicKey == null) {
- callback(
- CompletionResult.Success(
- ScanResponse(
- card,
- ProductType.Twins,
- null
- )
- )
- )
- return@run
- }
- val verified =
- TwinsHelper.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey)
- if (verified) {
- val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
- val walletData = session.environment.walletData
val response = ScanResponse(
- card,
- ProductType.Twins,
- walletData,
- twinPublicKey.toHexString()
+ card = card,
+ productType = ProductType.Twins,
+ walletData = null,
)
callback(CompletionResult.Success(response))
+ return@run
+ }
+
+ val verified = TwinsHelper.verifyTwinPublicKey(readDataResult.data.issuerData, publicKey)
+ val response = if (verified) {
+ val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
+ val walletData = session.environment.walletData
+ ScanResponse(
+ card = card,
+ productType = ProductType.Twins,
+ walletData = walletData,
+ secondTwinPublicKey = twinPublicKey.toHexString(),
+ )
} else {
- callback(
- CompletionResult.Success(
- ScanResponse(
- card,
- ProductType.Twins,
- null
- )
- )
+ ScanResponse(
+ card = card,
+ productType = ProductType.Twins,
+ walletData = null,
)
}
+ callback(CompletionResult.Success(response))
}
- is CompletionResult.Failure ->
+ is CompletionResult.Failure -> {
callback(CompletionResult.Success(ScanResponse(card, ProductType.Twins, null)))
+ }
}
}
}
-
}
fun Card.getCurvesForNonCreatedWallets(): List {
val curvesPresent = wallets.map { it.curve }.toSet()
- val curvesForNonCreatedWallets = supportedCurves
- .subtract(curvesPresent + EllipticCurve.Secp256r1)
+ val curvesForNonCreatedWallets = supportedCurves.subtract(curvesPresent + EllipticCurve.Secp256r1)
return curvesForNonCreatedWallets.toList()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt b/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt
index 032c0b97ca..f05065bc8d 100644
--- a/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt
+++ b/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt
@@ -4,16 +4,17 @@ import android.content.res.Resources
import android.net.Uri
import androidx.core.os.ConfigurationCompat
import com.tangem.common.card.Card
-import java.util.*
+import java.util.Locale
/**
[REDACTED_AUTHOR]
*/
class CardTou {
- private val locale: Locale = ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)
+ private val locale: Locale =
+ ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)!!
fun getUrl(card: Card): Uri? {
- val issuerName = card.issuer.name ?: return null
+ val issuerName = card.issuer.name
if (issuerName.lowercase(Locale.getDefault()) != "start2coin") return null
val baseUrl = "https://app.tangem.com/tou/"
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt
index fe8a593787..b366229775 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt
@@ -1,11 +1,13 @@
package com.tangem.tap.domain.walletconnect
import com.github.salomonbrys.kotson.fromJson
+import com.github.salomonbrys.kotson.toMap
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonParser
import com.google.gson.annotations.SerializedName
+import com.tangem.blockchain.common.Blockchain
import com.trustwallet.walletconnect.JSONRPC_VERSION
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
@@ -18,9 +20,11 @@ class EthSignHelper {
.create()
}
- fun tryToParseEthTypedMessage(data: String): WCEthereumSignMessage? {
- val request =
- gson.fromJson(data)
+ fun parseCustomRequest(data: String): CustomJsonRpcRequest {
+ return gson.fromJson(data)
+ }
+
+ fun tryToParseEthTypedMessage(request: CustomJsonRpcRequest): WCEthereumSignMessage? {
return if (request.method == WCMethodExtended.ETH_SIGN_TYPE_DATA_V4) {
WCEthereumSignMessage(
listOf(
@@ -53,12 +57,26 @@ class EthSignHelper {
}
}
-data class CustomJsonRpcRequests(
+data class CustomJsonRpcRequest(
val id: Long,
val jsonrpc: String = JSONRPC_VERSION,
val method: WCMethodExtended?,
val params: JsonArray
-)
+) {
+
+ fun blockchainFromChainId(): Blockchain? {
+ return try {
+ val hex = params[0].asJsonObject.toMap()[CHAIN_ID_KEY]?.asString ?: ""
+ Blockchain.fromChainId(Integer.decode(hex))
+ } catch (exception: Exception) {
+ null
+ }
+ }
+
+ companion object {
+ const val CHAIN_ID_KEY = "chainId"
+ }
+}
enum class WCMethodExtended {
@SerializedName("wc_sessionRequest")
@@ -95,5 +113,10 @@ enum class WCMethodExtended {
GET_ACCOUNTS,
@SerializedName("trust_signTransaction")
- SIGN_TRANSACTION;
+ SIGN_TRANSACTION,
+
+ @SerializedName("wallet_switchEthereumChain")
+ SWITCH_CHAIN,
+
+ ;
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
index dd94266b06..7e4fae297a 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
@@ -5,20 +5,32 @@ import com.tangem.common.extensions.guard
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.features.details.redux.walletconnect.*
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
+import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
+import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData
+import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData
+import com.tangem.tap.features.details.redux.walletconnect.WcTransactionType
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.walletConnectRepository
import com.trustwallet.walletconnect.WCClient
import com.trustwallet.walletconnect.models.WCPeerMeta
-import com.trustwallet.walletconnect.models.binance.*
+import com.trustwallet.walletconnect.models.binance.WCBinanceCancelOrder
+import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
+import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
+import com.trustwallet.walletconnect.models.binance.WCBinanceTxConfirmParam
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
import com.trustwallet.walletconnect.models.session.WCSession
import com.trustwallet.walletconnect.models.session.WCSessionUpdate
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-import okhttp3.*
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.util.*
@@ -71,6 +83,22 @@ class WalletConnectManager {
}
}
+ fun updateBlockchain(session: WalletConnectSession) {
+ sessions[session.session]?.client?.updateSession(
+ accounts = listOfNotNull(session.getAddress()),
+ chainId = session.wallet.blockchain?.getChainId(),
+ approved = true
+ )
+
+
+ val updatedSession = sessions[session.session]?.copy(
+ wallet = session.wallet
+ )
+ if (updatedSession != null) {
+ sessions[session.session] = updatedSession
+ }
+ }
+
private fun setupConnectionTimeoutCheck(session: WCSession) {
scope.launch {
delay(20_000)
@@ -405,27 +433,47 @@ class WalletConnectManager {
onSessionClosed(session)
}
}
+ client.onWalletChangeNetwork = { id: Long, chainId: Int ->
+ Timber.d("On WC Switch Chain")
+ val blockchain = Blockchain.fromChainId(chainId)
+ Timber.d("WC switch chainID\nNew Blockchain: $blockchain")
+ val session = sessions[client.session]?.toWalletConnectSession()
+ if (session != null) {
+ store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session))
+ }
+ }
client.onCustomRequest = { id: Long, data: String ->
Timber.d("Custom Request")
Timber.d(data)
+ val request = EthSignHelper.parseCustomRequest(data)
- val message = EthSignHelper.tryToParseEthTypedMessage(data)
- if (message != null) {
- Timber.d("onEthSign_v4: $message")
- store.state.globalState.analyticsHandlers?.logWcEvent(
- Analytics.WcAnalyticsEvent.Action(
- Analytics.WcAction.PersonalSign
+ when (request.method) {
+ WCMethodExtended.ETH_SIGN_TYPE_DATA_V4 -> handleTypedDataV4(request, client, id)
+ else -> {
+ Timber.d("WC: unrecognized custom request")
+ }
+ }
+ }
+
+ }
+
+ private fun handleTypedDataV4(request: CustomJsonRpcRequest, client: WCClient, id: Long) {
+ val message = EthSignHelper.tryToParseEthTypedMessage(request)
+ if (message != null) {
+ Timber.d("onEthSign_v4: $message")
+ store.state.globalState.analyticsHandlers?.logWcEvent(
+ Analytics.WcAnalyticsEvent.Action(
+ Analytics.WcAction.PersonalSign
+ )
+ )
+ sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
+ store.dispatchOnMain(
+ WalletConnectAction.HandlePersonalSignRequest(
+ message,
+ sessionData,
+ id
)
)
- sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
- store.dispatchOnMain(
- WalletConnectAction.HandlePersonalSignRequest(
- message,
- sessionData,
- id
- )
- )
- }
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt
index aba7726098..3da00c517b 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt
@@ -28,11 +28,15 @@ class WalletConnectNetworkUtils {
peer.name.contains("BSC") -> {
Blockchain.BSC
}
+ peer.url.contains("honeyswap.1hive.eth.limo") -> {
+ // Check if something's changed after this bug report:
+ // https://github.com/1Hive/honeyswap-interface/issues/83
+ Blockchain.Gnosis
+ }
else -> {
- Blockchain.Ethereum
+ Blockchain.Ethereum
}
}
}
-
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
index 1d0dc4a9e0..04f1ec8141 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
@@ -5,8 +5,18 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.Companion.toKeccak
-import com.tangem.blockchain.common.*
-import com.tangem.blockchain.extensions.*
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.BlockchainSdkError
+import com.tangem.blockchain.common.CommonSigner
+import com.tangem.blockchain.common.TransactionData
+import com.tangem.blockchain.common.TransactionSender
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.extensions.Result
+import com.tangem.blockchain.extensions.SimpleResult
+import com.tangem.blockchain.extensions.hexToBigDecimal
+import com.tangem.blockchain.extensions.isAscii
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
@@ -15,9 +25,14 @@ import com.tangem.common.extensions.toHexString
import com.tangem.crypto.CryptoUtils
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.Analytics
+import com.tangem.tap.common.extensions.logSendTransactionError
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.toFormattedString
-import com.tangem.tap.features.details.redux.walletconnect.*
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
+import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
+import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData
+import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData
+import com.tangem.tap.features.details.redux.walletconnect.WcTransactionType
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
import com.tangem.tap.store
@@ -56,7 +71,7 @@ class WalletConnectSdkHelper {
(walletManager as? EthereumGasLoader)?.getGasPrice()) {
is Result.Success -> result.data.toBigDecimal()
is Result.Failure -> {
- Timber.e(result.error)
+ (result.error as? Throwable)?.let { Timber.e(it) }
return null
}
null -> return null
@@ -108,9 +123,8 @@ class WalletConnectSdkHelper {
)
val blockchain = session.wallet.getBlockchainForSession()
return factory.makeWalletManager(
- session.wallet.cardId,
- blockchain,
- publicKey
+ blockchain = blockchain,
+ publicKey = publicKey
)
}
@@ -124,20 +138,18 @@ class WalletConnectSdkHelper {
private suspend fun sendTransaction(data: WcTransactionData): String? {
val result = (data.walletManager as TransactionSender).send(
transactionData = data.transaction,
- signer = Signer(tangemSdk)
+ signer = CommonSigner(tangemSdk)
)
return when (result) {
SimpleResult.Success -> {
HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash
}
is SimpleResult.Failure -> {
- (result.error as? TangemSdkError)?.let { error ->
- store.state.globalState.analyticsHandlers?.logCardSdkError(
- error,
- Analytics.ActionToLog.WalletConnectTransaction,
- )
- }
- Timber.e(result.error)
+ store.state.globalState.analyticsHandlers?.logSendTransactionError(
+ result.error,
+ Analytics.ActionToLog.WalletConnectTransaction,
+ )
+ Timber.e(result.error as BlockchainSdkError)
null
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt
new file mode 100644
index 0000000000..72134d880b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt
@@ -0,0 +1,94 @@
+package com.tangem.tap.domain.walletconnect
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.common.WalletManagerFactory
+import com.tangem.common.card.EllipticCurve
+import com.tangem.domain.common.ScanResponse
+import com.tangem.domain.common.TapWorkarounds.isTestCard
+import com.tangem.tap.domain.extensions.getPrimaryCurve
+import com.tangem.tap.domain.extensions.makeWalletManagerForApp
+import com.tangem.tap.domain.tokens.CurrenciesRepository
+import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
+import com.tangem.tap.features.wallet.redux.WalletState
+
+class WcWalletManagerFactory(
+ private val factory: WalletManagerFactory,
+ private val currenciesRepository: CurrenciesRepository,
+ ) {
+
+ suspend fun getWalletManager(
+ wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState
+ ): WalletManager? {
+ val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) {
+ Blockchain.EthereumTestnet
+ } else {
+ blockchain
+ }
+
+ val blockchainNetwork = BlockchainNetwork(
+ blockchain = blockchainToMake,
+ derivationPath = wallet.derivationPath?.rawPath,
+ tokens = emptyList()
+ )
+
+ return if (walletState.cardId == wallet.cardId) {
+ walletState.getWalletManager(blockchainNetwork)
+ } else {
+ val blockchainNetworkWithTokens = currenciesRepository
+ .loadSavedCurrencies(
+ cardId = wallet.cardId,
+ isHdWalletSupported = wallet.derivationPath != null
+ ).firstOrNull { it == blockchainNetwork }
+
+ if (blockchainNetworkWithTokens != null) {
+ factory.makeWalletManager(
+ blockchain = blockchainToMake,
+ publicKey = Wallet.PublicKey(
+ wallet.walletPublicKey!!,
+ wallet.derivedPublicKey,
+ wallet.derivationPath
+ ),
+ tokens = blockchainNetworkWithTokens.tokens,
+ curve = blockchainToMake.getPrimaryCurve() ?: EllipticCurve.Secp256k1
+ )
+ } else {
+ null
+ }
+ }
+ }
+
+ suspend fun getWalletManager(
+ scanResponse: ScanResponse, blockchain: Blockchain, walletState: WalletState
+ ): WalletManager? {
+ val card = scanResponse.card
+ val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
+ Blockchain.EthereumTestnet
+ } else {
+ blockchain
+ }
+
+ val blockchainNetwork = BlockchainNetwork(
+ blockchain = blockchainToMake,
+ card = card
+ )
+
+ return if (walletState.cardId == card.cardId) {
+ walletState.getWalletManager(blockchainNetwork)
+ } else {
+ if (currenciesRepository
+ .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
+ .contains(blockchainNetwork)
+ ) {
+ factory.makeWalletManagerForApp(
+ scanResponse = scanResponse,
+ blockchainNetwork = blockchainNetwork
+ )
+ } else {
+ null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
index 2a38c4e657..650ce5ea09 100644
--- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
+++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
@@ -1,13 +1,20 @@
package com.tangem.tap.features.demo
import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager
-import com.tangem.blockchain.common.*
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.TransactionData
+import com.tangem.blockchain.common.TransactionSender
+import com.tangem.blockchain.common.TransactionSigner
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.common.toBlockchainSdkError
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.CompletionResult
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.wallet.redux.WalletAction
@@ -36,7 +43,8 @@ object DemoHelper {
WalletAction.TradeCryptoAction.Buy::class.java,
WalletAction.TradeCryptoAction.Sell::class.java,
BackupAction.StartBackup::class.java,
- WalletAction.ExploreAddress::class.java
+ WalletAction.ExploreAddress::class.java,
+ DetailsAction.ResetToFactory.Start::class.java,
)
fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId)
@@ -471,9 +479,12 @@ class DemoTransactionSender(
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
val dataToSign = randomString(32).toByteArray()
- val signerResponse = signer.sign(dataToSign, walletManager.wallet.cardId, walletManager.wallet.publicKey)
+ val signerResponse = signer.sign(
+ hash = dataToSign,
+ publicKey = walletManager.wallet.publicKey
+ )
return when (signerResponse) {
- is CompletionResult.Success -> SimpleResult.Failure(Exception(ID))
+ is CompletionResult.Success -> SimpleResult.Failure(Exception(ID).toBlockchainSdkError())
is CompletionResult.Failure -> SimpleResult.fromTangemSdkError(signerResponse.error)
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt
index 92551d6411..87af003060 100644
--- a/app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt
+++ b/app/src/main/java/com/tangem/tap/features/demo/DemoMiddlewares.kt
@@ -5,7 +5,7 @@ import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.scope
import com.tangem.tap.store
diff --git a/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt b/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt
index 99ecfbc227..66d09bdbf0 100644
--- a/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt
+++ b/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt
@@ -1,10 +1,10 @@
package com.tangem.tap.features.demo
-import com.tangem.blockchain.common.WalletManager
+import com.tangem.common.card.Card
import com.tangem.domain.common.ScanResponse
/**
[REDACTED_AUTHOR]
*/
fun ScanResponse.isDemoCard(): Boolean = DemoHelper.isDemoCardId(card.cardId)
-fun WalletManager.isDemoWallet(): Boolean = DemoHelper.isDemoCardId(wallet.cardId)
\ No newline at end of file
+fun Card.isDemoCard(): Boolean = DemoHelper.isDemoCardId(cardId)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
index c37a625c54..b421b69730 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt
@@ -3,12 +3,8 @@ package com.tangem.tap.features.details.redux
import com.tangem.blockchain.common.Wallet
import com.tangem.common.card.Card
import com.tangem.domain.common.ScanResponse
-import com.tangem.domain.common.TwinCardNumber
-import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.tap.common.entities.FiatCurrency
-import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.domain.termsOfUse.CardTou
-import com.tangem.wallet.R
import org.rekotlin.Action
sealed class DetailsAction : Action {
@@ -20,31 +16,24 @@ sealed class DetailsAction : Action {
) : DetailsAction()
object ShowDisclaimer : DetailsAction()
- data class ReCreateTwinsWallet(val number: TwinCardNumber) : DetailsAction()
+ object ReCreateTwinsWallet : DetailsAction()
sealed class ResetToFactory : DetailsAction() {
- object Check : ResetToFactory()
- object Proceed : ResetToFactory() {
- object NotAllowedByCard : ResetToFactory(), NotificationAction {
- override val messageResource = R.string.error_purge_prohibited
- }
-
- object NotEmpty : ResetToFactory(), NotificationAction {
- override val messageResource = R.string.details_notification_erase_wallet_not_possible
- }
- }
-
- object Confirm : ResetToFactory()
- object Cancel : ResetToFactory()
+ object Start : ResetToFactory()
+ object Proceed : ResetToFactory()
+ data class Confirm(val confirmed: Boolean) : ResetToFactory()
object Failure : ResetToFactory()
object Success : ResetToFactory()
}
object CreateBackup : DetailsAction()
+ object ScanCard : DetailsAction()
+
+ data class PrepareCardSettingsData(val card: Card) : DetailsAction()
+ object ResetCardSettingsData : DetailsAction()
+
sealed class ManageSecurity : DetailsAction() {
- data class CheckCurrentSecurityOption(val card: Card) : ManageSecurity()
- data class SetCurrentOption(val userCodes: CheckUserCodesResponse) : ManageSecurity()
object OpenSecurity : ManageSecurity()
data class SelectOption(val option: SecurityOption) : ManageSecurity()
object SaveChanges : ManageSecurity() {
@@ -52,11 +41,12 @@ sealed class DetailsAction : Action {
object Failure : ManageSecurity()
}
- data class ConfirmSelection(val option: SecurityOption) : ManageSecurity() {
- object AlreadySet : ManageSecurity(), NotificationAction {
- override val messageResource = R.string.details_notification_security_option_already_active
- }
- }
+ object ChangeAccessCode : ManageSecurity()
+ }
+
+ sealed class AppSettings : DetailsAction() {
+ data class SwitchPrivacySetting(val enable: Boolean, val setting: PrivacySetting) :
+ AppSettings()
}
data class ChangeAppCurrency(val fiatCurrency: FiatCurrency) : DetailsAction()
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
index efed01f9c3..7825d58a8f 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
@@ -1,9 +1,8 @@
package com.tangem.tap.features.details.redux
import com.tangem.common.CompletionResult
-import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.TangemSdkError
-import com.tangem.operations.pins.CheckUserCodesResponse
+import com.tangem.domain.common.isTangemTwins
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.extensions.dispatchNotification
@@ -13,7 +12,7 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
-import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
+import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransactions
@@ -23,44 +22,74 @@ import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import org.rekotlin.Action
import org.rekotlin.Middleware
class DetailsMiddleware {
private val eraseWalletMiddleware = EraseWalletMiddleware()
private val manageSecurityMiddleware = ManageSecurityMiddleware()
- val detailsMiddleware: Middleware = { _, _ ->
+ private val managePrivacyMiddleware = ManagePrivacyMiddleware()
+ val detailsMiddleware: Middleware = { _, state ->
{ next ->
{ action ->
- when (action) {
- is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action)
- is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action)
- is DetailsAction.ShowDisclaimer -> {
- store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer)
- store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
+ handleAction(state, action)
+ next(action)
+ }
+ }
+ }
+
+ private fun handleAction(state: () -> AppState?, action: Action) {
+ if (DemoHelper.tryHandle(state, action)) return
+
+ when (action) {
+ is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action)
+ is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action)
+ is DetailsAction.AppSettings -> managePrivacyMiddleware.handle(action)
+ is DetailsAction.ShowDisclaimer -> {
+ val uri = store.state.detailsState.cardTermsOfUseUrl
+ if (uri != null) {
+ store.dispatch(NavigationAction.OpenDocument(uri))
+ }
+ }
+ is DetailsAction.ReCreateTwinsWallet -> {
+ val wallet =
+ store.state.walletState.walletManagers.map { it.wallet }.firstOrNull()
+ if (wallet == null) {
+ store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
+ } else {
+ if (wallet.hasSendableAmountsOrPendingTransactions()) {
+ val walletIsNotEmpty =
+ store.state.globalState.resources.strings.walletIsNotEmpty
+ store.dispatchNotification(walletIsNotEmpty)
+ } else {
+ store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
}
- is DetailsAction.ReCreateTwinsWallet -> {
- val wallet = store.state.walletState.walletManagers.map { it.wallet }.firstOrNull()
- if (wallet == null) {
- store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
- store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
- } else {
- if (wallet.hasSendableAmountsOrPendingTransactions()) {
- val walletIsNotEmpty = store.state.globalState.resources.strings.walletIsNotEmpty
- store.dispatchNotification(walletIsNotEmpty)
- } else {
- store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
- store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
- }
+ }
+ }
+ is DetailsAction.CreateBackup -> {
+ store.state.detailsState.scanResponse?.let {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
+ store.dispatch(
+ GlobalAction.Onboarding.Start(
+ it,
+ fromHomeScreen = false,
+ ),
+ )
+ }
+ }
+ DetailsAction.ScanCard -> {
+ scope.launch {
+ when (val result = tangemSdkManager.scanCard()) {
+ is CompletionResult.Success -> {
+ val card = result.data
+ store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card))
}
- }
- is DetailsAction.CreateBackup -> {
- store.state.detailsState.scanResponse?.let {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
- store.dispatch(GlobalAction.Onboarding.Start(it, fromHomeScreen = false))
+ is CompletionResult.Failure -> {
}
}
}
- next(action)
}
}
}
@@ -68,23 +97,15 @@ class DetailsMiddleware {
class EraseWalletMiddleware {
fun handle(action: DetailsAction.ResetToFactory) {
when (action) {
+ is DetailsAction.ResetToFactory.Start -> {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.ResetToFactory))
+ }
is DetailsAction.ResetToFactory.Proceed -> {
- when (store.state.detailsState.eraseWalletState) {
- EraseWalletState.Allowed ->
- store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm))
- EraseWalletState.NotAllowedByCard ->
- store.dispatch(DetailsAction.ResetToFactory.Proceed.NotAllowedByCard)
- EraseWalletState.NotEmpty ->
- store.dispatch(DetailsAction.ResetToFactory.Proceed.NotEmpty)
- else -> { /* no-op */
- }
+ val card = store.state.detailsState.cardSettingsState?.card ?: return
+ if (card.isTangemTwins()) {
+ store.dispatch(DetailsAction.ReCreateTwinsWallet)
+ return
}
- }
- is DetailsAction.ResetToFactory.Cancel -> {
- store.dispatch(NavigationAction.PopBackTo())
- }
- is DetailsAction.ResetToFactory.Confirm -> {
- val card = store.state.detailsState.scanResponse?.card ?: return
scope.launch {
val result = tangemSdkManager.resetToFactorySettings(card)
withContext(Dispatchers.Main) {
@@ -98,7 +119,7 @@ class DetailsMiddleware {
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
Analytics.ActionToLog.PurgeWallet,
- card = store.state.detailsState.scanResponse?.card
+ card = store.state.detailsState.scanResponse?.card,
)
}
}
@@ -115,44 +136,13 @@ class DetailsMiddleware {
class ManageSecurityMiddleware {
fun handle(action: DetailsAction.ManageSecurity) {
when (action) {
- is DetailsAction.ManageSecurity.CheckCurrentSecurityOption -> {
- if (action.card.firmwareVersion >= FirmwareVersion.IsAccessCodeStatusAvailable) {
- // for a card that meets this condition, we can get these statuses from it
- val simulatedResponse = CheckUserCodesResponse(
- action.card.isAccessCodeSet, action.card.isPasscodeSet ?: false
- )
- store.dispatch(DetailsAction.ManageSecurity.SetCurrentOption(simulatedResponse))
- store.dispatch(DetailsAction.ManageSecurity.OpenSecurity)
- } else {
- scope.launch {
- when (val response = tangemSdkManager.checkUserCodes(action.card.cardId)) {
- is CompletionResult.Success -> {
- store.dispatchOnMain(DetailsAction.ManageSecurity.SetCurrentOption(response.data))
- store.dispatchOnMain(DetailsAction.ManageSecurity.OpenSecurity)
- }
- is CompletionResult.Failure -> {
- }
- }
- }
- }
- }
is DetailsAction.ManageSecurity.OpenSecurity -> {
store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity))
}
- is DetailsAction.ManageSecurity.ConfirmSelection -> {
- if (action.option != store.state.detailsState.securityScreenState?.currentOption) {
- if (action.option == SecurityOption.LongTap) {
- store.dispatch(DetailsAction.ManageSecurity.SaveChanges)
- } else {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsConfirm))
- }
- } else {
- store.dispatch(DetailsAction.ManageSecurity.ConfirmSelection.AlreadySet)
- }
- }
is DetailsAction.ManageSecurity.SaveChanges -> {
val cardId = store.state.detailsState.scanResponse?.card?.cardId
- val selectedOption = store.state.detailsState.securityScreenState?.selectedOption
+ val selectedOption =
+ store.state.detailsState.cardSettingsState?.manageSecurityState?.selectedOption
scope.launch {
val result = when (selectedOption) {
SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId)
@@ -166,9 +156,7 @@ class DetailsMiddleware {
selectedOption?.let {
store.dispatch(GlobalAction.UpdateSecurityOptions(it))
}
- if (selectedOption != SecurityOption.LongTap) {
- store.dispatch(NavigationAction.PopBackTo())
- }
+ store.dispatch(NavigationAction.PopBackTo())
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success)
}
is CompletionResult.Failure -> {
@@ -178,9 +166,9 @@ class DetailsMiddleware {
actionToLog = Analytics.ActionToLog.ChangeSecOptions,
parameters = mapOf(
AnalyticsParam.NEW_SECURITY_OPTION to
- (selectedOption?.name ?: "")
+ (selectedOption?.name ?: ""),
),
- card = store.state.detailsState.scanResponse?.card
+ card = store.state.detailsState.scanResponse?.card,
)
}
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure)
@@ -191,9 +179,25 @@ class DetailsMiddleware {
}
}
}
+ is DetailsAction.ManageSecurity.ChangeAccessCode -> {
+ val card = store.state.detailsState.cardSettingsState?.card ?: return
+ scope.launch {
+ tangemSdkManager.setAccessCode(card.cardId)
+ }
+ }
else -> { /* no-op */
}
}
}
}
-}
\ No newline at end of file
+
+ class ManagePrivacyMiddleware {
+ fun handle(action: DetailsAction.AppSettings) {
+ when (action) {
+ is DetailsAction.AppSettings.SwitchPrivacySetting -> {
+// TODO()
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
index a095d46d7e..b327e24452 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
@@ -1,13 +1,14 @@
package com.tangem.tap.features.details.redux
-
import com.tangem.common.card.Card
+import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
+import com.tangem.domain.common.TapWorkarounds.isTangemNote
import com.tangem.domain.common.isTangemTwin
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.extensions.isWalletDataSupported
import com.tangem.tap.domain.extensions.signedHashesCount
-import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransactions
+import com.tangem.tap.store
import org.rekotlin.Action
import java.util.*
@@ -19,69 +20,95 @@ class DetailsReducer {
private fun internalReduce(action: Action, state: AppState): DetailsState {
if (action !is DetailsAction) return state.detailsState
-
val detailsState = state.detailsState
return when (action) {
is DetailsAction.PrepareScreen -> {
- handlePrepareScreen(action, detailsState)
+ handlePrepareScreen(action)
}
+ is DetailsAction.PrepareCardSettingsData -> {
+ handlePrepareCardSettingsScreen(action.card, detailsState)
+ }
+ is DetailsAction.ResetCardSettingsData -> detailsState.copy(cardSettingsState = null)
is DetailsAction.ResetToFactory -> {
handleEraseWallet(action, detailsState)
}
is DetailsAction.ManageSecurity -> {
handleSecurityAction(action, detailsState)
}
+ is DetailsAction.AppSettings -> {
+ handlePrivacyAction(action, detailsState)
+ }
is DetailsAction.ChangeAppCurrency ->
detailsState.copy(appCurrency = action.fiatCurrency)
-
else -> detailsState
}
}
private fun handlePrepareScreen(
action: DetailsAction.PrepareScreen,
- state: DetailsState,
): DetailsState {
-
return DetailsState(
scanResponse = action.scanResponse,
wallets = action.wallets,
- cardInfo = action.scanResponse.card.toCardInfo(),
cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card),
createBackupAllowed = action.scanResponse.card.backupStatus == Card.BackupStatus.NoBackup,
+ appCurrency = store.state.globalState.appCurrency,
)
}
+private fun handlePrepareCardSettingsScreen(card: Card, state: DetailsState): DetailsState {
+ val cardSettingsState = CardSettingsState(
+ cardInfo = card.toCardInfo(),
+ manageSecurityState = prepareSecurityOptions(card),
+ card = card,
+ resetCardAllowed = isResetToFactoryAllowedByCard(card),
+ )
+ return state.copy(cardSettingsState = cardSettingsState)
+}
+
+private fun prepareSecurityOptions(card: Card): ManageSecurityState {
+ val securityOption = when {
+ card.isAccessCodeSet -> {
+ SecurityOption.AccessCode
+ }
+ card.isPasscodeSet == true -> {
+ SecurityOption.PassCode
+ }
+ else -> {
+ SecurityOption.LongTap
+ }
+ }
+ val allowedSecurityOptions = when {
+ card.isStart2Coin || card.isTangemNote() -> {
+ EnumSet.of(SecurityOption.LongTap)
+ }
+ card.settings.isBackupAllowed -> {
+ EnumSet.of(securityOption)
+ }
+ else -> prepareAllowedSecurityOptions(card, securityOption)
+ }
+ return ManageSecurityState(
+ currentOption = securityOption,
+ allowedOptions = allowedSecurityOptions,
+ selectedOption = securityOption,
+ )
+}
+
+private fun isResetToFactoryAllowedByCard(card: Card): Boolean {
+ val notAllowedByAnyWallet = card.wallets.any { it.settings.isPermanent }
+ val notAllowedByCard = notAllowedByAnyWallet ||
+ (card.isWalletDataSupported && (!card.isTangemNote() && !card.settings.isBackupAllowed)) ||
+ card.isSaltPay
+ return !notAllowedByCard
+}
+
private fun handleEraseWallet(
action: DetailsAction.ResetToFactory,
state: DetailsState,
): DetailsState {
return when (action) {
- DetailsAction.ResetToFactory.Check -> {
- val card = state.scanResponse?.card
- val notAllowedByAnyWallet = card?.wallets?.any { it.settings.isPermanent } ?: false
- val notAllowedByCard = notAllowedByAnyWallet ||
- (card?.isWalletDataSupported == true &&
- (!state.scanResponse.isTangemNote() && !state.scanResponse.supportsBackup()))
-
- val notEmpty = state.wallets.any { it.hasSendableAmountsOrPendingTransactions() }
- val eraseWalletState = when {
- notAllowedByCard -> EraseWalletState.NotAllowedByCard
- notEmpty -> EraseWalletState.NotEmpty
- else -> EraseWalletState.Allowed
- }
- state.copy(eraseWalletState = eraseWalletState)
- }
- DetailsAction.ResetToFactory.Proceed -> {
- if (state.eraseWalletState == EraseWalletState.Allowed) {
- state.copy(confirmScreenState = ConfirmScreenState.EraseWallet)
- } else {
- state
- }
- }
- DetailsAction.ResetToFactory.Cancel -> state.copy(eraseWalletState = null)
- DetailsAction.ResetToFactory.Failure -> state.copy(eraseWalletState = null)
- DetailsAction.ResetToFactory.Success -> state.copy(eraseWalletState = null)
+ is DetailsAction.ResetToFactory.Confirm ->
+ state.copy(cardSettingsState = state.cardSettingsState?.copy(resetConfirmed = action.confirmed))
else -> state
}
}
@@ -90,68 +117,45 @@ private fun handleSecurityAction(
action: DetailsAction.ManageSecurity, state: DetailsState,
): DetailsState {
return when (action) {
- is DetailsAction.ManageSecurity.SetCurrentOption -> {
- val securityOption = when {
- action.userCodes.isAccessCodeSet -> {
- SecurityOption.AccessCode
- }
- action.userCodes.isPasscodeSet -> {
- SecurityOption.PassCode
- }
- else -> {
- SecurityOption.LongTap
- }
- }
- state.copy(securityScreenState = SecurityScreenState(currentOption = securityOption))
- }
- is DetailsAction.ManageSecurity.OpenSecurity -> {
- val allowedSecurityOptions = when {
- state.scanResponse?.card?.isStart2Coin == true ||
- state.scanResponse?.isTangemNote() == true -> {
- EnumSet.of(SecurityOption.LongTap)
- }
- state.scanResponse?.supportsBackup() == true -> {
- EnumSet.of(state.securityScreenState?.currentOption)
- }
- else -> prepareAllowedSecurityOptions(
- state.scanResponse?.card, state.securityScreenState?.currentOption
- )
- }
- state.copy(securityScreenState = state.securityScreenState?.copy(
- allowedOptions = allowedSecurityOptions,
- selectedOption = state.securityScreenState.currentOption
- ))
- }
is DetailsAction.ManageSecurity.SelectOption -> {
- state.copy(securityScreenState = state.securityScreenState?.copy(
- selectedOption = action.option
- ))
- }
- is DetailsAction.ManageSecurity.ConfirmSelection -> {
- val confirmScreenState = when (action.option) {
- SecurityOption.LongTap -> ConfirmScreenState.LongTap
- SecurityOption.PassCode -> ConfirmScreenState.PassCode
- SecurityOption.AccessCode -> ConfirmScreenState.AccessCode
- }
- state.copy(confirmScreenState = confirmScreenState)
+ val manageSecurityState = state.cardSettingsState?.manageSecurityState?.copy(
+ selectedOption = action.option,
+ )
+ state.copy(cardSettingsState = state.cardSettingsState?.copy(manageSecurityState = manageSecurityState))
}
is DetailsAction.ManageSecurity.SaveChanges.Success -> {
// Setting options to show only LongTap from now on for non-twins
+ val manageSecurityState = state.cardSettingsState?.manageSecurityState?.copy(
+ currentOption = state.cardSettingsState.manageSecurityState.selectedOption,
+ allowedOptions = state.scanResponse?.card?.let {
+ prepareAllowedSecurityOptions(
+ it, state.cardSettingsState.manageSecurityState.selectedOption,
+ )
+ } ?: EnumSet.of(SecurityOption.LongTap),
+ )
state.copy(
- securityScreenState = state.securityScreenState?.copy(
- currentOption = state.securityScreenState.selectedOption,
- allowedOptions = state.scanResponse?.card?.let {
- prepareAllowedSecurityOptions(
- it, state.securityScreenState.selectedOption
- )
- } ?: EnumSet.of(SecurityOption.LongTap)
- ))
+ cardSettingsState = state.cardSettingsState?.copy(
+ manageSecurityState = manageSecurityState,
+ ),
+ )
}
-
else -> state
}
}
+private fun handlePrivacyAction(
+ action: DetailsAction.AppSettings, state: DetailsState,
+): DetailsState {
+ return when (action) {
+ is DetailsAction.AppSettings.SwitchPrivacySetting -> {
+ when (action.setting) {
+ PrivacySetting.SaveWallets -> state.copy(saveWallets = action.enable)
+ PrivacySetting.SaveAccessCode -> state.copy(saveAccessCodes = action.enable)
+ }
+ }
+ }
+}
+
private fun prepareAllowedSecurityOptions(
card: Card?, currentSecurityOption: SecurityOption?,
): EnumSet {
@@ -169,7 +173,6 @@ private fun prepareAllowedSecurityOptions(
return allowedSecurityOptions
}
-
private fun Card.toCardInfo(): CardInfo {
val cardId = this.cardId.chunked(4).joinToString(separator = " ")
val issuer = this.issuer.name
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
index ee6fd2bb7e..8e02856097 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.features.details.redux
import android.net.Uri
import com.tangem.blockchain.common.Wallet
+import com.tangem.common.card.Card
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.entities.FiatCurrency
@@ -14,13 +15,13 @@ import kotlin.properties.ReadOnlyProperty
data class DetailsState(
val scanResponse: ScanResponse? = null,
val wallets: List = emptyList(),
- val cardInfo: CardInfo? = null,
- val eraseWalletState: EraseWalletState? = null,
- val confirmScreenState: ConfirmScreenState? = null,
- val securityScreenState: SecurityScreenState? = null,
+ val cardSettingsState: CardSettingsState? = null,
val cardTermsOfUseUrl: Uri? = null,
+ val privacyPolicyUrl: String? = null,
val createBackupAllowed: Boolean = false,
- val appCurrency: FiatCurrency = FiatCurrency.Default
+ val appCurrency: FiatCurrency = FiatCurrency.Default,
+ val saveWallets: Boolean = true,
+ val saveAccessCodes: Boolean = true,
) : StateType {
// if you do not delegate - the application crashes on startup,
@@ -39,13 +40,23 @@ data class CardInfo(
val signedHashes: Int,
)
-enum class EraseWalletState { Allowed, NotAllowedByCard, NotEmpty }
-enum class ConfirmScreenState { EraseWallet, LongTap, AccessCode, PassCode }
-data class SecurityScreenState(
+data class CardSettingsState(
+ val cardInfo: CardInfo,
+ val card: Card,
+ val manageSecurityState: ManageSecurityState?,
+ val resetCardAllowed: Boolean,
+ val resetConfirmed: Boolean = false,
+)
+
+data class ManageSecurityState(
val currentOption: SecurityOption = SecurityOption.LongTap,
val selectedOption: SecurityOption = currentOption,
val allowedOptions: EnumSet = EnumSet.allOf(SecurityOption::class.java),
val buttonProceed: Button = Button(true),
)
-enum class SecurityOption { LongTap, PassCode, AccessCode }
\ No newline at end of file
+enum class SecurityOption { LongTap, PassCode, AccessCode }
+
+enum class PrivacySetting {
+ SaveWallets, SaveAccessCode
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
index a180d20d4a..14a871fbd9 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
@@ -1,7 +1,6 @@
package com.tangem.tap.features.details.redux.walletconnect
-import android.app.Activity
-import com.tangem.domain.common.ScanResponse
+import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.wallet.R
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
@@ -18,25 +17,21 @@ sealed class WalletConnectAction : Action {
object RestoreSessions : WalletConnectAction()
data class StartWalletConnect(
- val activity: Activity,
+ val copiedUri: String?,
) : WalletConnectAction()
data class ShowClipboardOrScanQrDialog(val wcUri: String) : WalletConnectAction()
-
object UnsupportedCard : WalletConnectAction()
-
data class OpenSession(
val wcUri: String,
) : WalletConnectAction()
- data class AddScanResponse(
- val scanResponse: ScanResponse
- ): WalletConnectAction()
+ data class SetNewSessionData(
+ val newSession: NewWcSessionData,
+ ) : WalletConnectAction()
object RefuseOpeningSession : WalletConnectAction()
-
data class OpeningSessionTimeout(val session: WCSession) : WalletConnectAction()
-
data class ScanCard(
val session: WalletConnectSession, val chainId: Int?,
) : WalletConnectAction()
@@ -47,8 +42,18 @@ sealed class WalletConnectAction : Action {
data class Success(val session: WalletConnectSession) : WalletConnectAction()
}
- data class FailureEstablishingSession(val session: WCSession?) : WalletConnectAction()
+ data class SwitchBlockchain(
+ val blockchain: Blockchain?,
+ val session: WalletConnectSession,
+ ) : WalletConnectAction()
+ data class SelectNetwork(val session: WalletConnectSession, val networks: List) : WalletConnectAction()
+ data class ChooseNetwork(val blockchain: Blockchain) : WalletConnectAction()
+ data class UpdateBlockchain(
+ val updatedSession: WalletConnectSession,
+ ) : WalletConnectAction()
+
+ data class FailureEstablishingSession(val session: WCSession?) : WalletConnectAction()
data class SetSessionsRestored(val sessions: List) :
WalletConnectAction()
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
index 8dbaafc988..d98fb0b0f5 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
@@ -1,38 +1,35 @@
package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.DerivationParams
-import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.common.DerivationStyle
+import com.tangem.common.card.Card
import com.tangem.common.extensions.guard
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.derivationStyle
-import com.tangem.domain.common.TapWorkarounds.isTestCard
+import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.extensions.dispatchOnMain
-import com.tangem.tap.common.extensions.getFromClipboard
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
-import com.tangem.tap.domain.extensions.makeWalletManagerForApp
-import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.domain.walletconnect.BnbHelper
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
+import com.tangem.tap.domain.walletconnect.WcWalletManagerFactory
import com.tangem.tap.features.demo.DemoHelper
-import com.tangem.tap.features.wallet.redux.Currency
-import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
+import timber.log.Timber
class WalletConnectMiddleware {
private val walletConnectManager = WalletConnectManager()
-
val walletConnectMiddleware: Middleware = { dispatch, state ->
{ next ->
{ action ->
@@ -57,20 +54,36 @@ class WalletConnectMiddleware {
}
}
is WalletConnectAction.StartWalletConnect -> {
- val uri = action.activity.getFromClipboard()?.toString()
+ val uri = action.copiedUri
if (uri != null && WalletConnectManager.isCorrectWcUri(uri)) {
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
} else {
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.QrScan))
}
}
+ is WalletConnectAction.SelectNetwork -> {
+ store.dispatch(
+ GlobalAction.ShowDialog(
+ WalletConnectDialog.ChooseNetwork(
+ session = action.session,
+ networks = action.networks,
+ ),
+ ),
+ )
+ }
+ is WalletConnectAction.ChooseNetwork -> {
+ val data = state()?.walletConnectState?.newSessionData ?: return
+ scope.launch {
+ prepareWalletManager(data.scanResponse, store.state.walletState, action.blockchain, data.session)
+ }
+ }
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
store.dispatchOnMain(
GlobalAction.ShowDialog(
WalletConnectDialog.ClipboardOrScanQr(
- action.wcUri
- )
- )
+ action.wcUri,
+ ),
+ ),
)
}
is WalletConnectAction.OpeningSessionTimeout -> {
@@ -92,8 +105,8 @@ class WalletConnectMiddleware {
is WalletConnectAction.RefuseOpeningSession -> {
store.dispatch(
GlobalAction.ShowDialog(
- WalletConnectDialog.OpeningSessionRejected
- )
+ WalletConnectDialog.OpeningSessionRejected,
+ ),
)
}
is WalletConnectAction.ScanCard -> {
@@ -110,14 +123,14 @@ class WalletConnectMiddleware {
transaction = action.transaction,
session = action.session,
id = action.id,
- type = action.type
+ type = action.type,
)
}
is WalletConnectAction.HandlePersonalSignRequest -> {
walletConnectManager.handlePersonalSignRequest(
message = action.message,
session = action.session,
- id = action.id
+ id = action.id,
)
}
is WalletConnectAction.RejectRequest -> {
@@ -138,9 +151,9 @@ class WalletConnectMiddleware {
session = action.sessionData.session,
sessionId = action.id,
cardId = action.sessionData.wallet.cardId,
- dAppName = action.sessionData.peerMeta.name
- )
- )
+ dAppName = action.sessionData.peerMeta.name,
+ ),
+ ),
)
}
is WalletConnectAction.BinanceTransaction.Transfer -> {
@@ -152,126 +165,166 @@ class WalletConnectMiddleware {
session = action.sessionData.session,
sessionId = action.id,
cardId = action.sessionData.wallet.cardId,
- dAppName = action.sessionData.peerMeta.name
- )
- )
+ dAppName = action.sessionData.peerMeta.name,
+ ),
+ ),
)
}
is WalletConnectAction.BinanceTransaction.Sign -> {
walletConnectManager.signBnb(
- action.id, action.data, action.sessionData
+ action.id, action.data, action.sessionData,
)
}
+ is WalletConnectAction.SwitchBlockchain -> {
+ if (action.session.wallet.derivationStyle == DerivationStyle.LEGACY) {
+ Timber.d("Cannot switch chains on AC01/AC02 wallets")
+ return
+ }
+ val blockchain = action.blockchain.guard {
+ store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork))
+ return
+ }
+ val factory = WcWalletManagerFactory(
+ factory = store.state.globalState.tapWalletManager.walletManagerFactory,
+ currenciesRepository = currenciesRepository,
+ )
+ val walletState = store.state.walletState
+ scope.launch {
+ val walletManager = factory.getWalletManager(
+ wallet = action.session.wallet,
+ blockchain = blockchain,
+ walletState = walletState,
+ ).guard {
+ store.dispatchOnMain(
+ GlobalAction.ShowDialog(
+ WalletConnectDialog.AddNetwork(blockchain.fullName),
+ ),
+ )
+ return@launch
+ }
+ val updatedWallet = action.session.wallet.copy(
+ walletPublicKey = walletManager.wallet.publicKey.seedKey,
+ derivedPublicKey = walletManager.wallet.publicKey.derivedKey,
+ derivationPath = walletManager.wallet.publicKey.derivationPath,
+ blockchain = action.blockchain,
+ )
+ val updatedSession = action.session.copy(wallet = updatedWallet)
+ store.dispatchOnMain(WalletConnectAction.UpdateBlockchain(updatedSession))
+ }
+ }
+ is WalletConnectAction.UpdateBlockchain -> {
+ walletConnectManager.updateBlockchain(action.updatedSession)
+ }
}
}
private fun scanCard(session: WalletConnectSession, chainId: Int?) {
val blockchain = WalletConnectNetworkUtils.parseBlockchain(
- chainId = chainId, peer = session.peerMeta
+ chainId = chainId,
+ peer = session.peerMeta,
) ?: Blockchain.Ethereum
store.dispatch(
GlobalAction.ScanCard(
additionalBlockchainsToDerive = listOf(blockchain),
onSuccess = { scanResponse ->
- handleScanResponse(scanResponse, session, blockchain)
+ handleScanResponse(scanResponse = scanResponse, session = session, blockchain = blockchain)
},
onFailure = {
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
- }, R.string.wallet_connect_scan_card_message
- )
+ },
+ R.string.wallet_connect_scan_card_message,
+ ),
)
}
+ private suspend fun getAvailableBlockchains(card: Card, walletState: WalletState): List {
+ return if (walletState.cardId == card.cardId) {
+ walletState.currencies.filter {
+ it.isBlockchain() && !it.isCustomCurrency(card.derivationStyle) && it.blockchain.isEvm()
+ }.map { it.blockchain }
+ } else {
+ currenciesRepository
+ .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
+ .filter {
+ it.blockchain.isEvm() &&
+ it.derivationPath == it.blockchain.derivationPath(card.derivationStyle)?.rawPath
+ }
+ .map { it.blockchain }
+ }
+ }
+
+ private suspend fun prepareWalletManager(
+ scanResponse: ScanResponse,
+ walletState: WalletState,
+ blockchain: Blockchain,
+ session: WalletConnectSession,
+ ) {
+ val factory = WcWalletManagerFactory(
+ factory = store.state.globalState.tapWalletManager.walletManagerFactory,
+ currenciesRepository = currenciesRepository,
+ )
+ val walletManager = factory.getWalletManager(scanResponse, blockchain, walletState).guard {
+ store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(session.session))
+ store.dispatchOnMain(
+ GlobalAction.ShowDialog(
+ WalletConnectDialog.AddNetwork(blockchain.fullName),
+ ),
+ )
+ return
+ }
+ val wallet = walletManager.wallet
+ val derivedKey =
+ if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) {
+ null
+ } else {
+ walletManager.wallet.publicKey.blockchainKey
+ }
+ val walletForSession = WalletForSession(
+ cardId = scanResponse.card.cardId,
+ walletPublicKey = wallet.publicKey.seedKey,
+ derivedPublicKey = derivedKey,
+ derivationPath = wallet.publicKey.derivationPath,
+ derivationStyle = scanResponse.card.derivationStyle,
+ blockchain = wallet.blockchain,
+ )
+
+ withMainContext {
+ val updatedSession = session.copy(wallet = walletForSession)
+ walletConnectManager.updateSession(updatedSession)
+
+ store.dispatch(WalletConnectAction.ApproveSession(session.session))
+ }
+ }
+
private fun handleScanResponse(
- scanResponse: ScanResponse, session: WalletConnectSession, blockchain: Blockchain
+ scanResponse: ScanResponse,
+ session: WalletConnectSession,
+ blockchain: Blockchain,
) {
val card = scanResponse.card
if (!card.isMultiwalletAllowed) {
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
return
}
-
- val walletManager = getWalletManager(scanResponse, blockchain).guard {
- store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
- return
- }
-
- val wallet = walletManager.wallet
- val derivedKey =
- if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) {
- null
- } else {
- walletManager.wallet.publicKey.blockchainKey
- }
- val walletForSession = WalletForSession(
- cardId = scanResponse.card.cardId,
- walletPublicKey = wallet.publicKey.seedKey,
- derivedPublicKey = derivedKey,
- derivationPath = wallet.publicKey.derivationPath,
- blockchain = wallet.blockchain
+ val walletState = store.state.walletState
+ val updatedSession = session.copy(wallet = session.wallet.copy(blockchain = blockchain))
+ store.dispatch(
+ WalletConnectAction.SetNewSessionData(
+ NewWcSessionData(session = updatedSession, scanResponse = scanResponse, blockchain = blockchain),
+ ),
)
- val updatedSession = session.copy(wallet = walletForSession)
- walletConnectManager.updateSession(updatedSession)
- store.dispatchOnMain(WalletConnectAction.AddScanResponse(scanResponse))
+ scope.launch {
+ val blockchains = if (blockchain.isEvm()) getAvailableBlockchains(card, walletState) else emptyList()
- store.dispatchOnMain(
- GlobalAction.ShowDialog(
- WalletConnectDialog.ApproveWcSession(
- updatedSession
+ withMainContext {
+ store.dispatch(
+ GlobalAction.ShowDialog(
+ WalletConnectDialog.ApproveWcSession(session = updatedSession, networks = blockchains),
+ ),
)
- )
- )
- }
-
- private fun getWalletManager(
- scanResponse: ScanResponse, blockchain: Blockchain
- ): WalletManager? {
- val card = scanResponse.card
- val factory = store.state.globalState.tapWalletManager.walletManagerFactory
- val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
- Blockchain.EthereumTestnet
- } else {
- blockchain
- }
-
- return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) {
- val derivationPath = blockchainToMake.derivationPath(card.derivationStyle)?.rawPath
- store.state.walletState.getWalletManager(
- Currency.Blockchain(blockchainToMake, derivationPath)
- )
- ?: factory.makeWalletManagerForApp(
- scanResponse,
- blockchainToMake,
- card.derivationStyle?.let { DerivationParams.Default(it) }
- )
- ?.also { walletManager ->
- store.dispatch(
- WalletAction.MultiWallet.AddBlockchain(
- BlockchainNetwork.fromWalletManager(walletManager), walletManager
- )
- )
- }
- } else {
- val walletManager = factory.makeWalletManagerForApp(
- scanResponse,
- blockchainToMake,
- card.derivationStyle?.let { DerivationParams.Default(it) }
- )
- scope.launch {
- if (currenciesRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
- .find { it.blockchain == blockchainToMake } != null
- ) {
- walletManager?.let {
- currenciesRepository.saveUpdatedCurrency(
- card.cardId,
- BlockchainNetwork.fromWalletManager(walletManager)
- )
- }
- }
}
- return walletManager
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt
index 216367e9ee..544b6fa7be 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt
@@ -13,18 +13,17 @@ class WalletConnectReducer {
is WalletConnectAction.ApproveSession.Success -> {
state.copy(
loading = false,
- sessions = state.sessions + action.session
+ sessions = state.sessions + action.session,
)
}
- is WalletConnectAction.OpenSession -> {
+ is WalletConnectAction.OpenSession -> {
state.copy(loading = true)
}
- is WalletConnectAction.AddScanResponse -> {
- state.copy(scanResponse = action.scanResponse)
+ is WalletConnectAction.SetNewSessionData -> {
+ state.copy(newSessionData = action.newSession)
}
is WalletConnectAction.SetSessionsRestored ->
WalletConnectState(sessions = action.sessions)
-
is WalletConnectAction.RemoveSession -> {
val sessions =
state.sessions.filterNot { it.session.toUri() == action.session.toUri() }
@@ -34,6 +33,11 @@ class WalletConnectReducer {
is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false)
is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false)
is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false)
+ is WalletConnectAction.UpdateBlockchain -> state.copy(
+ sessions = state.sessions
+ .filterNot { it.peerId == action.updatedSession.peerId }
+ + action.updatedSession
+ )
else -> state
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
index 0039e247ee..0bdfaf69f6 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.features.details.redux.walletconnect
import com.squareup.moshi.JsonClass
import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.hdWallet.DerivationPath
@@ -15,7 +16,13 @@ import com.trustwallet.walletconnect.models.session.WCSession
data class WalletConnectState(
val loading: Boolean = false,
val sessions: List = listOf(),
- val scanResponse: ScanResponse? = null
+ val newSessionData: NewWcSessionData? = null,
+)
+
+data class NewWcSessionData(
+ val session: WalletConnectSession,
+ val scanResponse: ScanResponse,
+ val blockchain: Blockchain?,
)
data class WalletConnectSession(
@@ -37,8 +44,9 @@ data class WalletForSession(
val walletPublicKey: ByteArray? = null,
val derivedPublicKey: ByteArray? = null,
val derivationPath: DerivationPath? = null,
+ val derivationStyle: DerivationStyle? = null,
val isTestNet: Boolean = false,
- val blockchain: Blockchain? = if (isTestNet) Blockchain.EthereumTestnet else Blockchain.Ethereum
+ val blockchain: Blockchain? = if (isTestNet) Blockchain.EthereumTestnet else Blockchain.Ethereum,
) {
fun getBlockchainForSession(): Blockchain {
@@ -82,18 +90,30 @@ data class WalletForSession(
sealed class WalletConnectDialog : StateDialog {
data class ClipboardOrScanQr(val clipboardUri: String) : WalletConnectDialog()
object UnsupportedCard : WalletConnectDialog()
+ object UnsupportedNetwork : WalletConnectDialog()
+ data class AddNetwork(val network: String) : WalletConnectDialog()
object OpeningSessionRejected : WalletConnectDialog()
object SessionTimeout : WalletConnectDialog()
- data class ApproveWcSession(val session: WalletConnectSession) : WalletConnectDialog()
+ data class ApproveWcSession(
+ val session: WalletConnectSession,
+ val networks: List,
+ ) : WalletConnectDialog()
+
+ data class ChooseNetwork(
+ val session: WalletConnectSession,
+ val networks: List,
+ ) : WalletConnectDialog()
+
data class RequestTransaction(val dialogData: TransactionRequestDialogData) :
WalletConnectDialog()
data class PersonalSign(val data: PersonalSignDialogData) : WalletConnectDialog()
- data class BnbTransactionDialog(val data: BinanceMessageData,
- val session: WCSession,
- val sessionId: Long,
- val cardId: String,
- val dAppName: String
+ data class BnbTransactionDialog(
+ val data: BinanceMessageData,
+ val session: WCSession,
+ val sessionId: Long,
+ val cardId: String,
+ val dAppName: String,
) : WalletConnectDialog()
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt
deleted file mode 100644
index acbe36e4ae..0000000000
--- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsConfirmFragment.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.tangem.tap.features.details.ui
-
-import android.os.Bundle
-import android.view.View
-import androidx.activity.OnBackPressedCallback
-import androidx.fragment.app.Fragment
-import androidx.transition.TransitionInflater
-import by.kirich1409.viewbindingdelegate.viewBinding
-import com.tangem.tap.common.extensions.getDrawable
-import com.tangem.tap.common.redux.navigation.NavigationAction
-import com.tangem.tap.features.details.redux.ConfirmScreenState
-import com.tangem.tap.features.details.redux.DetailsAction
-import com.tangem.tap.features.details.redux.DetailsState
-import com.tangem.tap.store
-import com.tangem.wallet.R
-import com.tangem.wallet.databinding.FragmentDetailsConfirmBinding
-import org.rekotlin.StoreSubscriber
-
-class DetailsConfirmFragment : Fragment(R.layout.fragment_details_confirm),
- StoreSubscriber {
-
- private val binding: FragmentDetailsConfirmBinding by viewBinding(FragmentDetailsConfirmBinding::bind)
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- store.dispatch(NavigationAction.PopBackTo())
- }
- })
- val inflater = TransitionInflater.from(requireContext())
- enterTransition = inflater.inflateTransition(R.transition.slide_right)
- exitTransition = inflater.inflateTransition(R.transition.fade)
- }
-
- override fun onStart() {
- super.onStart()
- store.subscribe(this) { state ->
- state.skipRepeats { oldState, newState ->
- oldState.detailsState == newState.detailsState
- }.select { it.detailsState }
- }
- }
-
- override fun onStop() {
- super.onStop()
- store.unsubscribe(this)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- binding.toolbar.setNavigationOnClickListener {
- store.dispatch(NavigationAction.PopBackTo())
- }
- }
-
-
- override fun newState(state: DetailsState) {
- if (activity == null || view == null) return
- setState(state)
- }
-
- private fun setState(state: DetailsState) = with(binding) {
- when (state.confirmScreenState) {
- ConfirmScreenState.EraseWallet -> {
- toolbar.title = getString(R.string.details_row_title_reset_factory_settings)
- tvWarningDescription.text = getString(R.string.details_row_title_reset_factory_settings_warning)
- btnConfirm.text = getString(R.string.details_row_title_reset_factory_settings)
- btnConfirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
- null, null, getDrawable(R.drawable.ic_send), null
- )
- btnConfirm.setOnClickListener { store.dispatch(DetailsAction.ResetToFactory.Confirm) }
- }
- ConfirmScreenState.LongTap, ConfirmScreenState.AccessCode,
- ConfirmScreenState.PassCode -> {
- toolbar.title = getString(R.string.details_manage_security_title)
- tvWarningDescription.text = getString(R.string.details_security_management_warning)
- btnConfirm.text = getString(R.string.common_save_changes)
- btnConfirm.setCompoundDrawablesRelativeWithIntrinsicBounds(
- null, null, getDrawable(R.drawable.ic_save), null
- )
- btnConfirm.setOnClickListener { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) }
- }
- }
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
deleted file mode 100644
index e8f3b56a62..0000000000
--- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
+++ /dev/null
@@ -1,148 +0,0 @@
-package com.tangem.tap.features.details.ui
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.View
-import androidx.activity.OnBackPressedCallback
-import androidx.fragment.app.Fragment
-import androidx.transition.TransitionInflater
-import by.kirich1409.viewbindingdelegate.viewBinding
-import com.tangem.domain.common.getTwinCardIdForUser
-import com.tangem.tap.common.extensions.show
-import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.common.redux.navigation.AppScreen
-import com.tangem.tap.common.redux.navigation.NavigationAction
-import com.tangem.tap.domain.extensions.isMultiwalletAllowed
-import com.tangem.tap.features.details.redux.DetailsAction
-import com.tangem.tap.features.details.redux.DetailsState
-import com.tangem.tap.features.details.redux.SecurityOption
-import com.tangem.tap.features.feedback.FeedbackEmail
-import com.tangem.tap.features.wallet.redux.WalletAction
-import com.tangem.tap.store
-import com.tangem.wallet.R
-import com.tangem.wallet.databinding.FragmentDetailsBinding
-import org.rekotlin.StoreSubscriber
-
-class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber {
-
- private val binding: FragmentDetailsBinding by viewBinding(FragmentDetailsBinding::bind)
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- store.dispatch(NavigationAction.PopBackTo())
- }
- })
- val inflater = TransitionInflater.from(requireContext())
- enterTransition = inflater.inflateTransition(R.transition.slide_right)
- exitTransition = inflater.inflateTransition(R.transition.fade)
- }
-
- override fun onStart() {
- super.onStart()
- store.subscribe(this) { state ->
- state.skipRepeats { oldState, newState ->
- oldState.detailsState == newState.detailsState
- }.select { it.detailsState }
- }
- }
-
- override fun onStop() {
- super.onStop()
- store.unsubscribe(this)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- binding.toolbar.setNavigationOnClickListener {
- store.dispatch(NavigationAction.PopBackTo())
- }
- }
-
-
- override fun newState(state: DetailsState) {
- if (activity == null || view == null) return
- setState(state)
- }
-
- private fun setState(state: DetailsState) = with (binding){
-
- if (state.cardInfo != null) {
- val cardId = if (state.isTangemTwins) {
- state.scanResponse?.card?.getTwinCardIdForUser()
- } else {
- state.cardInfo.cardId
- }
- tvCardId.text = cardId
- tvIssuer.text = state.cardInfo.issuer
- tvSignedHashes.text = getString(
- R.string.details_row_subtitle_signed_hashes_format,
- state.cardInfo.signedHashes.toString()
- )
- }
-
- tvSignedHashes.show(!state.isTangemTwins)
- tvSignedHashesTitle.show(!state.isTangemTwins)
-
- tvDisclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
-
- tvCardTou.show(state.cardTermsOfUseUrl != null)
- tvCardTou.setOnClickListener {
- val intent = Intent(Intent.ACTION_VIEW)
- intent.data = state.cardTermsOfUseUrl
- startActivity(intent)
- }
-
- if (state.isTangemTwins) {
- tvResetToFactory.show()
- tvResetToFactory.text = getText(R.string.details_row_title_twins_recreate)
- tvResetToFactory.setOnClickListener {
- store.dispatch(DetailsAction.ReCreateTwinsWallet(state.twinCardsState.cardNumber!!))
- }
- } else {
- tvResetToFactory.show()
- tvResetToFactory.text = getText(R.string.details_row_title_reset_factory_settings)
- tvResetToFactory.setOnClickListener {
- store.dispatch(DetailsAction.ResetToFactory.Check)
- store.dispatch(DetailsAction.ResetToFactory.Proceed)
- }
- }
-
- tvCreateBackup.show(state.createBackupAllowed)
- tvCreateBackup.setOnClickListener {
- store.dispatch(DetailsAction.CreateBackup)
- }
-
- tvAppCurrency.show(state.scanResponse?.card?.isMultiwalletAllowed != true)
- tvAppCurrencyTitle.show(state.scanResponse?.card?.isMultiwalletAllowed != true)
- tvAppCurrency.text = state.appCurrency.code
-
- tvAppCurrency.setOnClickListener {
- store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
- }
-
- tvSendFeedback.setOnClickListener {
- store.dispatch(GlobalAction.SendFeedback(FeedbackEmail()))
- }
-
- tvWalletConnect.show(state.scanResponse?.card?.isMultiwalletAllowed == true)
- tvWalletConnect.setOnClickListener {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
- }
-
- llManageSecurity.setOnClickListener {
- store.dispatch(DetailsAction.ManageSecurity.CheckCurrentSecurityOption(state.scanResponse!!.card))
- }
-
- val currentSecurity = when (state.securityScreenState?.currentOption) {
- SecurityOption.LongTap -> R.string.details_manage_security_long_tap
- SecurityOption.PassCode -> R.string.details_manage_security_passcode
- SecurityOption.AccessCode -> R.string.details_manage_security_access_code
- null -> null
- }
- currentSecurity?.let { tvSecurity.text = getString(it) }
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt
deleted file mode 100644
index e9c8b38565..0000000000
--- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsSecurityFragment.kt
+++ /dev/null
@@ -1,147 +0,0 @@
-package com.tangem.tap.features.details.ui
-
-import android.os.Bundle
-import android.view.View
-import androidx.activity.OnBackPressedCallback
-import androidx.fragment.app.Fragment
-import androidx.transition.TransitionInflater
-import by.kirich1409.viewbindingdelegate.viewBinding
-import com.tangem.common.card.Card
-import com.tangem.tap.common.extensions.show
-import com.tangem.tap.common.redux.navigation.NavigationAction
-import com.tangem.tap.features.details.redux.DetailsAction
-import com.tangem.tap.features.details.redux.DetailsState
-import com.tangem.tap.features.details.redux.SecurityOption
-import com.tangem.tap.store
-import com.tangem.wallet.R
-import com.tangem.wallet.databinding.FragmentDetailsSecurityBinding
-import org.rekotlin.StoreSubscriber
-import java.util.*
-
-class DetailsSecurityFragment : Fragment(R.layout.fragment_details_security),
- StoreSubscriber {
-
- private val binding: FragmentDetailsSecurityBinding by viewBinding(
- FragmentDetailsSecurityBinding::bind
- )
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- store.dispatch(NavigationAction.PopBackTo())
- }
- })
- val inflater = TransitionInflater.from(requireContext())
- enterTransition = inflater.inflateTransition(R.transition.slide_right)
- exitTransition = inflater.inflateTransition(R.transition.fade)
- }
-
- override fun onStart() {
- super.onStart()
- store.subscribe(this) { state ->
- state.skipRepeats { oldState, newState ->
- oldState.detailsState == newState.detailsState
- }.select { it.detailsState }
- }
- }
-
- override fun onStop() {
- super.onStop()
- store.unsubscribe(this)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- binding.toolbar.setNavigationOnClickListener {
- store.dispatch(NavigationAction.PopBackTo())
- }
- setOnClickListeners()
- }
-
- private fun setOnClickListeners() {
- binding.btnSaveChanges.setOnClickListener {
- store.state.detailsState.securityScreenState?.selectedOption?.let {
- store.dispatch(DetailsAction.ManageSecurity.ConfirmSelection(it))
- }
- }
- }
-
- override fun newState(state: DetailsState) {
- if (activity == null || view == null) return
-
- selectSecurityOption(state.securityScreenState?.selectedOption)
- for (option in SecurityOption.values()) {
- setupOption(
- option,
- state.securityScreenState?.allowedOptions
- ?: EnumSet.noneOf(SecurityOption::class.java)
- )
- }
- binding.tvAccessCodeUnavailableDisclaimer.show(
- state.scanResponse?.card?.backupStatus == Card.BackupStatus.NoBackup
- )
- }
-
- private fun selectSecurityOption(securityOption: SecurityOption?) = with(binding) {
- radiobuttonLongTap.isChecked =
- securityOption == SecurityOption.LongTap && radiobuttonLongTap.isEnabled
- radiobuttonPasscode.isChecked =
- securityOption == SecurityOption.PassCode && radiobuttonPasscode.isEnabled
- radiobuttonAccessCode.isChecked =
- securityOption == SecurityOption.AccessCode && radiobuttonAccessCode.isEnabled
- }
-
- private fun setupOption(option: SecurityOption, allowedOptions: EnumSet) {
- when (option) {
- SecurityOption.LongTap -> { enableLongTap(allowedOptions.contains(option)) }
- SecurityOption.PassCode -> { enablePasscode(allowedOptions.contains(option)) }
- SecurityOption.AccessCode -> { enableAccessCode(allowedOptions.contains(option)) }
- }
- }
-
- private fun enableLongTap(enable: Boolean) = with(binding) {
- groupLongTap.show(enable)
- val alpha = if (enable) 1f else 0.5f
- tvLongTapDescription.alpha = alpha
- tvLongTapTitle.alpha = alpha
- radiobuttonLongTap.alpha = alpha
- radiobuttonLongTap.isEnabled = enable
- vLongTap.isClickable = enable
- if (enable) {
- vLongTap.setOnClickListener {
- store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.LongTap))
- }
- }
- }
-
- private fun enablePasscode(enable: Boolean) = with(binding) {
- groupPasscode.show(enable)
- val alpha = if (enable) 1f else 0.5f
- tvPasscodeDescription.alpha = alpha
- tvPasscodeTitle.alpha = alpha
- radiobuttonPasscode.alpha = alpha
- radiobuttonPasscode.isEnabled = enable
- vPasscode.isClickable = enable
- if (enable) {
- vPasscode.setOnClickListener {
- store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.PassCode))
- }
- }
- }
-
- private fun enableAccessCode(enable: Boolean) = with(binding) {
- groupAccessCode.show(enable)
- val alpha = if (enable) 1f else 0.5f
- tvAccessCodeDescription.alpha = alpha
- tvAccessCodeTitle.alpha = alpha
- radiobuttonAccessCode.alpha = alpha
- radiobuttonAccessCode.isEnabled = enable
- vAccessCode.isClickable = enable
- if (enable) {
- vAccessCode.setOnClickListener {
- store.dispatch(DetailsAction.ManageSecurity.SelectOption(SecurityOption.AccessCode))
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt
new file mode 100644
index 0000000000..bea7b87171
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt
@@ -0,0 +1,71 @@
+package com.tangem.tap.features.details.ui.appsettings
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import androidx.transition.TransitionInflater
+import com.google.accompanist.appcompattheme.AppCompatTheme
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.details.redux.DetailsAction
+import com.tangem.tap.features.details.redux.DetailsState
+import com.tangem.tap.store
+import com.tangem.wallet.R
+import org.rekotlin.StoreSubscriber
+
+class AppSettingsFragment : Fragment(), StoreSubscriber {
+ private val viewModel = AppSettingsViewModel(store)
+ private var screenState: MutableState =
+ mutableStateOf(viewModel.updateState(store.state.detailsState))
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(R.transition.slide_right)
+ exitTransition = inflater.inflateTransition(R.transition.fade)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ return ComposeView(requireContext()).apply {
+ setContent {
+ isTransitionGroup = true
+ AppCompatTheme {
+ AppSettingsScreen(
+ state = screenState.value,
+ onBackPressed = {
+ store.dispatch(DetailsAction.ResetCardSettingsData)
+ store.dispatch(NavigationAction.PopBackTo())
+ },
+ )
+ }
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.detailsState == newState.detailsState
+ }.select { it.detailsState }
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ store.unsubscribe(this)
+ }
+
+ override fun newState(state: DetailsState) {
+ if (activity == null || view == null) return
+ screenState.value = viewModel.updateState(state)
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt
new file mode 100644
index 0000000000..34018707cf
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt
@@ -0,0 +1,230 @@
+package com.tangem.tap.features.details.ui.appsettings
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.material.AlertDialog
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.tangem.tap.common.compose.TangemTypography
+import com.tangem.tap.features.details.redux.PrivacySetting
+import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
+import com.tangem.tap.features.details.ui.common.TangemSwitch
+import com.tangem.wallet.R
+
+@Composable
+fun AppSettingsScreen(
+ state: AppSettingsScreenState,
+ onBackPressed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ SettingsScreensScaffold(
+ content = {
+ AppSettings(state = state, modifier = modifier)
+ },
+ titleRes = R.string.app_settings_title,
+ onBackClick = onBackPressed,
+ )
+}
+
+@Composable
+private fun AppSettings(
+ state: AppSettingsScreenState,
+ modifier: Modifier = Modifier,
+) {
+ var dialogType by remember { mutableStateOf(null) }
+ val onDialogStateChange: (PrivacySetting?) -> Unit = { dialogType = it }
+
+ dialogType?.let {
+ SettingsAlertDialog(
+ element = it,
+ onDialogStateChange = onDialogStateChange,
+ onSettingToggled = state.onSettingToggled,
+ modifier = modifier,
+ )
+ }
+
+ Column(
+ modifier = modifier
+ .fillMaxSize(),
+ ) {
+ AppSettingsElement(
+ state = state,
+ setting = PrivacySetting.SaveWallets,
+ onDialogStateChange = onDialogStateChange,
+ modifier = modifier,
+ )
+ Spacer(modifier = Modifier.size(32.dp))
+ AppSettingsElement(
+ state = state,
+ setting = PrivacySetting.SaveAccessCode,
+ onDialogStateChange = onDialogStateChange,
+ modifier = modifier,
+ )
+ }
+}
+
+@Composable
+private fun AppSettingsElement(
+ state: AppSettingsScreenState,
+ setting: PrivacySetting,
+ onDialogStateChange: (PrivacySetting?) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val titleRes = when (setting) {
+ PrivacySetting.SaveWallets -> R.string.app_settings_saved_wallet
+ PrivacySetting.SaveAccessCode -> R.string.app_settings_saved_access_codes
+ }
+ val subtitleRes = when (setting) {
+ PrivacySetting.SaveWallets -> R.string.app_settings_saved_wallet_footer
+ PrivacySetting.SaveAccessCode -> R.string.app_settings_saved_access_codes_footer
+ }
+ val checked = state.settings[setting] ?: false
+
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(start = 20.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ modifier = modifier
+ .weight(0.6f)
+ .padding(end = 4.dp),
+ ) {
+ Text(
+ text = stringResource(id = titleRes),
+ style = TangemTypography.subtitle1,
+ color = colorResource(id = R.color.text_primary_1),
+ )
+ Spacer(modifier = Modifier.size(4.dp))
+ Text(
+ text = stringResource(id = subtitleRes),
+ style = TangemTypography.body2,
+ color = colorResource(id = R.color.text_secondary),
+ )
+ }
+ TangemSwitch(
+ checked = checked,
+ onCheckedChange = {
+ onCheckedChange(
+ element = setting,
+ enabled = it,
+ onSettingToggled = state.onSettingToggled,
+ onDialogStateChange = onDialogStateChange,
+ )
+ },
+ modifier = modifier
+ .padding(20.dp),
+ )
+ }
+}
+
+private fun onCheckedChange(
+ element: PrivacySetting, enabled: Boolean,
+ onSettingToggled: (PrivacySetting, Boolean) -> Unit,
+ onDialogStateChange: (PrivacySetting?) -> Unit,
+) {
+ //Show warning if user wants to disable the switch
+ if (!enabled) {
+ onDialogStateChange(element)
+ } else {
+ onSettingToggled(element, enabled)
+ }
+}
+
+@Composable
+private fun SettingsAlertDialog(
+ element: PrivacySetting,
+ onDialogStateChange: (PrivacySetting?) -> Unit,
+ onSettingToggled: (PrivacySetting, Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val text = when (element) {
+ PrivacySetting.SaveWallets -> R.string.app_settings_off_saved_wallet_alert_message
+ PrivacySetting.SaveAccessCode -> R.string.app_settings_off_saved_access_code_alert_message
+ }
+ AlertDialog(
+ onDismissRequest = { onDialogStateChange(null) },
+ confirmButton = {
+ TextButton(
+ onClick = {
+ onDialogStateChange(null)
+ },
+ ) {
+ Text(
+ text = stringResource(id = R.string.common_cancel),
+ color = colorResource(id = R.color.text_secondary),
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Medium,
+ )
+ }
+ },
+ dismissButton = {
+ TextButton(
+ onClick = {
+ onDialogStateChange(null)
+ onSettingToggled(element, false)
+ },
+ modifier = modifier.padding(bottom = 14.dp),
+ ) {
+ Text(
+ text = stringResource(id = R.string.common_delete),
+ color = colorResource(id = R.color.text_warning),
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Medium,
+ )
+ }
+ },
+ title = {
+ Text(
+ text = stringResource(id = R.string.common_attention),
+ color = colorResource(id = R.color.text_primary_1),
+ )
+ },
+ text = {
+ Text(
+ text = stringResource(id = text),
+ color = colorResource(id = R.color.text_secondary),
+ )
+ },
+ shape = MaterialTheme.shapes.medium.copy(all = CornerSize(size = 28.dp)),
+ modifier = modifier.padding(vertical = 34.dp, horizontal = 24.dp),
+ )
+}
+
+@Preview
+@Composable
+fun AppSettingsScreenPreview() {
+ AppSettingsScreen(
+ state = AppSettingsScreenState(
+ settings = mapOf(
+ PrivacySetting.SaveWallets to true,
+ PrivacySetting.SaveAccessCode to false,
+ ),
+ onSettingToggled = { _, _ -> },
+ ),
+ onBackPressed = { },
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt
new file mode 100644
index 0000000000..043423b3d3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt
@@ -0,0 +1,10 @@
+package com.tangem.tap.features.details.ui.appsettings
+
+import com.tangem.tap.features.details.redux.PrivacySetting
+
+data class AppSettingsScreenState(
+ val settings: Map,
+ val onSettingToggled: (PrivacySetting, Boolean) -> Unit,
+)
+
+
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt
new file mode 100644
index 0000000000..65dd12eaeb
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt
@@ -0,0 +1,27 @@
+package com.tangem.tap.features.details.ui.appsettings
+
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.features.details.redux.DetailsAction
+import com.tangem.tap.features.details.redux.DetailsState
+import com.tangem.tap.features.details.redux.PrivacySetting
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import org.rekotlin.Store
+
+class AppSettingsViewModel(private val store: Store) {
+ private val _uiState = MutableStateFlow(updateState(store.state.detailsState))
+ val uiState: StateFlow = _uiState
+ fun updateState(state: DetailsState): AppSettingsScreenState {
+ return AppSettingsScreenState(
+ settings = mapOf(
+ PrivacySetting.SaveWallets to state.saveWallets,
+ PrivacySetting.SaveAccessCode to state.saveAccessCodes,
+ ),
+ onSettingToggled = { privacySetting, enabled -> onSettingsToggled(privacySetting, enabled) },
+ )
+ }
+
+ private fun onSettingsToggled(setting: PrivacySetting, enable: Boolean) {
+ store.dispatch(DetailsAction.AppSettings.SwitchPrivacySetting(enable = enable, setting = setting))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt
new file mode 100644
index 0000000000..a679020e84
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt
@@ -0,0 +1,73 @@
+package com.tangem.tap.features.details.ui.cardsettings
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import androidx.transition.TransitionInflater
+import com.google.accompanist.appcompattheme.AppCompatTheme
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.details.redux.DetailsAction
+import com.tangem.tap.features.details.redux.DetailsState
+import com.tangem.tap.store
+import org.rekotlin.StoreSubscriber
+
+class CardSettingsFragment : Fragment(), StoreSubscriber {
+
+ private val viewModel = CardSettingsViewModel(store)
+
+ private var screenState: MutableState =
+ mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState))
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(android.R.transition.fade)
+ exitTransition = inflater.inflateTransition(android.R.transition.fade)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ return ComposeView(requireContext()).apply {
+ setContent {
+ isTransitionGroup = true
+ AppCompatTheme {
+ CardSettingsScreen(
+ state = screenState.value,
+ onBackPressed = {
+ store.dispatch(DetailsAction.ResetCardSettingsData)
+ store.dispatch(NavigationAction.PopBackTo())
+ },
+ )
+ }
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.detailsState == newState.detailsState
+ }.select { it.detailsState }
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ store.unsubscribe(this)
+ }
+
+ override fun newState(state: DetailsState) {
+ if (activity == null || view == null) return
+ screenState.value = viewModel.updateState(state.cardSettingsState)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt
new file mode 100644
index 0000000000..92eec4c59b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt
@@ -0,0 +1,169 @@
+package com.tangem.tap.features.details.ui.cardsettings
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.tangem.tap.common.compose.TangemTypography
+import com.tangem.tap.features.details.ui.common.DetailsMainButton
+import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
+import com.tangem.wallet.R
+
+@Composable
+fun CardSettingsScreen(
+ state: CardSettingsScreenState,
+ onBackPressed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ SettingsScreensScaffold(
+ content =
+ if (state.cardDetails == null) {
+ { CardSettingsReadCard(state.onScanCardClick, modifier = modifier) }
+ } else {
+ { CardSettings(state = state, modifier = modifier) }
+ },
+ titleRes = R.string.card_settings_title,
+ onBackClick = onBackPressed,
+ )
+}
+
+@Composable
+fun CardSettingsReadCard(
+ onScanCardClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.SpaceBetween,
+ ) {
+
+ Box(
+ modifier = modifier
+ .fillMaxWidth(),
+
+ ) {
+ Image(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(start = 80.dp, end = 80.dp, top = 70.dp)
+ .rotate(-15f),
+ painter = painterResource(id = R.drawable.card_placeholder_secondary),
+ contentDescription = "",
+ contentScale = ContentScale.FillWidth,
+ )
+ Image(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(start = 60.dp, end = 60.dp)
+ .rotate(-1f),
+ painter = painterResource(id = R.drawable.card_placeholder_black),
+ contentDescription = "",
+ contentScale = ContentScale.FillWidth,
+ )
+
+ }
+
+
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(start = 16.dp, end = 16.dp, bottom = 32.dp),
+ ) {
+
+ Text(
+ text = stringResource(id = R.string.scan_card_settings_title),
+ color = colorResource(id = R.color.text_primary_1),
+ style = TangemTypography.headline3,
+ )
+ Spacer(modifier = modifier.size(20.dp))
+ Text(
+ text = stringResource(id = R.string.scan_card_settings_message),
+ color = colorResource(id = R.color.text_secondary),
+ style = TangemTypography.body1,
+ )
+ Spacer(modifier = modifier.size(29.dp))
+ DetailsMainButton(
+ title = stringResource(id = R.string.scan_card_settings_button),
+ onClick = onScanCardClick,
+ modifier = modifier,
+ )
+ }
+ }
+}
+
+@Composable
+fun CardSettings(
+ state: CardSettingsScreenState,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth(),
+ ) {
+ state.cardDetails?.map {
+ val paddingBottom = when (it) {
+ is CardInfo.CardId, is CardInfo.Issuer -> 12.dp
+ is CardInfo.SignedHashes -> 14.dp
+ is CardInfo.SecurityMode -> 16.dp
+ is CardInfo.ChangeAccessCode -> 16.dp
+ is CardInfo.ResetToFactorySettings -> 28.dp
+ }
+ val paddingTop = when (it) {
+ is CardInfo.CardId -> 0.dp
+ is CardInfo.Issuer -> 12.dp
+ is CardInfo.SignedHashes -> 12.dp
+ is CardInfo.SecurityMode -> 14.dp
+ is CardInfo.ChangeAccessCode -> 16.dp
+ is CardInfo.ResetToFactorySettings -> 16.dp
+ }
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .clickable(
+ enabled = it.clickable,
+ onClick = { state.onElementClick(it) },
+ )
+ .padding(start = 20.dp, end = 20.dp, bottom = paddingBottom, top = paddingTop),
+ ) {
+ val titleColor = if (it.clickable) R.color.text_primary_1 else R.color.text_tertiary
+ val subtitleColor = if (it.clickable) R.color.text_secondary else R.color.text_tertiary
+ Text(
+ text = it.titleRes.resolveReference(),
+ color = colorResource(id = titleColor),
+ style = TangemTypography.subtitle1,
+ )
+ Spacer(modifier = modifier.size(4.dp))
+ Text(
+ text = it.subtitle.resolveReference(),
+ color = colorResource(id = subtitleColor),
+ style = TangemTypography.body2,
+ )
+ }
+
+ }
+
+ }
+}
+
+@Composable
+@Preview
+fun CardSettingsPreview() {
+ CardSettingsScreen(state = CardSettingsScreenState(onScanCardClick = {}) {}, {})
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt
new file mode 100644
index 0000000000..e7354df5c5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt
@@ -0,0 +1,74 @@
+package com.tangem.tap.features.details.ui.cardsettings
+
+import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.res.stringResource
+import com.tangem.tap.features.details.redux.SecurityOption
+import com.tangem.tap.features.details.ui.securitymode.toTitleRes
+import com.tangem.wallet.R
+
+data class CardSettingsScreenState(
+ val cardDetails: List? = null,
+ val onScanCardClick: () -> Unit,
+ val onElementClick: (CardInfo) -> Unit,
+)
+
+sealed class CardInfo(
+ val titleRes: TextReference, val subtitle: TextReference, val clickable: Boolean = false,
+) {
+ class CardId(subtitle: String) : CardInfo(
+ titleRes = TextReference.Res(R.string.details_row_title_cid),
+ subtitle = TextReference
+ .Str(subtitle),
+ )
+
+ class Issuer(subtitle: String) : CardInfo(
+ titleRes = TextReference.Res(R.string.details_row_title_issuer),
+ subtitle = TextReference
+ .Str(subtitle),
+ )
+
+ class SignedHashes(hashes: String) : CardInfo(
+ titleRes = TextReference.Res(R.string.details_row_title_signed_hashes),
+ subtitle = TextReference.Res(R.string.details_row_subtitle_signed_hashes_format, hashes),
+ )
+
+ class SecurityMode(
+ securityOption: SecurityOption,
+ clickable: Boolean,
+ ) : CardInfo(
+ titleRes = TextReference.Res(R.string.card_settings_security_mode),
+ subtitle = TextReference.Res(securityOption.toTitleRes()),
+ clickable = clickable,
+ )
+
+ object ChangeAccessCode : CardInfo(
+ titleRes = TextReference.Res(R.string.card_settings_change_access_code),
+ subtitle = TextReference.Res(R.string.card_settings_change_access_code_footer),
+ clickable = true,
+ )
+
+ object ResetToFactorySettings : CardInfo(
+ titleRes = TextReference.Res(R.string.card_settings_reset_card_to_factory),
+ subtitle = TextReference.Res(R.string.card_settings_reset_card_to_factory_footer),
+ clickable = true,
+ )
+}
+
+sealed interface TextReference {
+ class Res(@StringRes val id: Int, val formatArgs: List = emptyList()) : TextReference {
+ constructor(@StringRes id: Int, vararg formatArgs: Any) : this(id, formatArgs.toList())
+ }
+
+ class Str(val value: String) : TextReference
+}
+
+@Composable
+@ReadOnlyComposable
+fun TextReference.resolveReference(): String {
+ return when (this) {
+ is TextReference.Res -> stringResource(this.id, *this.formatArgs.toTypedArray())
+ is TextReference.Str -> this.value
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt
new file mode 100644
index 0000000000..60affefd8b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt
@@ -0,0 +1,73 @@
+package com.tangem.tap.features.details.ui.cardsettings
+
+import com.tangem.domain.common.getTwinCardIdForUser
+import com.tangem.domain.common.isTangemTwins
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.features.details.redux.CardSettingsState
+import com.tangem.tap.features.details.redux.DetailsAction
+import org.rekotlin.Store
+
+class CardSettingsViewModel(private val store: Store) {
+
+ fun updateState(state: CardSettingsState?): CardSettingsScreenState {
+
+ return if (state?.manageSecurityState == null) {
+ CardSettingsScreenState(
+ cardDetails = null,
+ onElementClick = {},
+ onScanCardClick = {
+ store.dispatch(DetailsAction.ScanCard)
+ },
+ )
+ } else {
+ val cardId = if (state.card.isTangemTwins()) {
+ state.card.getTwinCardIdForUser()
+ } else {
+ state.cardInfo.cardId
+ }
+ val cardDetails: MutableList = mutableListOf(
+ CardInfo.CardId(cardId),
+ CardInfo.Issuer(state.cardInfo.issuer),
+ )
+
+ if (!state.card.isTangemTwins()) {
+ cardDetails.add(CardInfo.SignedHashes(state.cardInfo.signedHashes.toString()))
+ }
+ cardDetails.add(
+ CardInfo.SecurityMode(
+ securityOption = state.manageSecurityState.currentOption,
+ clickable = state.manageSecurityState.allowedOptions.size > 1,
+ ),
+ )
+ if (state.card.backupStatus?.isActive == true && state.card.isAccessCodeSet) {
+ cardDetails.add(CardInfo.ChangeAccessCode)
+ }
+ if (state.resetCardAllowed) {
+ cardDetails.add(CardInfo.ResetToFactorySettings)
+ }
+
+ CardSettingsScreenState(
+ cardDetails = cardDetails,
+ onScanCardClick = { },
+ onElementClick = {
+ handleClickingItem(it)
+ },
+ )
+ }
+ }
+
+ private fun handleClickingItem(item: CardInfo) {
+ when (item) {
+ is CardInfo.ChangeAccessCode -> {
+ store.dispatch(DetailsAction.ManageSecurity.ChangeAccessCode)
+ }
+ is CardInfo.ResetToFactorySettings -> {
+ store.dispatch(DetailsAction.ResetToFactory.Start)
+ }
+ is CardInfo.SecurityMode -> {
+ store.dispatch(DetailsAction.ManageSecurity.OpenSecurity)
+ }
+ else -> {}
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt
new file mode 100644
index 0000000000..d8e6bf1b11
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt
@@ -0,0 +1,134 @@
+package com.tangem.tap.features.details.ui.common
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.tangem.tap.common.compose.TangemTypography
+import com.tangem.wallet.R
+
+@Composable
+fun SettingsScreensScaffold(
+ content: @Composable () -> Unit,
+ background: @Composable (() -> Unit)? = null,
+ fab: @Composable (() -> Unit)? = null,
+ backgroundColor: Color = colorResource(id = R.color.background_primary),
+ titleRes: Int? = null,
+ onBackClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ BackHandler(true, onBackClick)
+
+ Scaffold(
+ topBar = {
+ EmptyTopBarWithNavigation(
+ onBackClick = onBackClick,
+ backgroundColor = backgroundColor,
+ )
+ },
+ modifier = modifier.systemBarsPadding(),
+ backgroundColor = backgroundColor,
+ floatingActionButton = { fab?.invoke() },
+ ) {
+ if (titleRes != null) {
+ Box(modifier = modifier.fillMaxSize()) {
+ background?.invoke()
+
+ Column(modifier = modifier.fillMaxWidth()) {
+ Text(
+ text = stringResource(id = titleRes),
+ modifier = modifier.padding(start = 20.dp, end = 20.dp, bottom = 52.dp),
+ style = TangemTypography.headline1,
+ color = colorResource(id = R.color.text_primary_1),
+ )
+ content()
+ }
+ }
+ } else {
+ content()
+ }
+ }
+}
+
+@Composable
+fun ScreenTitle(
+ titleRes: Int,
+ modifier: Modifier = Modifier,
+) {
+ Text(
+ text = stringResource(id = titleRes),
+ modifier = modifier.padding(start = 20.dp, end = 20.dp),
+ style = TangemTypography.headline1,
+ color = colorResource(id = R.color.text_primary_1),
+ )
+}
+
+@Composable
+fun EmptyTopBarWithNavigation(
+ onBackClick: () -> Unit,
+ backgroundColor: Color = colorResource(id = R.color.background_primary),
+ modifier: Modifier = Modifier,
+) {
+ TopAppBar(
+ title = { },
+ navigationIcon =
+ {
+ IconButton(onClick = onBackClick) {
+ Icon(
+ painterResource(id = R.drawable.ic_back), "",
+ tint = colorResource(id = R.color.icon_primary_1),
+ )
+ }
+ },
+ backgroundColor = backgroundColor,
+ elevation = 0.dp,
+ )
+}
+
+@Composable
+fun DetailsMainButton(
+ title: String,
+ enabled: Boolean = true,
+ onClick: (() -> Unit),
+ modifier: Modifier = Modifier,
+) {
+ Button(
+ onClick = onClick,
+ modifier = modifier
+ .fillMaxWidth()
+ .height(48.dp),
+ shape = RoundedCornerShape(12.dp),
+ enabled = enabled,
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = colorResource(R.color.button_primary),
+ contentColor = colorResource(R.color.text_primary_2),
+ disabledBackgroundColor = colorResource(R.color.button_disabled),
+ disabledContentColor = colorResource(R.color.text_disabled),
+ ),
+ ) {
+ Text(text = title)
+ Spacer(modifier = modifier.size(8.dp))
+ Icon(painter = painterResource(id = R.drawable.ic_tangem), contentDescription = "")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt
new file mode 100644
index 0000000000..bf5268bf6f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt
@@ -0,0 +1,96 @@
+package com.tangem.tap.features.details.ui.common
+
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.FastOutLinearInEasing
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.animateDp
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.indication
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.tangem.wallet.R
+
+@Composable
+fun TangemSwitch(
+ modifier: Modifier = Modifier,
+ enabledColor: Color = colorResource(id = R.color.control_checked),
+ disabledColor: Color = colorResource(id = R.color.icon_informative),
+ size: Dp = 48.dp,
+ checked: Boolean = false,
+ onCheckedChange: (Boolean) -> Unit,
+) {
+ val transition = updateTransition(checked, label = "SwitchState")
+ val color by transition.animateColor(
+ transitionSpec = {
+ tween(durationMillis = 200, easing = FastOutLinearInEasing)
+ },
+ label = "",
+ ) { enabled ->
+ if (enabled) enabledColor else disabledColor
+ }
+ val interactionSource = remember { MutableInteractionSource() }
+
+ Box(
+ modifier = modifier
+ .clickable(
+ interactionSource = interactionSource,
+ indication = null,
+ ) {
+ onCheckedChange(!checked)
+ }
+ .indication(
+ interactionSource = interactionSource,
+ indication = rememberRipple(
+ bounded = false,
+ color = Color.Transparent,
+ ),
+ ),
+ ) {
+ BoxWithConstraints(
+ modifier = modifier
+ .width(size)
+ .height(size / 2)
+ .indication(interactionSource, null)
+ .background(color = color, shape = RoundedCornerShape(100)),
+ contentAlignment = Alignment.CenterStart,
+ ) {
+ val roundCardSize = this.maxWidth / 2
+ val xOffset by transition.animateDp(
+ transitionSpec = {
+ tween(durationMillis = 150, easing = LinearOutSlowInEasing)
+ },
+ label = "xOffset",
+ ) { enabled ->
+ if (enabled) this.maxWidth - roundCardSize else 0.dp
+ }
+
+ Box(
+ modifier = Modifier
+ .size(this.maxWidth / 2)
+ .offset(x = xOffset, y = 0.dp)
+ .padding(3.dp)
+ .background(color = Color.White, shape = RoundedCornerShape(100)),
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt
new file mode 100644
index 0000000000..6674371319
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt
@@ -0,0 +1,69 @@
+package com.tangem.tap.features.details.ui.details
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import androidx.transition.TransitionInflater
+import com.google.accompanist.appcompattheme.AppCompatTheme
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.details.redux.DetailsState
+import com.tangem.tap.store
+import org.rekotlin.StoreSubscriber
+
+class DetailsFragment : Fragment(), StoreSubscriber {
+
+ private val detailsViewModel = DetailsViewModel(store)
+
+ private var detailsScreenState: MutableState =
+ mutableStateOf(detailsViewModel.updateState(store.state.detailsState))
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(android.R.transition.fade)
+ exitTransition = inflater.inflateTransition(android.R.transition.fade)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ return ComposeView(requireContext()).apply {
+ setContent {
+ isTransitionGroup = true
+ AppCompatTheme {
+ DetailsScreen(
+ state = detailsScreenState.value,
+ onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) },
+ )
+ }
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.detailsState == newState.detailsState
+ }.select { it.detailsState }
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ store.unsubscribe(this)
+ }
+
+ override fun newState(state: DetailsState) {
+ if (activity == null || view == null) return
+ detailsScreenState.value = detailsViewModel.updateState(state)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt
new file mode 100644
index 0000000000..24129ec514
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt
@@ -0,0 +1,197 @@
+package com.tangem.tap.features.details.ui.details
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Icon
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.tangem.tap.common.compose.TangemTypography
+import com.tangem.tap.features.details.ui.common.ScreenTitle
+import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
+import com.tangem.wallet.R
+
+@Composable
+fun DetailsScreen(
+ state: DetailsScreenState,
+ onBackPressed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ SettingsScreensScaffold(
+ content = { Content(state = state, modifier = modifier) },
+ onBackClick = onBackPressed,
+ )
+}
+
+@Composable
+fun Content(
+ state: DetailsScreenState,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState()),
+ ) {
+ ScreenTitle(titleRes = R.string.details_title, modifier.padding(bottom = 52.dp))
+ state.elements.map {
+ if (it == SettingsElement.WalletConnect) {
+ WalletConnectDetailsItem(
+ onItemsClick = state.onItemsClick,
+ modifier = modifier,
+ )
+ } else {
+ DetailsItem(
+ item = it,
+ appCurrency = state.appCurrency,
+ onItemsClick = state.onItemsClick,
+ modifier = modifier,
+ )
+ }
+ }
+ Spacer(modifier = modifier.weight(1f))
+ TangemSocialAccounts(state.tangemLinks, state.onSocialNetworkClick)
+ Spacer(modifier = Modifier.size(12.dp))
+ Text(
+ text = "${stringResource(id = state.appNameRes)} ${state.tangemVersion}",
+ style = TangemTypography.caption,
+ color = colorResource(id = R.color.text_tertiary),
+ modifier = modifier.padding(start = 16.dp, end = 16.dp, bottom = 40.dp),
+ )
+ }
+}
+
+@Composable
+fun WalletConnectDetailsItem(
+ onItemsClick: (SettingsElement) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ modifier = modifier
+ .height(84.dp)
+ .fillMaxWidth()
+ .clickable { onItemsClick(SettingsElement.WalletConnect) },
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_walletconnect),
+ contentDescription = stringResource(id = R.string.wallet_connect_title),
+ modifier = modifier.padding(start = 20.dp, end = 20.dp),
+ tint = colorResource(id = R.color.all_colors_azure),
+ )
+ Column(
+ modifier = modifier.height(56.dp),
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.Center,
+ ) {
+ Text(
+ text = stringResource(id = R.string.wallet_connect_title),
+ modifier = modifier.padding(end = 20.dp, bottom = 4.dp),
+ style = TangemTypography.headline3,
+ color = colorResource(id = R.color.text_primary_1),
+ )
+ Text(
+ text = stringResource(id = R.string.wallet_connect_subtitle),
+ modifier = modifier.padding(end = 20.dp, bottom = 4.dp),
+ style = TangemTypography.body1,
+ color = colorResource(id = R.color.text_secondary),
+ )
+ }
+ }
+}
+
+@Composable
+fun DetailsItem(
+ item: SettingsElement,
+ appCurrency: String,
+ onItemsClick: (SettingsElement) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ modifier = modifier
+ .height(56.dp)
+ .fillMaxWidth()
+ .clickable { onItemsClick(item) },
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ painter = painterResource(id = item.iconRes),
+ contentDescription = stringResource(id = item.titleRes),
+ modifier = modifier.padding(start = 20.dp, end = 20.dp),
+ tint = colorResource(id = R.color.icon_secondary),
+ )
+ Column(modifier = modifier.padding(end = 20.dp)) {
+ Text(
+ text = stringResource(id = item.titleRes),
+ modifier = modifier,
+ style = TangemTypography.subtitle1,
+ color = colorResource(id = R.color.text_primary_1),
+ )
+ if (item == SettingsElement.AppCurrency) {
+ Text(
+ text = appCurrency,
+ modifier = modifier,
+ style = TangemTypography.body2,
+ color = colorResource(id = R.color.text_secondary),
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun TangemSocialAccounts(
+ links: List,
+ onSocialNetworkClick: (String) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ LazyRow(
+ modifier = modifier.padding(start = 8.dp, end = 8.dp),
+ ) {
+ items(links) {
+ Icon(
+ painter = painterResource(id = it.iconRes),
+ contentDescription = "",
+ modifier = modifier
+ .padding(8.dp)
+ .clickable { onSocialNetworkClick(it.url) },
+ tint = colorResource(id = R.color.icon_informative),
+ )
+ }
+ }
+}
+
+@Composable
+@Preview
+fun Preview() {
+ DetailsScreen(
+ state = DetailsScreenState(
+ elements = SettingsElement.values().toList(),
+ tangemLinks = TangemSocialAccounts.accountsEn,
+ tangemVersion = "Tangem 2.14.12 (343)",
+ appCurrency = "Dollar",
+ onItemsClick = {}, onSocialNetworkClick = {},
+ ),
+ onBackPressed = {},
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt
new file mode 100644
index 0000000000..1962a53261
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt
@@ -0,0 +1,57 @@
+package com.tangem.tap.features.details.ui.details
+
+import com.tangem.wallet.R
+
+data class DetailsScreenState(
+ val elements: List,
+ val tangemLinks: List,
+ val tangemVersion: String,
+ val appCurrency: String,
+ val onItemsClick: (SettingsElement) -> Unit,
+ val onSocialNetworkClick: (String) -> Unit,
+) {
+ val appNameRes: Int = R.string.app_name
+}
+
+enum class SettingsElement(
+ val iconRes: Int,
+ val titleRes: Int,
+) {
+ WalletConnect(R.drawable.ic_walletconnect, R.string.wallet_connect_title),
+ Chat(R.drawable.ic_chat, R.string.details_ask_a_question),
+ SendFeedback(R.drawable.ic_comment, R.string.details_row_title_send_feedback),
+ CardSettings(R.drawable.ic_card_settings, R.string.card_settings_title),
+ AppCurrency(R.drawable.ic_currency, R.string.details_row_title_currency),
+ AppSettings(R.drawable.ic_settings, R.string.app_settings_title),
+ LinkMoreCards(R.drawable.ic_more_cards, R.string.details_row_title_create_backup),
+ TermsOfService(R.drawable.ic_text, R.string.disclaimer_title), // General Terms of Service of the App
+ TermsOfUse(R.drawable.ic_text, R.string.details_row_title_card_tou), // Terms of Use for S2C cards only
+ PrivacyPolicy(R.drawable.ic_lock, R.string.details_row_privacy_policy),
+ ;
+}
+
+data class TangemLink(
+ val iconRes: Int,
+ val url: String,
+)
+
+object TangemSocialAccounts {
+ val accountsEn: List = listOf(
+ TangemLink(R.drawable.ic_telegram, "https://t.me/TangemCards"),
+ TangemLink(R.drawable.ic_twitter, "https://twitter.com/tangem"),
+ TangemLink(R.drawable.ic_facebook, "https://m.facebook.com/TangemCards/"),
+ TangemLink(R.drawable.ic_instagram, "https://instagram.com/tangemcards"),
+ TangemLink(R.drawable.ic_github, "https://github.com/tangem"),
+ TangemLink(R.drawable.ic_youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
+ TangemLink(R.drawable.ic_linkedin, "https://www.linkedin.com/company/tangem"),
+ )
+ val accountsRu: List = listOf(
+ TangemLink(R.drawable.ic_telegram, "https://t.me/tangem_ru"),
+ TangemLink(R.drawable.ic_twitter, "https://twitter.com/tangem"),
+ TangemLink(R.drawable.ic_facebook, "https://m.facebook.com/TangemCards/"),
+ TangemLink(R.drawable.ic_instagram, "https://instagram.com/tangemcards"),
+ TangemLink(R.drawable.ic_github, "https://github.com/tangem"),
+ TangemLink(R.drawable.ic_youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
+ TangemLink(R.drawable.ic_linkedin, "https://www.linkedin.com/company/tangem"),
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt
new file mode 100644
index 0000000000..97ce019a84
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt
@@ -0,0 +1,104 @@
+package com.tangem.tap.features.details.ui.details
+
+import com.tangem.domain.common.TapWorkarounds.isStart2Coin
+import com.tangem.tap.common.feedback.FeedbackEmail
+import com.tangem.tap.common.feedback.SupportInfo
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.domain.extensions.isMultiwalletAllowed
+import com.tangem.tap.features.details.redux.DetailsAction
+import com.tangem.tap.features.details.redux.DetailsState
+import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
+import com.tangem.tap.features.home.LocaleRegionProvider
+import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.wallet.BuildConfig
+import org.rekotlin.Store
+
+class DetailsViewModel(private val store: Store) {
+ fun updateState(state: DetailsState): DetailsScreenState {
+ val settings = SettingsElement.values().mapNotNull {
+ when (it) {
+ SettingsElement.WalletConnect -> {
+ if (state.scanResponse?.card?.isMultiwalletAllowed == true) it else null
+ }
+ SettingsElement.LinkMoreCards -> {
+ if (state.createBackupAllowed) it else null
+ }
+ SettingsElement.PrivacyPolicy -> {
+ if (state.privacyPolicyUrl != null) it else null
+ }
+ SettingsElement.AppSettings -> null // TODO: until we implement settings from this screen
+ SettingsElement.AppCurrency -> if (state.scanResponse?.card?.isMultiwalletAllowed != true) it else null
+ SettingsElement.TermsOfUse -> if (state.scanResponse?.card?.isStart2Coin == true) it else null
+ else -> it
+ }
+ }
+
+ return DetailsScreenState(
+ settings,
+ tangemLinks = getSocialLinks(),
+ tangemVersion = getTangemAppVersion(),
+ appCurrency = state.appCurrency.name,
+ onItemsClick = { handleClickingSettingsItem(it) },
+ onSocialNetworkClick = { handleSocialNetworkClick(it) },
+ )
+ }
+
+ private fun handleSocialNetworkClick(url: String) {
+ store.dispatch(NavigationAction.OpenUrl(url))
+ }
+
+ private fun handleClickingSettingsItem(item: SettingsElement) {
+ when (item) {
+ SettingsElement.WalletConnect -> {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
+ }
+ SettingsElement.Chat -> {
+ store.dispatch(GlobalAction.OpenChat(SupportInfo()))
+ }
+ SettingsElement.SendFeedback -> {
+ store.dispatch(GlobalAction.SendEmail(FeedbackEmail()))
+ }
+ SettingsElement.CardSettings -> {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.CardSettings))
+ }
+ SettingsElement.AppCurrency -> {
+ store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
+ }
+ SettingsElement.AppSettings -> {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.AppSettings)) //TODO: To be available later
+ }
+ SettingsElement.LinkMoreCards -> {
+ store.dispatch(DetailsAction.CreateBackup)
+ }
+ SettingsElement.TermsOfService -> {
+ store.dispatch(DisclaimerAction.ShowAcceptedDisclaimer)
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
+ }
+ SettingsElement.TermsOfUse -> {
+ store.dispatch(DetailsAction.ShowDisclaimer)
+ }
+ SettingsElement.PrivacyPolicy -> {
+ // TODO: To be available later
+ }
+ }
+ }
+
+ private fun getSocialLinks(): List {
+ val locale = LocaleRegionProvider().getRegion()
+ return if (locale.lowercase() == RUSSIA_COUNTRY_CODE) {
+ TangemSocialAccounts.accountsRu
+ } else {
+ TangemSocialAccounts.accountsEn
+ }
+ }
+
+ private fun getTangemAppVersion(): String {
+ val versionCode: Int = BuildConfig.VERSION_CODE
+ val versionName: String = BuildConfig.VERSION_NAME
+ return "$versionName ($versionCode)"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt
new file mode 100644
index 0000000000..fa63874859
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt
@@ -0,0 +1,69 @@
+package com.tangem.tap.features.details.ui.resetcard
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import androidx.transition.TransitionInflater
+import com.google.accompanist.appcompattheme.AppCompatTheme
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.details.redux.DetailsState
+import com.tangem.tap.store
+import org.rekotlin.StoreSubscriber
+
+class ResetCardFragment : Fragment(), StoreSubscriber {
+
+ private val viewModel = ResetCardViewModel(store)
+
+ private var screenState: MutableState =
+ mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState))
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(android.R.transition.fade)
+ exitTransition = inflater.inflateTransition(android.R.transition.fade)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ return ComposeView(requireContext()).apply {
+ setContent {
+ isTransitionGroup = true
+ AppCompatTheme {
+ ResetCardScreen(
+ state = screenState.value,
+ onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) },
+ )
+ }
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.detailsState == newState.detailsState
+ }.select { it.detailsState }
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ store.unsubscribe(this)
+ }
+
+ override fun newState(state: DetailsState) {
+ if (activity == null || view == null) return
+ screenState.value = viewModel.updateState(state.cardSettingsState)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt
new file mode 100644
index 0000000000..2c07b092ad
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt
@@ -0,0 +1,144 @@
+package com.tangem.tap.features.details.ui.resetcard
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Icon
+import androidx.compose.material.IconToggleButton
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.tangem.tap.common.compose.TangemTypography
+import com.tangem.tap.features.details.ui.common.DetailsMainButton
+import com.tangem.tap.features.details.ui.common.ScreenTitle
+import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
+import com.tangem.wallet.R
+
+@Composable
+fun ResetCardScreen(
+ state: ResetCardScreenState,
+ onBackPressed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Box(modifier = modifier.background(colorResource(id = R.color.background_primary))) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_reset_background),
+ contentDescription = "",
+ modifier = modifier.offset(y = (-16).dp),
+ )
+ SettingsScreensScaffold(
+ content = { ResetCardView(state = state, modifier = modifier) },
+ onBackClick = onBackPressed,
+ backgroundColor = Color.Transparent,
+ )
+ }
+}
+
+@Composable
+fun ResetCardView(
+ state: ResetCardScreenState,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.Bottom,
+ ) {
+ Box(
+ modifier = modifier,
+ ) {
+ ScreenTitle(titleRes = R.string.reset_card_to_factory_navigation_title)
+ }
+ Spacer(
+ modifier = modifier
+ .defaultMinSize(20.dp)
+ .weight(1f),
+ )
+ Text(
+ text = stringResource(id = R.string.common_attention),
+ modifier = modifier.padding(start = 20.dp, end = 20.dp),
+ style = TangemTypography.headline3,
+ color = colorResource(id = R.color.text_primary_1),
+ )
+
+ Spacer(modifier = modifier.size(24.dp))
+
+ Text(
+ text = stringResource(id = R.string.reset_card_to_factory_message),
+ modifier = modifier.padding(start = 20.dp, end = 20.dp),
+ style = TangemTypography.body1,
+ color = colorResource(id = R.color.text_secondary),
+ )
+
+ Spacer(modifier = modifier.size(44.dp))
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(end = 20.dp),
+ ) {
+ IconToggleButton(
+ checked = state.accepted,
+ onCheckedChange = state.onAcceptWarningToggleClick,
+ modifier = modifier.padding(start = 20.dp, end = 20.dp),
+ ) {
+ Icon(
+ painter = painterResource(
+ if (state.accepted) {
+ R.drawable.ic_accepted
+ } else {
+ R.drawable.ic_unticked
+ },
+ ),
+ contentDescription = null,
+ tint = if (state.accepted) {
+ colorResource(id = R.color.icon_accent)
+ } else {
+ colorResource(id = R.color.icon_secondary)
+ },
+ )
+ }
+ Text(
+ text = stringResource(id = R.string.reset_card_to_factory_warning_message),
+ style = TangemTypography.body2,
+ color = colorResource(id = R.color.text_secondary),
+ )
+ }
+
+ Spacer(modifier = modifier.size(32.dp))
+ Box(
+ modifier = modifier
+ .padding(start = 16.dp, end = 16.dp, bottom = 32.dp),
+ ) {
+ DetailsMainButton(
+ title = stringResource(id = R.string.reset_card_to_factory_button_title),
+ onClick = state.onResetButtonClick,
+ enabled = state.resetButtonEnabled,
+ )
+ }
+
+ }
+}
+
+@Composable
+@Preview
+fun ResetCardScreenPreview(
+
+) {
+ ResetCardScreen(state = ResetCardScreenState(onAcceptWarningToggleClick = {}, accepted = true) {}, {})
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt
new file mode 100644
index 0000000000..f658eac463
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt
@@ -0,0 +1,10 @@
+package com.tangem.tap.features.details.ui.resetcard
+
+data class ResetCardScreenState(
+ val accepted: Boolean = false,
+ val onAcceptWarningToggleClick: (Boolean) -> Unit,
+ val onResetButtonClick: () -> Unit,
+) {
+ val resetButtonEnabled: Boolean
+ get() = accepted
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt
new file mode 100644
index 0000000000..15f891dba3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt
@@ -0,0 +1,17 @@
+package com.tangem.tap.features.details.ui.resetcard
+
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.features.details.redux.CardSettingsState
+import com.tangem.tap.features.details.redux.DetailsAction
+import org.rekotlin.Store
+
+class ResetCardViewModel(private val store: Store) {
+
+ fun updateState(state: CardSettingsState?): ResetCardScreenState {
+ return ResetCardScreenState(
+ accepted = state?.resetConfirmed ?: false,
+ onAcceptWarningToggleClick = { store.dispatch(DetailsAction.ResetToFactory.Confirm(it)) },
+ onResetButtonClick = { store.dispatch(DetailsAction.ResetToFactory.Proceed) },
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt
new file mode 100644
index 0000000000..96d55a808a
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt
@@ -0,0 +1,69 @@
+package com.tangem.tap.features.details.ui.securitymode
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import androidx.transition.TransitionInflater
+import com.google.accompanist.appcompattheme.AppCompatTheme
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.details.redux.DetailsState
+import com.tangem.tap.store
+import org.rekotlin.StoreSubscriber
+
+class SecurityModeFragment : Fragment(), StoreSubscriber {
+
+ private val viewModel = SecurityModeViewModel(store)
+
+ private var screenState: MutableState =
+ mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState?.manageSecurityState))
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(android.R.transition.fade)
+ exitTransition = inflater.inflateTransition(android.R.transition.fade)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ return ComposeView(requireContext()).apply {
+ setContent {
+ isTransitionGroup = true
+ AppCompatTheme {
+ SecurityModeScreen(
+ state = screenState.value,
+ onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) },
+ )
+ }
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.detailsState == newState.detailsState
+ }.select { it.detailsState }
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ store.unsubscribe(this)
+ }
+
+ override fun newState(state: DetailsState) {
+ if (activity == null || view == null) return
+ screenState.value = viewModel.updateState(state.cardSettingsState?.manageSecurityState)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt
new file mode 100644
index 0000000000..e18146767f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreen.kt
@@ -0,0 +1,132 @@
+package com.tangem.tap.features.details.ui.securitymode
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.material.RadioButton
+import androidx.compose.material.RadioButtonDefaults
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.tangem.tap.common.compose.TangemTypography
+import com.tangem.tap.features.details.redux.SecurityOption
+import com.tangem.tap.features.details.ui.common.DetailsMainButton
+import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
+import com.tangem.wallet.R
+
+@Composable
+fun SecurityModeScreen(
+ state: SecurityModeScreenState,
+ onBackPressed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ SettingsScreensScaffold(
+ content = { SecurityModeOptions(state = state, modifier = modifier) },
+ titleRes = R.string.card_settings_security_mode,
+ onBackClick = onBackPressed,
+ )
+}
+
+@Composable
+fun SecurityModeOptions(
+ state: SecurityModeScreenState,
+ modifier: Modifier = Modifier,
+) {
+
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(bottom = 28.dp)
+ .offset(y = (-16).dp),
+ verticalArrangement = Arrangement.SpaceBetween,
+ ) {
+ state.availableOptions.map {
+ SecurityOption(option = it, state = state, modifier = modifier)
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ DetailsMainButton(
+ title = stringResource(id = R.string.common_save_changes),
+ enabled = state.isSaveChangesEnabled,
+ onClick = state.onSaveChangesClicked,
+ modifier = modifier
+ .padding(start = 20.dp, end = 20.dp),
+ )
+ }
+}
+
+@Composable
+fun SecurityOption(
+ option: SecurityOption, state: SecurityModeScreenState,
+ modifier: Modifier,
+) {
+ val selected = option == state.selectedSecurityMode
+
+ val title = option.toTitleRes()
+
+ val subtitle = when (option) {
+ SecurityOption.LongTap -> R.string.details_manage_security_long_tap_description
+ SecurityOption.PassCode -> R.string.details_manage_security_passcode_description
+ SecurityOption.AccessCode -> R.string.details_manage_security_access_code_description
+ }
+
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .selectable(
+ selected = selected, onClick = { state.onNewModeSelected(option) },
+ )
+ .padding(start = 20.dp, end = 20.dp, top = 16.dp, bottom = 16.dp),
+ ) {
+
+ RadioButton(
+ selected = selected, onClick = null,
+ modifier = modifier.padding(end = 20.dp),
+ colors = RadioButtonDefaults.colors(
+ unselectedColor = colorResource(id = R.color.icon_secondary),
+ selectedColor = colorResource(id = R.color.icon_accent),
+ ),
+ )
+
+ Column {
+ Text(
+ text = stringResource(id = title),
+ style = TangemTypography.subtitle1,
+ color = colorResource(id = R.color.text_primary_1),
+ )
+ Spacer(modifier = modifier.size(4.dp))
+ Text(
+ text = stringResource(id = subtitle),
+ style = TangemTypography.body2,
+ color = colorResource(id = R.color.text_secondary),
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+fun SecurityModeScreenPreview() {
+ SecurityModeScreen(
+ state = SecurityModeScreenState(
+ availableOptions = SecurityOption.values().toList(),
+ selectedSecurityMode = SecurityOption.LongTap,
+ isSaveChangesEnabled = false,
+ onNewModeSelected = {},
+ onSaveChangesClicked = {},
+ ),
+ onBackPressed = {},
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreenState.kt
new file mode 100644
index 0000000000..97482c7c01
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeScreenState.kt
@@ -0,0 +1,20 @@
+package com.tangem.tap.features.details.ui.securitymode
+
+import com.tangem.tap.features.details.redux.SecurityOption
+import com.tangem.wallet.R
+
+data class SecurityModeScreenState(
+ val availableOptions: List,
+ val selectedSecurityMode: SecurityOption,
+ val isSaveChangesEnabled: Boolean,
+ val onNewModeSelected: (SecurityOption) -> Unit,
+ val onSaveChangesClicked: () -> Unit,
+)
+
+fun SecurityOption.toTitleRes(): Int {
+ return when (this) {
+ SecurityOption.LongTap -> R.string.details_manage_security_long_tap
+ SecurityOption.PassCode -> R.string.details_manage_security_passcode
+ SecurityOption.AccessCode -> R.string.details_manage_security_access_code
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt
new file mode 100644
index 0000000000..b25277864d
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeViewModel.kt
@@ -0,0 +1,27 @@
+package com.tangem.tap.features.details.ui.securitymode
+
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.features.details.redux.DetailsAction
+import com.tangem.tap.features.details.redux.ManageSecurityState
+import com.tangem.tap.features.details.redux.SecurityOption
+import org.rekotlin.Store
+
+class SecurityModeViewModel(val store: Store) {
+
+ fun updateState(state: ManageSecurityState?): SecurityModeScreenState {
+ if (state == null) return SecurityModeScreenState(
+ availableOptions = emptyList(),
+ selectedSecurityMode = SecurityOption.LongTap,
+ isSaveChangesEnabled = false,
+ onNewModeSelected = {},
+ onSaveChangesClicked = {},
+ )
+ return SecurityModeScreenState(
+ availableOptions = state.allowedOptions.toList(),
+ selectedSecurityMode = state.selectedOption,
+ isSaveChangesEnabled = state.selectedOption != state.currentOption,
+ onNewModeSelected = { store.dispatch(DetailsAction.ManageSecurity.SelectOption(it)) },
+ onSaveChangesClicked = { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) },
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt
new file mode 100644
index 0000000000..8d3de292ae
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt
@@ -0,0 +1,76 @@
+package com.tangem.tap.features.details.ui.walletconnect
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import androidx.transition.TransitionInflater
+import com.google.accompanist.appcompattheme.AppCompatTheme
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
+import com.tangem.tap.store
+import org.rekotlin.StoreSubscriber
+
+class WalletConnectFragment : Fragment(), StoreSubscriber {
+ private val viewModel = WalletConnectViewModel(store)
+ private var screenState: MutableState =
+ mutableStateOf(viewModel.updateState(store.state.walletConnectState))
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val inflater = TransitionInflater.from(requireContext())
+ enterTransition = inflater.inflateTransition(android.R.transition.fade)
+ exitTransition = inflater.inflateTransition(android.R.transition.fade)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ return ComposeView(requireContext()).apply {
+ setContent {
+ isTransitionGroup = true
+ AppCompatTheme {
+ WalletConnectScreen(
+ state = screenState.value,
+ onBackPressed = {
+ if (screenState.value.isLoading) {
+ store.dispatch(
+ WalletConnectAction.FailureEstablishingSession(
+ store.state.walletConnectState.newSessionData?.session?.session,
+ ),
+ )
+ }
+ store.dispatch(NavigationAction.PopBackTo())
+ },
+ )
+ }
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ store.subscribe(this) { state ->
+ state.skipRepeats { oldState, newState ->
+ oldState.walletConnectState == newState.walletConnectState
+ }.select { it.walletConnectState }
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ store.unsubscribe(this)
+ }
+
+ override fun newState(state: WalletConnectState) {
+ if (activity == null || view == null) return
+ screenState.value = viewModel.updateState(state)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt
new file mode 100644
index 0000000000..71a9386b36
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt
@@ -0,0 +1,180 @@
+package com.tangem.tap.features.details.ui.walletconnect
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.FloatingActionButton
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.LinearProgressIndicator
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.tangem.tap.common.compose.TangemTypography
+import com.tangem.tap.common.extensions.getFromClipboard
+import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
+import com.tangem.wallet.R
+
+@Composable
+fun WalletConnectScreen(
+ state: WalletConnectScreenState,
+ onBackPressed: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val context = LocalContext.current
+
+ SettingsScreensScaffold(
+ content = {
+ if (state.sessions.isEmpty()) {
+ EmptyScreen(state, modifier)
+ } else {
+ WalletConnectSessions(state, modifier)
+ }
+ },
+ fab = {
+ if (!state.isLoading) {
+ AddSessionFab(onAddSession = { state.onAddSession(context.getFromClipboard()?.toString()) })
+ }
+ },
+ titleRes = R.string.wallet_connect_title,
+ onBackClick = onBackPressed,
+ )
+}
+
+@Composable
+private fun AddSessionFab(
+ onAddSession: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ FloatingActionButton(
+ onClick = onAddSession,
+ backgroundColor = colorResource(id = R.color.button_primary),
+ contentColor = colorResource(id = R.color.icon_primary_2),
+ shape = RoundedCornerShape(16.dp),
+ modifier = modifier,
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_plus),
+ contentDescription = "",
+ )
+ }
+}
+
+@Composable
+private fun EmptyScreen(state: WalletConnectScreenState, modifier: Modifier = Modifier) {
+ if (state.isLoading) {
+ LinearProgressIndicator(
+ modifier = modifier.fillMaxWidth(),
+ color = colorResource(id = R.color.icon_accent),
+ )
+ }
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(bottom = 64.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_walletconnect),
+ contentDescription = "",
+ colorFilter = ColorFilter.tint(colorResource(id = R.color.icon_inactive)),
+ contentScale = ContentScale.FillWidth,
+ modifier = modifier
+ .width(width = 100.dp),
+ )
+ Spacer(modifier = modifier.size(24.dp))
+ Text(
+ text = stringResource(id = R.string.wallet_connect_subtitle),
+ style = TangemTypography.body2,
+ color = colorResource(id = R.color.text_tertiary),
+ )
+ }
+}
+
+@Composable
+private fun WalletConnectSessions(
+ state: WalletConnectScreenState,
+ modifier: Modifier = Modifier,
+) {
+ if (state.isLoading) {
+ LinearProgressIndicator(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(2.dp),
+ color = colorResource(id = R.color.icon_accent),
+ )
+ } else {
+ Spacer(modifier = modifier.height(2.dp))
+ }
+
+ LazyColumn(
+ modifier = modifier.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ items(state.sessions) { session ->
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp, vertical = 16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Column(modifier = modifier) {
+ Text(
+ text = session.description,
+ style = TangemTypography.subtitle1,
+ color = colorResource(id = R.color.text_primary_1),
+ )
+ }
+ IconButton(
+ onClick = { state.onRemoveSession(session.sessionId) },
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_cross_rounded_24),
+ contentDescription = "",
+ tint = colorResource(id = R.color.icon_warning),
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+@Preview
+fun WalletConnectScreenPreview() {
+ WalletConnectScreen(
+ state = WalletConnectScreenState(
+ sessions = listOf(
+ WcSessionForScreen(
+ description = "session from some dApp",
+ cardId = "12312312321",
+ sessionId = "",
+ ),
+ ),
+ isLoading = true,
+ ),
+ {},
+ )
+}
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt
new file mode 100644
index 0000000000..44792f9623
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt
@@ -0,0 +1,27 @@
+package com.tangem.tap.features.details.ui.walletconnect
+
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
+
+data class WalletConnectScreenState(
+ val sessions: List,
+ val isLoading: Boolean = false,
+ val onRemoveSession: (String) -> Unit = {},
+ val onAddSession: (String?) -> Unit = {},
+)
+
+data class WcSessionForScreen(
+ val description: String,
+ val cardId: String,
+ val sessionId: String,
+) {
+ companion object {
+ fun fromSession(session: WalletConnectSession): WcSessionForScreen {
+ return WcSessionForScreen(
+ description = session.peerMeta.name,
+ cardId = session.wallet.cardId,
+ sessionId = session.session.toUri(),
+ )
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectSessionsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectSessionsFragment.kt
deleted file mode 100644
index b019dc95e7..0000000000
--- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectSessionsFragment.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-package com.tangem.tap.features.details.ui.walletconnect
-
-import android.os.Bundle
-import android.view.View
-import androidx.activity.OnBackPressedCallback
-import androidx.fragment.app.Fragment
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.transition.TransitionInflater
-import by.kirich1409.viewbindingdelegate.viewBinding
-import com.tangem.tap.common.extensions.show
-import com.tangem.tap.common.redux.navigation.NavigationAction
-import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
-import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
-import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
-import com.tangem.tap.store
-import com.tangem.wallet.R
-import com.tangem.wallet.databinding.FragmentWalletConnectSessionsBinding
-import org.rekotlin.StoreSubscriber
-
-
-class WalletConnectSessionsFragment : Fragment(R.layout.fragment_wallet_connect_sessions),
- StoreSubscriber {
-
- private lateinit var walletConnectSessionsAdapter: WalletConnectSessionsAdapter
- private val binding: FragmentWalletConnectSessionsBinding by viewBinding(
- FragmentWalletConnectSessionsBinding::bind
- )
-
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- activity?.onBackPressedDispatcher?.addCallback(
- this, object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- store.dispatch(NavigationAction.PopBackTo())
- }
- })
- val inflater = TransitionInflater.from(requireContext())
- enterTransition = inflater.inflateTransition(R.transition.slide_right)
- exitTransition = inflater.inflateTransition(R.transition.fade)
- }
-
- override fun onStart() {
- super.onStart()
- store.subscribe(this) { state ->
- state.skipRepeats { oldState, newState ->
- oldState.walletConnectState == newState.walletConnectState
- }.select { it.walletConnectState }
- }
- }
-
- override fun onStop() {
- super.onStop()
- store.unsubscribe(this)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- binding.toolbar.setNavigationOnClickListener {
- store.dispatch(NavigationAction.PopBackTo())
- }
- setOnClickListeners()
- setupTransactionsRecyclerView()
- }
-
- private fun setOnClickListeners() {
- binding.fabOpenSession.setOnClickListener {
- store.dispatch(WalletConnectAction.StartWalletConnect(activity = requireActivity()))
- }
- }
-
- private fun setupTransactionsRecyclerView() = with(binding) {
- walletConnectSessionsAdapter = WalletConnectSessionsAdapter()
- rvWalletConnectSessions.layoutManager = LinearLayoutManager(requireContext())
- rvWalletConnectSessions.adapter = walletConnectSessionsAdapter
- walletConnectSessionsAdapter.submitList(store.state.walletConnectState.sessions)
- }
-
- override fun newState(state: WalletConnectState) {
- if (activity == null || view == null) return
-
- binding.fabOpenSession.show(!state.loading)
- binding.pbWalletConnect.show(state.loading)
-
- showUpdatedList(state.sessions)
- }
-
- private fun showUpdatedList(sessions: List) = with(binding) {
- walletConnectSessionsAdapter.submitList(sessions)
- rvWalletConnectSessions.show(sessions.isNotEmpty())
- tvNoSessions.show(sessions.isEmpty())
- tvNoSessionsTitle.show(sessions.isEmpty())
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt
new file mode 100644
index 0000000000..6bbde4c1ae
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt
@@ -0,0 +1,26 @@
+package com.tangem.tap.features.details.ui.walletconnect
+
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
+import org.rekotlin.Store
+
+class WalletConnectViewModel(private val store: Store) {
+ fun updateState(state: WalletConnectState): WalletConnectScreenState {
+ return WalletConnectScreenState(
+ state.sessions.map { wcSession -> WcSessionForScreen.fromSession(wcSession) },
+ isLoading = state.loading,
+ onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, state.sessions) },
+ onAddSession = { copiedUri -> store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) },
+ )
+ }
+
+ private fun onRemoveSession(sessionUri: String, sessions: List) {
+ sessions
+ .firstOrNull { it.session.toUri() == sessionUri }
+ ?.let { baseSession ->
+ store.dispatch(WalletConnectAction.DisconnectSession(baseSession.session))
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt
index 84b9151436..b9fd651bb8 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
+import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
@@ -10,26 +11,33 @@ import com.tangem.wallet.R
class ApproveWcSessionDialog {
companion object {
- fun create(session: WalletConnectSession, context: Context): AlertDialog {
+ fun create(session: WalletConnectSession, networks: List, context: Context): AlertDialog {
val message = context.getString(
R.string.wallet_connect_request_session_start,
session.wallet.cardId,
session.peerMeta.name,
- session.peerMeta.url
+ session.peerMeta.url,
)
return AlertDialog.Builder(context).apply {
setTitle(context.getString(R.string.wallet_connect))
setMessage(message)
setPositiveButton(context.getText(R.string.common_start)) { _, _ ->
- store.dispatch(WalletConnectAction.ApproveSession(
- session.session
- ))
+ store.dispatch(GlobalAction.HideDialog)
+ store.dispatch(WalletConnectAction.ChooseNetwork(session.wallet.blockchain!!))
+ }
+ if (networks.size > 1) {
+ setNeutralButton(context.getText(R.string.wallet_connect_select_network)) { _, _ ->
+ store.dispatch(GlobalAction.HideDialog)
+ store.dispatch(WalletConnectAction.SelectNetwork(session = session, networks = networks))
+ }
}
setNegativeButton(context.getText(R.string.common_reject)) { _, _ ->
+ store.dispatch(GlobalAction.HideDialog)
store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session))
}
- setOnDismissListener {
+ setOnCancelListener {
store.dispatch(GlobalAction.HideDialog)
+ store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session))
}
}.create()
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ChooseNetworkDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ChooseNetworkDialog.kt
new file mode 100644
index 0000000000..8e0ffe0f4a
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ChooseNetworkDialog.kt
@@ -0,0 +1,38 @@
+package com.tangem.tap.features.details.ui.walletconnect.dialogs
+
+import android.content.Context
+import androidx.appcompat.app.AlertDialog
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
+import com.tangem.tap.store
+import com.tangem.wallet.R
+
+object ChooseNetworkDialog {
+ fun create(
+ session: WalletConnectSession,
+ networks: List,
+ context: Context,
+ ): AlertDialog {
+ return AlertDialog.Builder(context)
+ .setTitle(context.getString(R.string.wallet_connect_select_network))
+ .setNegativeButton(context.getString(R.string.common_cancel)) { _, _ ->
+ store.dispatch(WalletConnectAction.FailureEstablishingSession(session.session))
+ }
+ .setOnDismissListener {
+ store.dispatch(GlobalAction.HideDialog)
+ }
+ .setSingleChoiceItems(networks.map { it.fullName }.toTypedArray(), 0) { _, which ->
+ networks.getOrNull(which)?.let { selectedBlockchain ->
+ store.dispatch(
+ WalletConnectAction.ChooseNetwork(
+ blockchain = selectedBlockchain,
+ ),
+ )
+ store.dispatch(GlobalAction.HideDialog)
+ }
+ }
+ .create()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/SimpleAlertDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/SimpleAlertDialog.kt
deleted file mode 100644
index f4efd59532..0000000000
--- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/SimpleAlertDialog.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.tangem.tap.features.details.ui.walletconnect.dialogs
-
-import android.content.Context
-import androidx.appcompat.app.AlertDialog
-import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.store
-import com.tangem.wallet.R
-
-class SimpleAlertDialog {
- companion object {
- fun create(
- titleRes: Int,
- messageRes: Int,
- buttonRes: Int = R.string.common_ok,
- context: Context,
- ): AlertDialog {
-
- return AlertDialog.Builder(context).apply {
- setTitle(context.getString(titleRes))
- setMessage(context.getText(messageRes))
- setPositiveButton(context.getText(buttonRes)) { _, _ -> }
- setOnDismissListener {
- store.dispatch(GlobalAction.HideDialog)
- }
- }.create()
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt
index ad5a091c99..e69de29bb2 100644
--- a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt
+++ b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt
@@ -1,380 +0,0 @@
-package com.tangem.tap.features.feedback
-
-import android.app.Activity
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.Build
-import com.tangem.Log
-import com.tangem.TangemSdkLogger
-import com.tangem.blockchain.common.*
-import com.tangem.domain.common.ScanResponse
-import com.tangem.domain.common.TapWorkarounds
-import com.tangem.tap.common.extensions.sendEmail
-import com.tangem.tap.common.extensions.stripZeroPlainString
-import com.tangem.wallet.R
-import timber.log.Timber
-import java.io.File
-import java.io.FileWriter
-import java.io.StringWriter
-import java.text.SimpleDateFormat
-import java.util.*
-
-
-/**
-[REDACTED_AUTHOR]
- */
-class FeedbackManager(
- val infoHolder: AdditionalEmailInfo,
- private val logCollector: TangemLogCollector,
-) {
-
- private lateinit var activity: Activity
-
- fun updateActivity(activity: Activity) {
- this.activity = activity
- }
-
- fun send(emailData: EmailData, onFail: ((Exception) -> Unit)? = null) {
- if (!this::activity.isInitialized) return
-
- emailData.prepare(infoHolder)
- val fileLog = if (emailData is ScanFailsEmail) createLogFile() else null
- activity.sendEmail(
- email = getSupportEmail(),
- subject = activity.getString(emailData.subjectResId),
- message = emailData.joinTogether(activity, infoHolder),
- file = fileLog,
- onFail = onFail
- )
- }
-
- private fun getSupportEmail(): String {
- return if (TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)) {
- S2C_SUPPORT_EMAIL
- } else {
- DEFAULT_SUPPORT_EMAIL
- }
- }
-
- private fun createLogFile(): File? {
- return try {
- val file = File(activity.filesDir, "logs.txt")
- file.delete()
- file.createNewFile()
-
- val stringWriter = StringWriter()
- logCollector.getLogs().forEach { stringWriter.append(it) }
- val fileWriter = FileWriter(file)
- fileWriter.write(stringWriter.toString())
- fileWriter.close()
- logCollector.clearLogs()
- file
- } catch (ex: Exception) {
- Timber.e(ex, "Can't create a file for email attachment")
- null
- }
- }
-
- companion object {
- const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com"
- const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com"
- }
-}
-
-class TangemLogCollector : TangemSdkLogger {
- private val dateFormatter = SimpleDateFormat("HH:mm:ss.SSS")
- private val logs = mutableListOf()
- private val mutex = Object()
-
- override fun log(message: () -> String, level: Log.Level) {
- val time = dateFormatter.format(Date())
- synchronized(mutex) {
- logs.add("$time: ${message()}\n")
- }
- }
-
- fun getLogs(): List = synchronized(mutex) { logs.toList() }
-
- fun clearLogs() {
- synchronized(mutex) { logs.clear() }
- }
-}
-
-class AdditionalEmailInfo {
- class EmailWalletInfo(
- var blockchain: Blockchain = Blockchain.Unknown,
- var address: String = "",
- var explorerLink: String = "",
- var host: String = "",
- var derivationPath: String = "",
- )
-
- var appVersion: String = ""
-
- // card
- var cardId: String = ""
- var cardFirmwareVersion: String = ""
- var cardIssuer: String = ""
- var cardBlockchain: String = ""
-
- // wallets
- internal val walletsInfo = mutableListOf()
- internal val tokens = mutableMapOf>()
- internal var onSendErrorWalletInfo: EmailWalletInfo? = null
- var signedHashesCount: String = ""
-
- // device
- var phoneModel: String = Build.MODEL
- var osVersion: String = Build.VERSION.SDK_INT.toString()
-
- // send error
- var destinationAddress: String = ""
- var amount: String = ""
- var fee: String = ""
- var token: String = ""
-
- fun setAppVersion(context: Context) {
- try {
- val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
- appVersion = pInfo.versionName
- } catch (e: PackageManager.NameNotFoundException) {
- e.printStackTrace()
- }
- }
-
- fun setCardInfo(data: ScanResponse) {
- cardId = data.card.cardId
- cardBlockchain = data.walletData?.blockchain ?: ""
- cardFirmwareVersion = data.card.firmwareVersion.stringValue
- cardIssuer = data.card.issuer.name
- signedHashesCount = data.card.wallets
- .joinToString("; ") { "${it.curve.curve} - ${it.totalSignedHashes}" }
- }
-
- fun setWalletsInfo(walletManagers: List) {
- walletsInfo.clear()
- tokens.clear()
- walletManagers.forEach { manager ->
- walletsInfo.add(
- EmailWalletInfo(
- blockchain = manager.wallet.blockchain,
- address = getAddress(manager.wallet),
- explorerLink = getExploreUri(manager.wallet),
- host = manager.currentHost,
- derivationPath = manager.wallet.publicKey.derivationPath?.rawPath ?: ""
- )
- )
- if (manager.cardTokens.isNotEmpty()) {
- tokens[manager.wallet.blockchain] = manager.cardTokens
- }
- }
- }
-
- fun updateOnSendError(wallet: Wallet, host: String, amountToSend: Amount, feeAmount: Amount, destinationAddress: String) {
- onSendErrorWalletInfo = EmailWalletInfo(
- blockchain = wallet.blockchain,
- address = getAddress(wallet),
- explorerLink = getExploreUri(wallet),
- host = host,
- derivationPath = wallet.publicKey.derivationPath?.rawPath ?: ""
- )
-
- this.destinationAddress = destinationAddress
- amount = amountToSend.value?.stripZeroPlainString() ?: "0"
- fee = feeAmount.value?.stripZeroPlainString() ?: "0"
- token = if (amountToSend.type is AmountType.Token) amountToSend.currencySymbol else ""
- }
-
- private fun getAddress(wallet: Wallet): String {
- return if (wallet.addresses.size == 1) {
- wallet.address
- } else {
- val addresses = wallet.addresses.joinToString(", ") {
- "${it.type.javaClass.simpleName} - ${it.value}"
- }
- "Multiple address: $addresses"
- }
- }
-
- private fun getExploreUri(wallet: Wallet): String {
- return if (wallet.addresses.size == 1) {
- wallet.getExploreUrl(wallet.address)
- } else {
- val links = wallet.addresses.joinToString(", ") {
- "${it.type.javaClass.simpleName} - ${wallet.getExploreUrl(it.value)}"
- }
- "Multiple explorers links: $links"
- }
- }
-}
-
-interface EmailData {
- val subjectResId: Int
- val mainMessageResId: Int
-
- fun getDataCollectionMessageResId(): Int = R.string.feedback_data_collection_message
-
- fun prepare(infoHolder: AdditionalEmailInfo) {}
-
- fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String
-
- fun joinTogether(context: Context, infoHolder: AdditionalEmailInfo): String {
- return StringBuilder().apply {
- append(context.getString(mainMessageResId))
- appendLine(3)
- append(context.getString(getDataCollectionMessageResId()))
- appendLine()
- append(createOptionalMessage(infoHolder))
- }.toString()
- }
-}
-
-class RateCanBeBetterEmail : EmailData {
- override val subjectResId: Int = R.string.feedback_subject_rate_negative
- override val mainMessageResId: Int = R.string.feedback_preface_rate_negative
-
- override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder)
- .appendCardInfo()
- .appendLine()
- .appendPhoneInfo()
- .build()
-}
-
-class ScanFailsEmail : EmailData {
-
- override val subjectResId: Int = R.string.feedback_subject_scan_failed
- override val mainMessageResId: Int = R.string.feedback_preface_scan_failed
-
- override fun joinTogether(context: Context, infoHolder: AdditionalEmailInfo): String = StringBuilder().apply {
- append(context.getString(mainMessageResId))
- appendLine(4)
- append(createOptionalMessage(infoHolder))
- }.toString()
-
- override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder)
- .appendPhoneInfo()
- .build()
-}
-
-class SendTransactionFailedEmail(
- val error: String
-) : EmailData {
-
- override val subjectResId: Int = R.string.feedback_subject_tx_failed
- override val mainMessageResId: Int = R.string.feedback_preface_tx_failed
-
- override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder)
- .appendCardInfo()
- .appendDelimiter()
- .appendTxFailedBlockchainInfo(error)
- .appendLine()
- .appendPhoneInfo()
- .build()
-}
-
-class FeedbackEmail : EmailData {
- override val subjectResId: Int
- get() = if (isS2CCard) s2cSubject else tangemSubject
- override val mainMessageResId: Int
- get() = if (isS2CCard) s2cMainMessage else tangemMainMessage
-
- private val tangemSubject: Int = R.string.feedback_subject_support_tangem
- private val tangemMainMessage: Int = R.string.feedback_preface_support
-
- private val s2cSubject: Int = R.string.feedback_subject_support
- private val s2cMainMessage: Int = R.string.feedback_preface_support
-
- private var isS2CCard = false
-
- override fun prepare(infoHolder: AdditionalEmailInfo) {
- isS2CCard = TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)
- }
-
- override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder)
- .appendCardInfo()
- .appendWalletsInfo()
- .appendLine()
- .appendPhoneInfo()
- .build()
-}
-
-
-class EmailDataBuilder(
- private val infoHolder: AdditionalEmailInfo
-) {
- val builder = StringBuilder()
-
- fun appendDelimiter(): EmailDataBuilder {
- builder.appendDelimiter()
- return this
- }
-
- fun appendLine(count: Int = 1): EmailDataBuilder {
- builder.appendLine(count)
- return this
- }
-
- fun appendCardInfo(): EmailDataBuilder {
- builder.appendKeyValue("Card ID", infoHolder.cardId)
- builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
- builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain)
- builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
- return this
- }
-
- fun appendWalletsInfo(): EmailDataBuilder {
- infoHolder.walletsInfo.forEach {
- builder.appendDelimiter()
- builder.appendKeyValue("Blockchain", it.blockchain.fullName)
- builder.appendKeyValue("Host", it.host)
- builder.appendKeyValue("Wallet address", it.address)
- builder.appendKeyValue("Derivation path", it.derivationPath)
- builder.appendKeyValue("Explorer link", it.explorerLink)
-
- infoHolder.tokens[it.blockchain]?.let { tokens ->
- builder.append("Tokens:")
- appendLine()
- tokens.forEach { token ->
- builder.appendKeyValue("Name", token.name)
- builder.appendKeyValue("ID", token.id ?: "[custom token]")
- builder.appendKeyValue("Contract address", token.contractAddress)
- }
- }
- }
- return this
- }
-
- fun appendTxFailedBlockchainInfo(error: String): EmailDataBuilder {
- val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalEmailInfo.EmailWalletInfo()
- builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
- builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
- builder.appendKeyValue("Host", walletInfo.host)
- builder.appendKeyValue("Token", infoHolder.token)
- builder.appendKeyValue("Error", error)
- builder.appendDelimiter()
- builder.appendKeyValue("Source address", walletInfo.address)
- builder.appendKeyValue("Destination address", infoHolder.destinationAddress)
- builder.appendKeyValue("Amount", infoHolder.amount)
- builder.appendKeyValue("Fee", infoHolder.fee)
- return this
- }
-
- fun appendPhoneInfo(): EmailDataBuilder {
- builder.appendKeyValue("Phone model", infoHolder.phoneModel)
- builder.appendKeyValue("OS version", infoHolder.osVersion)
- builder.appendKeyValue("App version", infoHolder.appVersion)
- return this
- }
-
- fun build(): String = builder.toString()
-}
-
-private fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder {
- return if (value.isNotBlank()) this.append("$key: $value\n") else this
-}
-
-private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n")
-
-private fun StringBuilder.appendLine(count: Int = 1): StringBuilder {
- return append(List(count) { "\n" }.joinToString(separator = ""))
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
index c453cdadc4..27953640ea 100644
--- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
@@ -1,10 +1,14 @@
package com.tangem.tap.features.home
import android.os.Bundle
+import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ComposeView
+import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
import com.google.accompanist.appcompattheme.AppCompatTheme
import com.tangem.tap.common.redux.navigation.AppScreen
@@ -15,48 +19,36 @@ import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.store
-import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber
-class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber {
+class HomeFragment : Fragment(), StoreSubscriber {
- var homeState: MutableState = mutableStateOf(store.state.homeState)
+ private var homeState: MutableState = mutableStateOf(store.state.homeState)
+ private var composeView: ComposeView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
store.dispatch(HomeAction.Init)
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View? {
+ val context = container?.context ?: return null
store.dispatch(BackupAction.CheckForUnfinishedBackup)
-
-
- getView()?.findViewById(R.id.cv_stories)?.setContent {
- AppCompatTheme {
- StoriesScreen(
- homeState,
- onScanButtonClick = { store.dispatch(HomeAction.ReadCard) },
- onShopButtonClick = {
- store.dispatch(HomeAction.GoToShop(store.state.globalState.userCountryCode))
- },
- onSearchTokensClick = {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
- store.dispatch(TokensAction.AllowToAddTokens(false))
- store.dispatch(TokensAction.LoadCurrencies())
- }
- )
+ composeView = ComposeView(context).apply {
+ setContent {
+ AppCompatTheme {
+ ScreenContent()
+ }
}
}
- }
- private fun getRegionProvider(): RegionProvider = RegionService(
- listOf(
-// TelephonyManagerRegionProvider(requireContext()),
- LocaleRegionProvider()
- )
- )
+ return composeView
+ }
override fun onStart() {
super.onStart()
@@ -72,6 +64,11 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber Unit
-) {
- val screenState = remember { mutableStateOf(StartingScreenState.INIT) }
- val progress = remember { Animatable(0f) }
-
- LaunchedEffect(paused) {
- if (paused) {
- progress.stop()
- } else {
- progress.animateTo(
- targetValue = 2f,
- animationSpec = tween(
- durationMillis = duration,
- easing = LinearEasing
- )
- )
- }
- }
-
- when (progress.value) {
- in 0f..0.2f -> screenState.value = StartingScreenState.INIT
- in 0.2f..0.3f -> screenState.value = StartingScreenState.BUY
- in 0.3f..0.4f -> screenState.value = StartingScreenState.STORE
- in 0.4f..0.5f -> screenState.value = StartingScreenState.SEND
- in 0.5f..0.6f -> screenState.value = StartingScreenState.PAY
- in 0.6f..0.7f -> screenState.value = StartingScreenState.EXCHANGE
- in 0.7f..0.8f -> screenState.value = StartingScreenState.BORROW
- in 0.8f..1f -> screenState.value = StartingScreenState.LEND
- in 1f..1.2f -> screenState.value = StartingScreenState.SHOW_CARD
- in 1.2f..2f -> screenState.value = StartingScreenState.MEET_TANGEM
- }
-
- if (screenState.value == StartingScreenState.INIT) hideContent(true)
- if (screenState.value == StartingScreenState.BUY) hideContent(false)
-
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
- modifier =
- Modifier
- .fillMaxSize(),
- ) {
-
- val text = when (screenState.value) {
- StartingScreenState.INIT -> null
- StartingScreenState.BUY -> R.string.story_meet_buy
- StartingScreenState.STORE -> R.string.story_meet_store
- StartingScreenState.SEND -> R.string.story_meet_send
- StartingScreenState.PAY -> R.string.story_meet_pay
- StartingScreenState.EXCHANGE -> R.string.story_meet_exchange
- StartingScreenState.BORROW -> R.string.story_meet_borrow
- StartingScreenState.LEND -> R.string.story_meet_lend
- StartingScreenState.SHOW_CARD -> R.string.story_meet_title
- StartingScreenState.MEET_TANGEM -> R.string.story_meet_title
- }
-
- val style = TextStyle(
- fontSize = 60.sp,
- fontWeight = FontWeight.SemiBold,
- color = Color.White,
- textAlign = TextAlign.Center,
- )
-
- if (screenState.value != StartingScreenState.MEET_TANGEM) {
- TextAutoSize(
- modifier = Modifier
- .padding(start = 20.dp, end = 20.dp, bottom = 100.dp)
- .alpha(if (screenState.value == StartingScreenState.SHOW_CARD) 0f else 1f),
- text = text?.let { stringResource(text) } ?: "",
- textStyle = style,
- fontSizeRange = FontSizeRange(20.sp, 60.sp)
- )
- }
-
- AnimatedVisibility(
- visible = screenState.value == StartingScreenState.MEET_TANGEM,
- enter = slideInVertically() { it / 2 }
- ) {
- TextAutoSize(
- modifier = Modifier
- .fillMaxWidth()
- .padding(start = 40.dp, end = 40.dp),
- text = text?.let { stringResource(text) } ?: "",
- textStyle = style,
- fontSizeRange = FontSizeRange(20.sp, 60.sp)
- )
- }
-
- AnimatedVisibility(
- visible = screenState.value == StartingScreenState.SHOW_CARD ||
- screenState.value == StartingScreenState.MEET_TANGEM,
- enter = scaleIn(initialScale = 3f)
- ) {
- Image(
- painter = painterResource(
- id = R.drawable.meet_tangem
- ),
- contentDescription = null,
- contentScale = ContentScale.FillWidth,
- modifier = Modifier.fillMaxWidth()
- )
- }
- }
-}
-
-enum class StartingScreenState {
- INIT, BUY, STORE, SEND, PAY, EXCHANGE, BORROW, LEND, SHOW_CARD, MEET_TANGEM
-}
-
-
-@Preview
-@Composable
-fun Stories1Preview() {
- FirstStoriesContent(false, 7_000) {}
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt
new file mode 100644
index 0000000000..58f9356349
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt
@@ -0,0 +1,152 @@
+package com.tangem.tap.features.home.compose
+
+import androidx.compose.animation.core.*
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.absoluteOffset
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import com.tangem.tap.common.compose.extensions.AnimatedValue
+import com.tangem.tap.common.compose.extensions.toAnimatable
+
+@Composable
+fun HorizontalSlidingImage(
+ painter: Painter,
+ paused: Boolean,
+ duration: Int,
+ itemSize: DpSize,
+ startOffset: Float,
+ targetOffset: Float,
+ contentDescription: String,
+) {
+ val translateX = AnimatedValue(startOffset * -1f, (startOffset + targetOffset) * -1f)
+
+ Image(
+ modifier = Modifier
+ .requiredWidth(itemSize.width)
+ .requiredHeight(itemSize.height)
+ .graphicsLayer(
+ translationX = translateX.toAnimatable(isPaused = paused, duration = duration).value
+ ),
+ alignment = Alignment.TopStart,
+ contentScale = ContentScale.FillBounds,
+ painter = painter,
+ contentDescription = contentDescription,
+ )
+}
+
+@Composable
+fun StoriesTextAnimation(
+ slideInDuration: Int = 500,
+ slideInDelay: Int = 200,
+ slideDistance: Dp = 60.dp,
+ label: String = "",
+ content: @Composable (Modifier) -> Unit
+) {
+ val isLaunched = remember { mutableStateOf(false) }
+ val transition = updateTransition(targetState = isLaunched.value, label = label)
+
+ val offsetY = transition.animateDp(
+ transitionSpec = {
+ tween(
+ durationMillis = slideInDuration,
+ delayMillis = slideInDelay,
+ easing = FastOutSlowInEasing
+ )
+ },
+ label = "Slide in"
+ ) { value -> if (value) 0.dp else slideDistance }
+
+ val alpha = transition.animateFloat(
+ transitionSpec = {
+ tween(
+ durationMillis = slideInDuration * 2,
+ delayMillis = slideInDelay,
+ easing = FastOutSlowInEasing
+ )
+ },
+ label = "Visibility"
+ ) { value -> if (value) 1f else 0f }
+
+ content(
+ Modifier
+ .absoluteOffset(y = offsetY.value)
+ .alpha(alpha.value)
+ )
+
+ LaunchedEffect(Unit) { isLaunched.value = true }
+}
+
+@Composable
+fun StoriesBottomImageAnimation(
+ initialScale: Float = 2.5f,
+ firstStepDuration: Int,
+ totalDuration: Int,
+ content: @Composable (Modifier) -> Unit
+) {
+ val scaleSwitchBarrier = 1.15f
+ val secondStepDuration = totalDuration - firstStepDuration
+
+ val isFirstStepLaunched = remember { mutableStateOf(false) }
+ val isSecondStepLaunched = remember { mutableStateOf(false) }
+
+ val firstTransition = updateTransition(
+ targetState = isFirstStepLaunched.value,
+ label = "Image appearing"
+ )
+ val firstScaleStep = firstTransition.animateFloat(
+ transitionSpec = {
+ tween(
+ durationMillis = firstStepDuration,
+ easing = FastOutLinearInEasing,
+ )
+ },
+ label = "Appearing scale"
+ ) { value -> if (value) scaleSwitchBarrier else initialScale }
+
+ val secondTransition = updateTransition(
+ targetState = isSecondStepLaunched.value,
+ label = "Image slow outgoing"
+ )
+ val secondScaleStep = secondTransition.animateFloat(
+ transitionSpec = {
+ tween(
+ durationMillis = secondStepDuration,
+ easing = LinearEasing,
+ )
+ },
+ label = "Outgoing scale"
+ ) { value -> if (value) 1f else scaleSwitchBarrier }
+
+ val fadeIn = firstTransition.animateFloat(
+ transitionSpec = { tween(durationMillis = 400) },
+ label = "Fade in on start"
+ ) { value -> if (value) 1f else 0f }
+
+ if (firstScaleStep.value == scaleSwitchBarrier) {
+ isSecondStepLaunched.value = true
+ }
+
+ val modifier = if (!isSecondStepLaunched.value) {
+ Modifier.scale(firstScaleStep.value)
+ } else {
+ Modifier.scale(secondScaleStep.value)
+ }.alpha(fadeIn.value)
+
+ content(modifier)
+
+ LaunchedEffect(Unit) { isFirstStepLaunched.value = true }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt
deleted file mode 100644
index 410a8118aa..0000000000
--- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt
+++ /dev/null
@@ -1,162 +0,0 @@
-package com.tangem.tap.features.home.compose
-
-import android.content.Context
-import android.graphics.Typeface
-import android.util.TypedValue
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.compose.ui.viewinterop.AndroidView
-import com.tangem.tangem_sdk_new.extensions.dpToPx
-import com.tangem.tap.common.compose.SpacerS16
-import com.tangem.tap.common.compose.SpacerS24
-import com.tangem.tap.common.compose.extensions.toAndroidGraphicsColor
-import com.tangem.wallet.R
-
-@Composable
-fun StoriesGeneralContent(
- titleText: String,
- subtitleText: String,
- imageSource: Int?,
- isDarkBackground: Boolean,
- subtitleTextId: Int? = null,
- imageComposable: (() -> Unit)? = null,
-) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
- modifier =
- Modifier
- .fillMaxWidth()
- .fillMaxHeight(),
- ) {
-
- Text(
- text = titleText,
- fontSize = 32.sp,
- fontWeight = FontWeight.SemiBold,
- modifier = Modifier
- .padding(start = 40.dp, end = 40.dp),
- color = if (isDarkBackground) Color.White else Color(0xFF090E13),
- textAlign = TextAlign.Center
- )
-
- SpacerS16()
-
- SubtitleText(subtitleText, subtitleTextId)
-
- SpacerS24()
-
- if (imageSource != null) {
- Image(
- painter = painterResource(id = imageSource),
- contentDescription = null,
- contentScale = if (isDarkBackground) ContentScale.Inside else ContentScale.FillWidth,
- modifier = Modifier.fillMaxWidth()
- )
- }
- imageComposable?.invoke()
- }
-}
-
-@Composable
-fun SubtitleText(subtitleText: String, subtitleTextId: Int?) {
- val color = Color(0xFFA6AAAD)
-
- if (subtitleTextId == null) {
- Text(
- text = subtitleText,
- fontSize = 20.sp,
- fontWeight = FontWeight.Normal,
- modifier = Modifier.padding(start = 40.dp, end = 40.dp),
- color = color,
- textAlign = TextAlign.Center
- )
- } else {
- HtmlText(subtitleTextId) { context ->
- val padding = context.dpToPx(40f).toInt()
- TextView(context).apply {
- layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
- setPadding(padding, 0, padding, 0)
- textAlignment = View.TEXT_ALIGNMENT_CENTER
- typeface = Typeface.DEFAULT
- setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f)
- setTextColor(color.toAndroidGraphicsColor())
- }
- }
- }
-}
-
-@Composable
-fun StoriesRevolutionaryWallet() {
- StoriesGeneralContent(
- titleText = stringResource(id = R.string.story_awe_title),
- subtitleText = stringResource(id = R.string.story_awe_description),
- imageSource = R.drawable.revolutionary_wallet,
- isDarkBackground = true
- )
-}
-
-@Composable
-fun StoriesUltraSecureBackup() {
- StoriesGeneralContent(
- titleText = stringResource(id = R.string.story_backup_title),
- subtitleText = stringResource(id = R.string.story_backup_description),
- imageSource = R.drawable.floating_cards,
- subtitleTextId = R.string.story_backup_description,
- isDarkBackground = false
- )
-}
-
-@Composable
-fun StoriesThousandsOfCurrencies() {
- StoriesGeneralContent(
- titleText = stringResource(id = R.string.story_currencies_title),
- subtitleText = stringResource(id = R.string.story_currencies_description),
- imageSource = R.drawable.thousands_of_currencies,
- isDarkBackground = false
- )
-}
-
-@Composable
-fun StoriesWeb3() {
- StoriesGeneralContent(
- titleText = stringResource(id = R.string.story_web3_title),
- subtitleText = stringResource(id = R.string.story_web3_description),
- imageSource = R.drawable.web_3,
- isDarkBackground = false
- )
-}
-
-@Composable
-fun StoriesWalletForEveryone() {
- StoriesGeneralContent(
- titleText = stringResource(id = R.string.story_finish_title),
- subtitleText = stringResource(id = R.string.story_finish_description),
- imageSource = R.drawable.wallet_for_everyone,
- isDarkBackground = true
- )
-}
-
-@Composable
-fun HtmlText(
- stringResId: Int,
- modifier: Modifier = Modifier,
- factory: (Context) -> TextView
-) {
- AndroidView(factory, modifier) { it.text = it.context.getText(stringResId) }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt
similarity index 68%
rename from app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt
rename to app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt
index 4bfac1e8a0..21adc5e516 100644
--- a/app/src/main/java/com/tangem/tap/features/home/compose/Stories.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt
@@ -3,11 +3,25 @@ package com.tangem.tap.features.home.compose
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBars
+import androidx.compose.foundation.layout.union
+import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -24,8 +38,16 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import com.tangem.tap.common.compose.SpacerS24
import androidx.compose.ui.unit.sp
+import com.google.accompanist.systemuicontroller.rememberSystemUiController
+import com.tangem.tap.features.home.compose.content.FirstStoriesContent
+import com.tangem.tap.features.home.compose.content.StoriesCurrencies
+import com.tangem.tap.features.home.compose.content.StoriesRevolutionaryWallet
+import com.tangem.tap.features.home.compose.content.StoriesUltraSecureBackup
+import com.tangem.tap.features.home.compose.content.StoriesWalletForEveryone
+import com.tangem.tap.features.home.compose.content.StoriesWeb3
+import com.tangem.tap.features.home.compose.views.HomeButtons
+import com.tangem.tap.features.home.compose.views.StoriesProgressBar
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.wallet.R
@@ -39,9 +61,8 @@ fun StoriesScreen(
onSearchTokensClick: () -> Unit,
) {
val steps = 6
- val stepDuration = 8_000
-
val currentStep = remember { mutableStateOf(1) }
+ val systemUiController = rememberSystemUiController()
val isDarkBackground = currentStep.value !in 3..5
@@ -53,19 +74,24 @@ fun StoriesScreen(
}
val isPressed = remember { mutableStateOf(false) }
- val needsToBePaused =
- remember(homeState) { mutableStateOf(homeState.value.btnScanState.progressState == ProgressState.Loading) }
- val pause = isPressed.value || needsToBePaused.value
+ val isPaused = isPressed.value || homeState.value.btnScanState.progressState == ProgressState.Loading
val hideContent = remember { mutableStateOf(true) }
+ LaunchedEffect(key1 = isDarkBackground) {
+ systemUiController.setSystemBarsColor(
+ color = Color.Transparent,
+ darkIcons = !isDarkBackground,
+ )
+ }
+
Box(
- Modifier
+ modifier = Modifier
.fillMaxSize()
.background(Color(0xFF090E13))
) {
Row(
- Modifier.fillMaxSize()
+ modifier = Modifier.fillMaxSize()
) {
Box(
Modifier
@@ -106,25 +132,28 @@ fun StoriesScreen(
}
if (!isDarkBackground) {
Image(
+ modifier = Modifier.fillMaxSize(),
painter = painterResource(id = R.drawable.ic_overlay),
contentDescription = null,
- modifier = Modifier
- .fillMaxSize(),
contentScale = ContentScale.FillBounds
)
}
+
+ val statusBarInsets = WindowInsets.statusBars
+ .union(WindowInsets(top = 32.dp))
+
Column(
+ modifier = Modifier
+ .windowInsetsPadding(statusBarInsets)
+ .fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
- modifier = Modifier.fillMaxWidth()
) {
- SpacerS24()
StoriesProgressBar(
steps = steps,
currentStep = currentStep.value,
-// paused = isPressed.value,
- stepDuration = stepDuration,
- paused = pause,
+ stepDuration = currentStep.duration(),
+ paused = isPaused,
onStepFinished = goToNextScreen,
)
Image(
@@ -139,26 +168,26 @@ fun StoriesScreen(
colorFilter = if (isDarkBackground) null else ColorFilter.tint(Color.Black)
)
when (currentStep.value) {
- 1 -> FirstStoriesContent(pause, stepDuration) { hideContent.value = it }
- 2 -> StoriesRevolutionaryWallet()
- 3 -> StoriesUltraSecureBackup()
- 4 -> StoriesThousandsOfCurrencies()
- 5 -> StoriesWeb3()
- 6 -> StoriesWalletForEveryone()
+ 1 -> FirstStoriesContent(isPaused, currentStep.duration()) { hideContent.value = it }
+ 2 -> StoriesRevolutionaryWallet(currentStep.duration())
+ 3 -> StoriesUltraSecureBackup(isPaused, currentStep.duration())
+ 4 -> StoriesCurrencies(isPaused, currentStep.duration())
+ 5 -> StoriesWeb3(isPaused, currentStep.duration())
+ 6 -> StoriesWalletForEveryone(currentStep.duration())
}
}
Column(
modifier = Modifier
+ .navigationBarsPadding()
.align(Alignment.BottomCenter)
- .fillMaxWidth()
+ .fillMaxWidth(),
) {
if (currentStep.value == 4) Button(
onClick = onSearchTokensClick,
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
- .height(48.dp)
- ,
+ .height(48.dp),
colors = ButtonDefaults.textButtonColors(
backgroundColor = Color.White,
contentColor = Color(0xFF080C10)
@@ -184,13 +213,16 @@ fun StoriesScreen(
onShopButtonClick = onShopButtonClick
)
}
-
}
}
+private fun MutableState.duration(): Int = when (this.value) {
+ 1 -> 8000
+ else -> 6000
+}
@Preview
@Composable
-fun InstagramScreenPreview() {
+fun StoriesScreenPreview() {
StoriesScreen(onScanButtonClick = {}, onShopButtonClick = {}, onSearchTokensClick = {})
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt
new file mode 100644
index 0000000000..9ee80263c3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt
@@ -0,0 +1,263 @@
+package com.tangem.tap.features.home.compose.content
+
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.tangem.tap.common.compose.SpacerH16
+import com.tangem.tap.common.compose.SpacerH32
+import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation
+import com.tangem.tap.features.home.compose.StoriesTextAnimation
+import com.tangem.wallet.R
+
+@Composable
+fun StoriesRevolutionaryWallet(stepDuration: Int) {
+ SplitContent(
+ topContent = {
+ TopContent(
+ titleText = stringResource(id = R.string.story_awe_title),
+ subtitleText = stringResource(id = R.string.story_awe_description).annotated(),
+ isDarkBackground = true,
+ )
+ },
+ bottomContent = {
+ StoriesBottomImageAnimation(
+ totalDuration = stepDuration,
+ firstStepDuration = 300,
+ ) { modifier ->
+ StoriesImage(
+ modifier = modifier,
+ drawableResId = R.drawable.revolutionary_wallet,
+ isDarkBackground = true,
+ )
+ }
+ }
+ )
+}
+
+@Composable
+fun StoriesUltraSecureBackup(isPaused: Boolean, stepDuration: Int) {
+ val subtitleText = buildAnnotatedString {
+ append(stringResource(id = R.string.story_backup_description_1))
+ append(" ")
+ withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
+ append(stringResource(id = R.string.story_backup_description_2_bold))
+ }
+ append(" ")
+ append(stringResource(id = R.string.story_backup_description_3))
+ }
+ SplitContent(
+ topContent = {
+ TopContent(
+ titleText = stringResource(id = R.string.story_backup_title),
+ subtitleText = subtitleText,
+ isDarkBackground = false,
+ )
+ },
+ bottomContent = {
+ FloatingCardsContent(isPaused, stepDuration)
+ }
+ )
+}
+
+@Composable
+fun StoriesCurrencies(isPaused: Boolean, stepDuration: Int) {
+ SplitContent(
+ topContent = {
+ TopContent(
+ titleText = stringResource(id = R.string.story_currencies_title),
+ subtitleText = stringResource(id = R.string.story_currencies_description).annotated(),
+ isDarkBackground = false,
+ )
+ },
+ bottomContent = {
+ StoriesCurrenciesContent(paused = isPaused, duration = stepDuration)
+ }
+ )
+}
+
+@Composable
+fun StoriesWeb3(isPaused: Boolean, stepDuration: Int) {
+ SplitContent(
+ topContent = {
+ TopContent(
+ titleText = stringResource(id = R.string.story_web3_title),
+ subtitleText = stringResource(id = R.string.story_web3_description).annotated(),
+ isDarkBackground = false,
+ )
+ },
+ bottomContent = {
+ StoriesWeb3Content(paused = isPaused, duration = stepDuration)
+ }
+ )
+}
+
+@Composable
+fun StoriesWalletForEveryone(stepDuration: Int) {
+ SplitContent(
+ topContent = {
+ TopContent(
+ titleText = stringResource(id = R.string.story_finish_title),
+ subtitleText = stringResource(id = R.string.story_finish_description).annotated(),
+ isDarkBackground = true,
+ )
+ },
+ bottomContent = {
+ StoriesBottomImageAnimation(
+ totalDuration = stepDuration,
+ firstStepDuration = 500,
+ ) { modifier ->
+ StoriesImage(
+ modifier = modifier,
+ drawableResId = R.drawable.wallet_for_everyone,
+ isDarkBackground = true,
+ )
+ }
+ }
+ )
+}
+
+@Composable
+private fun SplitContent(
+ topContent: @Composable () -> Unit,
+ bottomContent: @Composable () -> Unit,
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Top
+ ) {
+ topContent()
+ bottomContent()
+ }
+}
+
+@Composable
+private fun TopContent(
+ titleText: String,
+ subtitleText: AnnotatedString,
+ isDarkBackground: Boolean,
+) {
+ SpacerH32()
+ StoriesTitleText(
+ text = titleText,
+ isDarkBackground = isDarkBackground,
+ )
+ SpacerH16()
+ StoriesSubtitleText(
+ subtitleText = subtitleText,
+ )
+ SpacerH32()
+}
+
+@Composable
+private fun StoriesTitleText(
+ text: String,
+ isDarkBackground: Boolean,
+) {
+ StoriesTextAnimation(
+ slideInDuration = 500,
+ slideInDelay = 150,
+ ) { modifier ->
+ Text(
+ modifier = modifier
+ .padding(start = 40.dp, end = 40.dp),
+ text = text,
+ fontSize = 32.sp,
+ fontWeight = FontWeight.SemiBold,
+ color = if (isDarkBackground) Color.White else Color(0xFF090E13),
+ textAlign = TextAlign.Center
+ )
+ }
+}
+
+@Composable
+private fun StoriesSubtitleText(
+ subtitleText: AnnotatedString,
+) {
+ val color = Color(0xFFA6AAAD)
+
+ StoriesTextAnimation(
+ slideInDuration = 500,
+ slideInDelay = 400,
+ ) { modifier ->
+ Text(
+ modifier = modifier
+ .padding(start = 40.dp, end = 40.dp),
+ fontWeight = FontWeight.Normal,
+ text = subtitleText,
+ fontSize = 20.sp,
+ color = color,
+ textAlign = TextAlign.Center
+ )
+ }
+}
+
+@Composable
+private fun StoriesImage(
+ modifier: Modifier = Modifier,
+ @DrawableRes drawableResId: Int,
+ isDarkBackground: Boolean,
+) {
+ Image(
+ painter = painterResource(id = drawableResId),
+ contentDescription = null,
+ contentScale = if (isDarkBackground) ContentScale.Inside else ContentScale.FillWidth,
+ modifier = modifier.fillMaxWidth()
+ )
+}
+
+private fun String.annotated(): AnnotatedString {
+ val source = this
+ return buildAnnotatedString { append(source) }
+}
+
+
+@Preview
+@Composable
+private fun RevolutionaryWalletPreview() {
+ StoriesRevolutionaryWallet(6000)
+}
+
+@Preview
+@Composable
+private fun UltraSecureBackupPreview() {
+ StoriesUltraSecureBackup(false, 6000)
+}
+
+@Preview
+@Composable
+private fun CurrenciesPreview() {
+ StoriesCurrencies(false, 6000)
+}
+
+
+@Preview
+@Composable
+private fun Web3Preview() {
+ StoriesWeb3(false, 6000)
+}
+
+
+@Preview
+@Composable
+private fun WalletForEveryonePreview() {
+ StoriesWalletForEveryone(6000)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt
new file mode 100644
index 0000000000..198e71d324
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt
@@ -0,0 +1,144 @@
+package com.tangem.tap.features.home.compose.content
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import com.tangem.tap.common.compose.SpacerH
+import com.tangem.tap.common.compose.extensions.dpSize
+import com.tangem.tap.common.compose.extensions.halfHeight
+import com.tangem.tap.common.compose.extensions.toPx
+import com.tangem.tap.common.extensions.isEven
+import com.tangem.tap.features.home.compose.HorizontalSlidingImage
+import com.tangem.wallet.R
+
+@Composable
+fun StoriesCurrenciesContent(
+ paused: Boolean,
+ duration: Int,
+) {
+ val currencyDrawableList = remember {
+ listOf(
+ R.drawable.currency0,
+ R.drawable.currency1,
+ R.drawable.currency2,
+ R.drawable.currency3,
+ R.drawable.currency4,
+ )
+ }
+
+ val screenWidth = LocalConfiguration.current.screenWidthDp.dp
+ val decreaseRate = remember { 1f / currencyDrawableList.size }
+ val designItemHeight = remember { 82.dp }
+
+ LightenBox {
+ Column(modifier = Modifier.graphicsLayer(clip = false)) {
+ currencyDrawableList.forEachIndexed { index, drawableResId ->
+ val painter = painterResource(id = drawableResId)
+ val scaledItemSize = scaleToDesignSize(painter.dpSize(), designItemHeight = designItemHeight)
+ val itemOversizedScreenWidthBy = scaledItemSize.width - screenWidth
+ val moveItemToStartOfScreen = itemOversizedScreenWidthBy / 2
+
+ val chessOffset = if (index.isEven()) 0.dp else scaledItemSize.halfHeight()
+ val animateFrom = chessOffset - moveItemToStartOfScreen
+ val animateTo = 50.dp - (50.dp * index * decreaseRate)
+
+ HorizontalSlidingImage(
+ paused = paused,
+ duration = duration,
+ painter = painter,
+ itemSize = scaledItemSize,
+ startOffset = animateFrom.toPx(),
+ targetOffset = animateTo.toPx(),
+ contentDescription = "Currency row",
+ )
+ SpacerH(12.dp)
+ }
+ }
+ }
+}
+
+@Composable
+fun StoriesWeb3Content(
+ paused: Boolean,
+ duration: Int,
+) {
+ val dappsItemList = remember {
+ listOf(
+ R.drawable.dapps0,
+ R.drawable.dapps1,
+ R.drawable.dapps2,
+ R.drawable.dapps3,
+ R.drawable.dapps4,
+ R.drawable.dapps5,
+ )
+ }
+ val screenWidth = LocalConfiguration.current.screenWidthDp.dp
+ val decreaseRate = remember { 1f / dappsItemList.size }
+ val designItemHeight = 75.dp
+
+ LightenBox {
+ Column(modifier = Modifier.graphicsLayer(clip = false)) {
+ dappsItemList.forEachIndexed { index, drawableResId ->
+ val painter = painterResource(id = drawableResId)
+ val scaledItemSize = scaleToDesignSize(painter.dpSize(), designItemHeight = designItemHeight)
+ val itemOversizedScreenWidthBy = scaledItemSize.width - screenWidth
+ val moveItemToStartOfScreen = itemOversizedScreenWidthBy / 2
+
+ val chessOffset = if (index.isEven()) 0.dp else scaledItemSize.width / 3
+ val animateFrom = chessOffset - moveItemToStartOfScreen
+ val animateTo = 70.dp - (70.dp * index * decreaseRate)
+
+ HorizontalSlidingImage(
+ paused = paused,
+ duration = duration,
+ painter = painter,
+ itemSize = scaledItemSize,
+ startOffset = animateFrom.toPx(),
+ targetOffset = animateTo.toPx(),
+ contentDescription = "Web3 row",
+ )
+ }
+ }
+
+ }
+}
+
+@Composable
+private fun LightenBox(content: @Composable () -> Unit) {
+ Box() {
+ content()
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(top = 250.dp)
+ .background(
+ brush = Brush.verticalGradient(
+ colors = listOf(
+ Color.White.copy(alpha = 0f),
+ Color.White.copy(alpha = 0.75f),
+ Color.White.copy(alpha = 0.95f),
+ Color.White
+ )
+ )
+ )
+ ) {}
+ }
+}
+
+private fun scaleToDesignSize(itemSize: DpSize, designItemHeight: Dp): DpSize {
+ val scaleRate = itemSize.height / designItemHeight
+ return itemSize / scaleRate
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt
new file mode 100644
index 0000000000..393642f976
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt
@@ -0,0 +1,163 @@
+package com.tangem.tap.features.home.compose.content
+
+import androidx.annotation.StringRes
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.tangem.tap.common.compose.FontSizeRange
+import com.tangem.tap.common.compose.TextAutoSize
+import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation
+import com.tangem.tap.features.home.compose.StoriesTextAnimation
+import com.tangem.wallet.R
+
+@Composable
+fun FirstStoriesContent(
+ isPaused: Boolean, duration: Int = 8_000,
+ hideContent: (Boolean) -> Unit
+) {
+ val screenState = remember { mutableStateOf(StartingScreenState.INIT) }
+ val progress = remember { Animatable(0f) }
+
+ LaunchedEffect(isPaused) {
+ if (isPaused) {
+ progress.stop()
+ } else {
+ progress.animateTo(
+ targetValue = 2f,
+ animationSpec = tween(
+ durationMillis = duration,
+ easing = LinearEasing
+ )
+ )
+ }
+ }
+
+ when (progress.value) {
+ in 0f..0.2f -> screenState.value = StartingScreenState.INIT
+ in 0.2f..0.3f -> screenState.value = StartingScreenState.BUY
+ in 0.3f..0.4f -> screenState.value = StartingScreenState.STORE
+ in 0.4f..0.5f -> screenState.value = StartingScreenState.SEND
+ in 0.5f..0.6f -> screenState.value = StartingScreenState.PAY
+ in 0.6f..0.7f -> screenState.value = StartingScreenState.EXCHANGE
+ in 0.7f..0.8f -> screenState.value = StartingScreenState.BORROW
+ in 0.8f..1f -> screenState.value = StartingScreenState.LEND
+ in 1f..1.2f -> screenState.value = StartingScreenState.SHOW_CARD
+ in 1.2f..2f -> screenState.value = StartingScreenState.MEET_TANGEM
+ }
+
+ if (screenState.value == StartingScreenState.INIT) hideContent(true)
+ if (screenState.value == StartingScreenState.BUY) hideContent(false)
+
+ val style = TextStyle(
+ fontSize = 60.sp,
+ fontWeight = FontWeight.SemiBold,
+ color = Color.White,
+ textAlign = TextAlign.Center,
+ )
+ val textId = screenState.textId()
+
+ Box(modifier = Modifier.fillMaxSize()) {
+
+ if (screenState.isSplashingTextDisplaying()) {
+ TextAutoSize(
+ modifier = Modifier
+ .align(Alignment.Center)
+ .padding(start = 20.dp, end = 20.dp, bottom = 100.dp),
+ text = textId?.let { stringResource(textId) } ?: "",
+ textStyle = style,
+ fontSizeRange = FontSizeRange(20.sp, 60.sp)
+ )
+ } else {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Box(
+ modifier = Modifier
+ .weight(0.7f)
+ ) {
+ if (screenState.isMeetTangemDisplaying()) {
+ StoriesTextAnimation(
+ slideInDelay = 0,
+ ) { modifier ->
+ TextAutoSize(
+ modifier = modifier
+ .padding(start = 20.dp, end = 20.dp, top = 50.dp)
+ .alpha(if (screenState.value == StartingScreenState.SHOW_CARD) 0f else 1f),
+ text = textId?.let { stringResource(textId) } ?: "",
+ textStyle = style,
+ fontSizeRange = FontSizeRange(30.sp, 50.sp)
+ )
+ }
+
+ }
+ }
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1.2f)
+ .wrapContentSize(),
+ ) {
+ StoriesBottomImageAnimation(
+ totalDuration = duration,
+ firstStepDuration = 400,
+ ) { modifier ->
+ Image(
+ modifier = modifier.fillMaxWidth(),
+ painter = painterResource(id = R.drawable.meet_tangem),
+ contentDescription = "Tangem Wallet card",
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+private enum class StartingScreenState {
+ INIT, BUY, STORE, SEND, PAY, EXCHANGE, BORROW, LEND, SHOW_CARD, MEET_TANGEM
+}
+
+@StringRes
+private fun MutableState.textId(): Int? = when (this.value) {
+ StartingScreenState.INIT -> null
+ StartingScreenState.BUY -> R.string.story_meet_buy
+ StartingScreenState.STORE -> R.string.story_meet_store
+ StartingScreenState.SEND -> R.string.story_meet_send
+ StartingScreenState.PAY -> R.string.story_meet_pay
+ StartingScreenState.EXCHANGE -> R.string.story_meet_exchange
+ StartingScreenState.BORROW -> R.string.story_meet_borrow
+ StartingScreenState.LEND -> R.string.story_meet_lend
+ StartingScreenState.SHOW_CARD -> R.string.story_meet_title
+ StartingScreenState.MEET_TANGEM -> R.string.story_meet_title
+}
+
+private fun MutableState.isSplashingTextDisplaying(): Boolean {
+ return this.value != StartingScreenState.MEET_TANGEM &&
+ this.value != StartingScreenState.SHOW_CARD
+}
+
+private fun MutableState.isMeetTangemDisplaying(): Boolean {
+ return this.value == StartingScreenState.MEET_TANGEM
+}
+
+
+@Preview
+@Composable
+fun FirstStoriesPreview() {
+ FirstStoriesContent(false, 8000) {}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt
new file mode 100644
index 0000000000..8528982857
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/FloatingCardsContent.kt
@@ -0,0 +1,103 @@
+package com.tangem.tap.features.home.compose.content
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.graphicsLayer
+import com.tangem.tap.common.compose.extensions.AnimatedValue
+import com.tangem.tap.common.compose.extensions.asImageBitmap
+import com.tangem.tap.common.compose.extensions.toAnimatable
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+@Composable
+fun FloatingCardsContent(
+ isPaused: Boolean,
+ stepDuration: Int,
+) {
+
+ val imageBitmap = asImageBitmap(R.drawable.card_placeholder_wallet)
+ val cards = listOf(
+ FloatingCard.first(),
+ FloatingCard.second(),
+ FloatingCard.third(),
+ )
+ Box() {
+ cards.forEach { floatingCard ->
+ FloatingCard.Item(
+ isPaused = isPaused,
+ imageBitmap = imageBitmap,
+ cardValues = floatingCard,
+ stepDuration = stepDuration,
+ )
+ }
+ }
+}
+
+private data class CardValues(
+ val translateX: AnimatedValue = AnimatedValue(0f, 0f),
+ val translateY: AnimatedValue = AnimatedValue(0f, 0f),
+ val rotationX: AnimatedValue = AnimatedValue(0f, 0f),
+ val rotationY: AnimatedValue = AnimatedValue(0f, 0f),
+ val rotationZ: AnimatedValue = AnimatedValue(0f, 0f),
+ val scale: AnimatedValue = AnimatedValue(1f, 1f),
+)
+
+private class FloatingCard {
+ companion object {
+
+ @Composable
+ fun Item(
+ isPaused: Boolean,
+ stepDuration: Int,
+ imageBitmap: ImageBitmap,
+ cardValues: CardValues,
+ ) {
+ Image(
+ bitmap = imageBitmap,
+ contentDescription = "Floating Tangem card",
+ modifier = Modifier
+ .graphicsLayer(
+ translationX = cardValues.translateX.toAnimatable(isPaused, stepDuration).value,
+ translationY = cardValues.translateY.toAnimatable(isPaused, stepDuration).value,
+ rotationX = cardValues.rotationX.toAnimatable(isPaused, stepDuration).value,
+ rotationY = cardValues.rotationY.toAnimatable(isPaused, stepDuration).value,
+ rotationZ = cardValues.rotationZ.toAnimatable(isPaused, stepDuration).value,
+ scaleX = cardValues.scale.toAnimatable(isPaused, stepDuration).value,
+ scaleY = cardValues.scale.toAnimatable(isPaused, stepDuration).value,
+ ),
+ )
+ }
+
+ fun first(): CardValues = CardValues(
+ translateX = -400f to -350f,
+ translateY = 30f to 32f,
+ rotationX = 10f to 15f,
+ rotationY = 15f to 15f,
+ rotationZ = 40f to 27f,
+ scale = 0.6f to 0.6f,
+ )
+
+ fun second(): CardValues = CardValues(
+ translateX = 350f to 300f,
+ translateY = -70f to 0f,
+ rotationX = 30f to 48f,
+ rotationY = 0f to 5f,
+ rotationZ = -34f to -42f,
+ scale = 0.47f to 0.35f,
+ )
+
+ fun third(): CardValues = CardValues(
+ translateX = 320f to 250f,
+ translateY = 500f to 500f,
+ rotationX = 0f to 3f,
+ rotationY = 10f to 10f,
+ rotationZ = -45f to -30f,
+ scale = 0.6f to 0.75f,
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt
similarity index 97%
rename from app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt
rename to app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt
index 04fb1f2647..d49013db87 100644
--- a/app/src/main/java/com/tangem/tap/features/home/compose/HomeButtons.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt
@@ -1,4 +1,4 @@
-package com.tangem.tap.features.home.compose
+package com.tangem.tap.features.home.compose.views
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesProgressBar.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt
similarity index 97%
rename from app/src/main/java/com/tangem/tap/features/home/compose/StoriesProgressBar.kt
rename to app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt
index d121949529..096b87cb0b 100644
--- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesProgressBar.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt
@@ -1,4 +1,4 @@
-package com.tangem.tap.features.home.compose
+package com.tangem.tap.features.home.compose.views
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt
index e9f47acef9..50faf24376 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt
@@ -10,8 +10,8 @@ import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchShare
import com.tangem.tap.common.extensions.dispatchToastNotification
import com.tangem.tap.common.redux.AppDialog
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.AddressData
-import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt
index a539cefefd..8284efcd3d 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt
@@ -12,8 +12,8 @@ import com.tangem.tap.common.extensions.isPositive
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.hasPendingTransactions
-import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.persistence.UsedCardsPrefStorage
import timber.log.Timber
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt
index 2c854ab5a0..34c0d381b2 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt
@@ -15,7 +15,7 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.hasWallets
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.features.demo.DemoHelper
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.scope
import com.tangem.tap.store
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt
index a9ebb0e757..ae7a2db594 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt
@@ -26,7 +26,7 @@ data class OnboardingNoteState(
get() = steps.indexOf(currentStep)
val isBuyAllowed: Boolean by ReadOnlyProperty { _, _ ->
- store.state.globalState.exchangeManager?.availableForBuy(walletBalance.currency) ?: false
+ store.state.globalState.exchangeManager.availableForBuy(walletBalance.currency)
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt
index 0f854da103..e470680dbc 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.features.onboarding.products.otherCards.redux
+import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
@@ -10,6 +11,8 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.extensions.hasWallets
+import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
@@ -73,6 +76,21 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) {
onboardingManager.scanResponse = updatedResponse
onboardingManager.activationStarted(updatedResponse.card.cardId)
+ val primaryBlockchain = updatedResponse.getBlockchain()
+ val blockchainNetworks = if (primaryBlockchain != Blockchain.Unknown) {
+ val primaryToken = updatedResponse.getPrimaryToken()
+ val blockchainNetwork = BlockchainNetwork(primaryBlockchain, updatedResponse.card).updateTokens(
+ listOfNotNull(primaryToken))
+ listOf(blockchainNetwork)
+ } else {
+ listOf(
+ BlockchainNetwork(Blockchain.Bitcoin, updatedResponse.card),
+ BlockchainNetwork(Blockchain.Ethereum, updatedResponse.card)
+ )
+ }
+
+ store.dispatch(WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks))
+
delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done))
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt
index 3ba1648a4b..0dc7e10566 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt
@@ -6,17 +6,23 @@ import com.tangem.common.extensions.guard
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
-import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.extensions.dispatchDialogShow
+import com.tangem.tap.common.extensions.dispatchErrorNotification
+import com.tangem.tap.common.extensions.dispatchOpenUrl
+import com.tangem.tap.common.extensions.getAddressData
+import com.tangem.tap.common.extensions.getToUpUrl
+import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.postUi
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.twins.TwinCardsManager
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
@@ -265,7 +271,8 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
}
CreateTwinWalletMode.RecreateWallet -> {
- store.dispatch(NavigationAction.PopBackTo())
+ currenciesRepository.removeCurrencies(scanResponse.card.cardId)
+ store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt
index 19cc8ad307..0b2e4b4d1a 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt
@@ -56,7 +56,7 @@ data class TwinCardsState(
get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet
val isBuyAllowed: Boolean by ReadOnlyProperty { _, _ ->
- store.state.globalState.exchangeManager?.availableForBuy(walletBalance.currency) ?: false
+ store.state.globalState.exchangeManager.availableForBuy(walletBalance.currency)
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
index c880488141..391d65cb96 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
+import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.domain.common.ScanResponse
@@ -12,9 +13,11 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.extensions.hasWallets
+import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.wallet.redux.Artwork
+import com.tangem.tap.features.wallet.redux.WalletAction
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
@@ -84,6 +87,16 @@ private fun handleWalletAction(action: Action) {
primaryCard = result.data.primaryCard
)
onboardingManager.scanResponse = updatedResponse
+ val blockchainNetworks = listOf(
+ BlockchainNetwork(Blockchain.Bitcoin, result.data.card),
+ BlockchainNetwork(Blockchain.Ethereum, result.data.card)
+ )
+ store.dispatchOnMain(
+ WalletAction.MultiWallet.SaveCurrencies(
+ blockchainNetworks = blockchainNetworks,
+ cardId = result.data.card.cardId
+ )
+ )
onboardingManager.activationStarted(updatedResponse.card.cardId)
store.dispatch(OnboardingWalletAction.ProceedBackup)
}
@@ -260,6 +273,15 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
backupService.proceedBackup { result ->
when (result) {
is CompletionResult.Success -> {
+ val blockchainNetworks = listOf(
+ BlockchainNetwork(Blockchain.Bitcoin, result.data),
+ BlockchainNetwork(Blockchain.Ethereum, result.data)
+ )
+ store.dispatchOnMain(
+ WalletAction.MultiWallet.SaveCurrencies(
+ blockchainNetworks = blockchainNetworks, cardId = result.data.cardId
+ )
+ )
if (backupService.currentState == BackupService.State.Finished) {
store.dispatchOnMain(BackupAction.FinishBackup)
} else {
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt
index 41ccf7083b..b51a53e29f 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt
@@ -20,7 +20,7 @@ class UnfinishedBackupFoundDialog {
}
setNegativeButton(R.string.welcome_interrupted_backup_alert_discard) { _, _ ->
store.dispatch(GlobalAction.HideDialog)
- store.dispatch(GlobalAction.ShowDialog(BackupDialog.ConfirmDiscardingBackup))
+ store.dispatch(GlobalAction.ShowDialog(BackupDialog.ConfirmDiscardingBackup))
}
setCancelable(false)
}.create()
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
index 2ed9fe33e0..8c76739e6b 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
@@ -4,6 +4,7 @@ import com.tangem.Message
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
+import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.ToastNotificationAction
@@ -155,7 +156,10 @@ sealed class SendAction : SendScreenAction {
val reduceAmount: BigDecimal,
) : Dialog()
- data class SendTransactionFails(val errorMessage: String) : Dialog()
+ sealed class SendTransactionFails : Dialog() {
+ data class CardSdkError(val error: TangemSdkError): Dialog()
+ data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError): Dialog()
+ }
object Hide : Dialog()
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
index 29165f8bad..cd4075a0ca 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
@@ -7,7 +7,7 @@ import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.demo.DemoTransactionSender
-import com.tangem.tap.features.demo.isDemoWallet
+import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.send.redux.AmountActionUi
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.ReceiptAction
@@ -28,6 +28,7 @@ class RequestFeeMiddleware {
fun handle(appState: AppState?, dispatch: DispatchFunction) {
val sendState = appState?.sendState ?: return
val walletManager = sendState.walletManager ?: return
+ val scanResponse = appState.globalState.scanResponse ?: return
if (!SendState.isReadyToRequestFee()) {
dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.ADDRESS_OR_AMOUNT_IS_EMPTY))
@@ -40,7 +41,7 @@ class RequestFeeMiddleware {
val destinationAddress = sendState.addressPayIdState.destinationWalletAddress!!
val destinationAmount = Amount(typedAmount, sendState.amountState.amountToSendCrypto)
- val txSender = if (walletManager.isDemoWallet()) {
+ val txSender = if (scanResponse.isDemoCard()) {
DemoTransactionSender(walletManager)
} else {
walletManager as TransactionSender
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
index 1727c81944..df1e3dd5ba 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
@@ -4,7 +4,11 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
-import com.tangem.blockchain.common.*
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.BlockchainSdkError
+import com.tangem.blockchain.common.TransactionError
+import com.tangem.blockchain.common.TransactionSender
+import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
@@ -15,7 +19,12 @@ import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
-import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.extensions.dispatchDialogShow
+import com.tangem.tap.common.extensions.dispatchErrorNotification
+import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.extensions.logSendTransactionError
+import com.tangem.tap.common.extensions.safeUpdate
+import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
@@ -26,9 +35,15 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.minimalAmount
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.demo.DemoTransactionSender
-import com.tangem.tap.features.demo.isDemoWallet
-import com.tangem.tap.features.send.redux.*
+import com.tangem.tap.features.demo.isDemoCard
+import com.tangem.tap.features.send.redux.AddressPayIdActionUi
+import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
+import com.tangem.tap.features.send.redux.AmountAction
+import com.tangem.tap.features.send.redux.AmountActionUi
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
+import com.tangem.tap.features.send.redux.PrepareSendScreen
+import com.tangem.tap.features.send.redux.SendAction
+import com.tangem.tap.features.send.redux.SendActionUi
import com.tangem.tap.features.send.redux.states.ButtonState
import com.tangem.tap.features.send.redux.states.ExternalTransactionData
import com.tangem.tap.features.send.redux.states.MainCurrencyType
@@ -44,7 +59,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.Action
import org.rekotlin.Middleware
-import timber.log.Timber
import java.util.*
/**
@@ -65,13 +79,19 @@ class SendMiddleware {
is SendActionUi.CheckIfTransactionDataWasProvided -> {
val transactionData = appState()?.sendState?.externalTransactionData
if (transactionData != null) {
- store.dispatchOnMain(AddressPayIdVerifyAction.AddressVerification.SetWalletAddress(
- transactionData.destinationAddress, false
- ))
+ store.dispatchOnMain(
+ AddressPayIdVerifyAction.AddressVerification.SetWalletAddress(
+ transactionData.destinationAddress, false,
+ ),
+ )
store.dispatchOnMain(AmountActionUi.SetMainCurrency(MainCurrencyType.CRYPTO))
store.dispatchOnMain(AmountActionUi.HandleUserInput(transactionData.amount))
- store.dispatchOnMain(AmountAction.SetAmount(transactionData.amount.toBigDecimal(),
- false))
+ store.dispatchOnMain(
+ AmountAction.SetAmount(
+ transactionData.amount.toBigDecimal(),
+ false,
+ ),
+ )
}
}
}
@@ -79,7 +99,6 @@ class SendMiddleware {
}
}
}
-
}
private fun verifyAndSendTransaction(
@@ -99,23 +118,31 @@ private fun verifyAndSendTransaction(
when {
hadTezosError -> {
val reduceAmount = walletManager.wallet.blockchain.minimalAmount()
- dispatch(SendAction.Dialog.TezosWarningDialog(reduceCallback = {
- dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
- dispatch(AmountActionUi.CheckAmountToSend)
- }, sendAllCallback = {
- sendTransaction(
- action, walletManager, amountToSend, feeAmount, destinationAddress,
- sendState.transactionExtrasState, card, sendState.externalTransactionData,
- dispatch
- )
- }, reduceAmount))
+ dispatch(
+ SendAction.Dialog.TezosWarningDialog(
+ reduceCallback = {
+ dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
+ dispatch(AmountActionUi.CheckAmountToSend)
+ },
+ sendAllCallback = {
+ sendTransaction(
+ action, walletManager, amountToSend, feeAmount, destinationAddress,
+ sendState.transactionExtrasState, card, sendState.externalTransactionData,
+ dispatch,
+ )
+ },
+ reduceAmount,
+ ),
+ )
}
transactionErrors.isNotEmpty() -> {
dispatch(SendAction.SendError(createValidateTransactionError(transactionErrors, walletManager)))
}
else -> {
- sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress,
- sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch)
+ sendTransaction(
+ action, walletManager, amountToSend, feeAmount, destinationAddress,
+ sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch,
+ )
}
}
}
@@ -136,7 +163,9 @@ private fun sendTransaction(
transactionExtras.xlmMemo?.memo?.let { txData = txData.copy(extras = StellarTransactionExtras(it)) }
transactionExtras.binanceMemo?.memo?.let { txData = txData.copy(extras = BinanceTransactionExtras(it.toString())) }
- transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it)) }
+ transactionExtras.xrpDestinationTag?.tag?.let {
+ txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it))
+ }
scope.launch {
val updateWalletResult = walletManager.safeUpdate()
@@ -161,17 +190,21 @@ private fun sendTransaction(
tangemSdk.config.linkedTerminal = false
}
- val signer = TangemSigner(tangemSdk, action.messageForSigner) { signResponse ->
+ val signer = TangemSigner(
+ card = card,
+ tangemSdk = tangemSdk,
+ initialMessage = action.messageForSigner,
+ ) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.totalSignedHashes,
walletPublicKey = walletManager.wallet.publicKey.seedKey,
- remainingSignatures = signResponse.remainingSignatures
- )
+ remainingSignatures = signResponse.remainingSignatures,
+ ),
)
}
val sendResult = try {
- if (walletManager.isDemoWallet()) {
+ if (card.isDemoCard()) {
DemoTransactionSender(walletManager).send(txData, signer)
} else {
(walletManager as TransactionSender).send(txData, signer)
@@ -186,14 +219,16 @@ private fun sendTransaction(
return@launch
}
- withContext(Dispatchers.Main) {
+ withMainContext {
+ dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED))
tangemSdk.config.linkedTerminal = isLinkedTerminal
+
when (sendResult) {
is SimpleResult.Success -> {
store.state.globalState.analyticsHandlers?.triggerEvent(
event = AnalyticsEvent.TRANSACTION_IS_SENT,
card = card,
- blockchain = walletManager.wallet.blockchain.currency
+ blockchain = walletManager.wallet.blockchain.currency,
)
dispatch(SendAction.SendSuccess)
@@ -213,76 +248,54 @@ private fun sendTransaction(
}
}
is SimpleResult.Failure -> {
- when (sendResult.error) {
+ store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError(
+ wallet = walletManager.wallet,
+ host = walletManager.currentHost,
+ amountToSend = amountToSend,
+ feeAmount = feeAmount,
+ destinationAddress = destinationAddress,
+ )
+ store.state.globalState.analyticsHandlers?.logSendTransactionError(
+ error = sendResult.error,
+ action = Analytics.ActionToLog.SendTransaction,
+ parameters = mapOf(AnalyticsParam.BLOCKCHAIN to walletManager.wallet.blockchain.currency),
+ card = card,
+ )
+
+ val error = (sendResult.error as? BlockchainSdkError) ?: return@withMainContext
+
+ when (error) {
+ is BlockchainSdkError.WrappedTangemError -> {
+ val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return@withMainContext
+ if (tangemSdkError is TangemSdkError.UserCancelled) return@withMainContext
+
+ dispatch(SendAction.Dialog.SendTransactionFails.CardSdkError(tangemSdkError))
+ }
is BlockchainSdkError.CreateAccountUnderfunded -> {
- val error = sendResult.error as BlockchainSdkError.CreateAccountUnderfunded
+ // from XLM, XRP
val reserve = error.minReserve.value?.stripZeroPlainString() ?: "0"
val symbol = error.minReserve.currencySymbol
dispatch(SendAction.SendError(TapError.CreateAccountUnderfunded(listOf(reserve, symbol))))
}
- is BlockchainSdkError.SendException -> {
- sendResult.error?.let { FirebaseCrashlytics.getInstance().recordException(it) }
- }
- is Throwable -> {
- val throwable = sendResult.error as Throwable
- val message = throwable.message
- val infoHolder = store.state.globalState.feedbackManager?.infoHolder
+ else -> {
when {
- message == null -> {
- dispatch(SendAction.SendError(TapError.UnknownError))
- infoHolder?.updateOnSendError(
- wallet = walletManager.wallet,
- host = walletManager.currentHost,
- amountToSend = amountToSend,
- feeAmount = feeAmount,
- destinationAddress = destinationAddress
+ error.customMessage.contains(DemoTransactionSender.ID) -> {
+ store.dispatchDialogShow(
+ AppDialog.SimpleOkDialogRes(
+ headerId = R.string.common_done,
+ messageId = R.string.alert_demo_feature_disabled,
+ onOk = { dispatch(NavigationAction.PopBackTo()) },
+ ),
)
- dispatch(SendAction.Dialog.SendTransactionFails("unknown error"))
- }
- message.contains("50002") -> {
- // user was cancelled the operation by closing the Sdk bottom sheet
- }
- // make it easier latter by handling an appropriate enumError or, like on iOS,
- // accept a string identifier of the error message
- message.contains("Target account is not created. To create account send 1+ XLM.") -> {
- dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
- }
- message.contains(DemoTransactionSender.ID) -> {
- delay(DELAY_SDK_DIALOG_CLOSE)
- store.dispatchDialogShow(AppDialog.SimpleOkDialogRes(
- R.string.common_done,
- R.string.alert_demo_feature_disabled
- ) { dispatch(NavigationAction.PopBackTo()) })
}
else -> {
- (sendResult.error as? TangemSdkError)?.let { error ->
- store.state.globalState.analyticsHandlers?.logCardSdkError(
- error,
- Analytics.ActionToLog.SendTransaction,
- mapOf(
- AnalyticsParam.BLOCKCHAIN
- to walletManager.wallet.blockchain.currency),
- card = card,
- )
- }
- Timber.e(throwable)
- FirebaseCrashlytics.getInstance().recordException(throwable)
- dispatch(SendAction.SendError(TapError.CustomError(message)))
- infoHolder?.updateOnSendError(
- wallet = walletManager.wallet,
- host = walletManager.currentHost,
- amountToSend = amountToSend,
- feeAmount = feeAmount,
- destinationAddress = destinationAddress
- )
- dispatch(SendAction.Dialog.SendTransactionFails(message))
+ dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error))
}
}
}
}
}
}
- dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED))
}
}
}
@@ -310,7 +323,10 @@ fun extractErrorsForAmountField(errors: EnumSet): EnumSet, walletManager: WalletManager): TapError.ValidateTransactionErrors {
+fun createValidateTransactionError(
+ errorList: EnumSet,
+ walletManager: WalletManager,
+): TapError.ValidateTransactionErrors {
val tapErrors = errorList.map {
when (it) {
TransactionError.AmountExceedsBalance -> TapError.AmountExceedsBalance
@@ -340,5 +356,4 @@ private fun updateWarnings(dispatch: (Action) -> Unit) {
val warnings = warningsManager.getWarnings(WarningMessage.Location.SendScreen, listOf(blockchain))
dispatch(SendAction.Warnings.Set(warnings))
-}
-
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt
index edb435953f..fd653557c7 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt
@@ -106,8 +106,12 @@ class FeeReducer : SendInternalReducer {
private fun getFeePrecision(sendState: SendState): FeePrecision {
val blockchain = sendState.walletManager?.wallet?.blockchain
- return if ((blockchain?.fullNameWithoutTestnet == Blockchain.Arbitrum.fullName || blockchain?.fullNameWithoutTestnet == Blockchain.Tron.fullName) &&
- sendState.amountState.typeOfAmount is AmountType.Token) {
+ return if (
+ (blockchain?.fullNameWithoutTestnet == Blockchain.Arbitrum.fullName ||
+ blockchain?.fullNameWithoutTestnet == Blockchain.Tron.fullName ||
+ blockchain?.fullNameWithoutTestnet == Blockchain.Gnosis.fullName) &&
+ sendState.amountState.typeOfAmount is AmountType.Token
+ ) {
FeePrecision.CAN_BE_LOWER
} else {
FeePrecision.PRECISE
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
index 673f19cb66..07936d3e49 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
@@ -3,7 +3,18 @@ package com.tangem.tap.features.send.redux.reducers
import com.tangem.blockchain.common.AmountType
import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.common.entities.IndeterminateProgressButton
-import com.tangem.tap.features.send.redux.*
+import com.tangem.tap.features.send.redux.AddressPayIdActionUi
+import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
+import com.tangem.tap.features.send.redux.AmountAction
+import com.tangem.tap.features.send.redux.AmountActionUi
+import com.tangem.tap.features.send.redux.FeeAction
+import com.tangem.tap.features.send.redux.FeeActionUi
+import com.tangem.tap.features.send.redux.PrepareSendScreen
+import com.tangem.tap.features.send.redux.ReceiptAction
+import com.tangem.tap.features.send.redux.ReleaseSendState
+import com.tangem.tap.features.send.redux.SendAction
+import com.tangem.tap.features.send.redux.SendScreenAction
+import com.tangem.tap.features.send.redux.TransactionExtrasAction
import com.tangem.tap.features.send.redux.states.ExternalTransactionData
import com.tangem.tap.features.send.redux.states.IdStateHolder
import com.tangem.tap.features.send.redux.states.SendState
@@ -46,7 +57,8 @@ private class SendReducer : SendInternalReducer {
sendState.copy(sendButtonState = IndeterminateProgressButton(action.state))
}
is SendAction.Dialog.TezosWarningDialog -> sendState.copy(dialog = action)
- is SendAction.Dialog.SendTransactionFails -> sendState.copy(dialog = action)
+ is SendAction.Dialog.SendTransactionFails.CardSdkError -> sendState.copy(dialog = action)
+ is SendAction.Dialog.SendTransactionFails.BlockchainSdkError -> sendState.copy(dialog = action)
is SendAction.Dialog.Hide -> sendState.copy(dialog = null)
is SendAction.Warnings.Set -> sendState.copy(sendWarningsList = action.warningList)
is SendAction.SendSpecificTransaction ->
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
index 8f4b106e97..a01c048058 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
@@ -2,8 +2,9 @@ package com.tangem.tap.features.send.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
+import com.tangem.tangem_sdk_new.extensions.localizedDescription
+import com.tangem.tap.common.feedback.SendTransactionFailedEmail
import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.features.feedback.SendTransactionFailedEmail
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -14,14 +15,22 @@ import com.tangem.wallet.R
class SendTransactionFailsDialog {
companion object {
- fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails): AlertDialog {
+ fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.CardSdkError): AlertDialog {
+ return create(context, dialog.error.localizedDescription(context))
+ }
+
+ fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.BlockchainSdkError): AlertDialog {
+ return create(context, dialog.error.customMessage)
+ }
+
+ private fun create(context: Context, errorMessage: String): AlertDialog {
return AlertDialog.Builder(context).apply {
setTitle(R.string.alert_failed_to_send_transaction_title)
- setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, dialog.errorMessage))
+ setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage))
setNeutralButton(R.string.alert_button_send_feedback) { _, _ ->
- store.dispatch(GlobalAction.SendFeedback(SendTransactionFailedEmail(dialog.errorMessage)))
+ store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage)))
}
- setPositiveButton(R.string.common_no) { _, _ -> }
+ setPositiveButton(R.string.common_cancel) { _, _ -> }
setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) }
}.create()
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
index 2b71c1ba31..4496bd570b 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
@@ -8,7 +8,12 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.text.bold
import com.tangem.common.extensions.remove
-import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.extensions.beginDelayedTransition
+import com.tangem.tap.common.extensions.enableError
+import com.tangem.tap.common.extensions.getColor
+import com.tangem.tap.common.extensions.getString
+import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.extensions.update
import com.tangem.tap.common.redux.getMessageString
import com.tangem.tap.common.text.DecimalDigitsInputFilter
import com.tangem.tap.domain.MultiMessageError
@@ -17,7 +22,17 @@ import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.SendAction
-import com.tangem.tap.features.send.redux.states.*
+import com.tangem.tap.features.send.redux.states.AddressPayIdState
+import com.tangem.tap.features.send.redux.states.AmountState
+import com.tangem.tap.features.send.redux.states.FeeState
+import com.tangem.tap.features.send.redux.states.MainCurrencyType
+import com.tangem.tap.features.send.redux.states.ReceiptLayoutType
+import com.tangem.tap.features.send.redux.states.ReceiptState
+import com.tangem.tap.features.send.redux.states.SendState
+import com.tangem.tap.features.send.redux.states.StateId
+import com.tangem.tap.features.send.redux.states.TransactionExtraError
+import com.tangem.tap.features.send.redux.states.TransactionExtrasState
+import com.tangem.tap.features.send.redux.states.XlmMemoType
import com.tangem.tap.features.send.ui.FeeUiHelper
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.send.ui.dialogs.SendTransactionFailsDialog
@@ -117,7 +132,13 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
dialog?.show()
}
}
- is SendAction.Dialog.SendTransactionFails -> {
+ is SendAction.Dialog.SendTransactionFails.CardSdkError -> {
+ if (dialog == null) {
+ dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog)
+ dialog?.show()
+ }
+ }
+ is SendAction.Dialog.SendTransactionFails.BlockchainSdkError -> {
if (dialog == null) {
dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog)
dialog?.show()
diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt
index 500cc39285..27a4a5b9e5 100644
--- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt
@@ -1,15 +1,20 @@
package com.tangem.tap.features.shop.redux
import android.content.Intent
+import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.shop.GooglePayService
import com.tangem.tap.common.shop.data.ProductType
import com.tangem.tap.common.shop.data.TangemProduct
+import com.tangem.wallet.R
import org.rekotlin.Action
sealed class ShopAction : Action {
object LoadProducts : ShopAction() {
data class Success(val products: List) : ShopAction()
+ object Failure : ShopAction(), NotificationAction {
+ override val messageResource = R.string.common_server_unavailable
+ }
}
data class ApplyPromoCode(val promoCode: String) : ShopAction() {
diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt
index 464a99de96..f3734950da 100644
--- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt
@@ -11,6 +11,7 @@ import com.tangem.tap.store
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
+import timber.log.Timber
class ShopMiddleware {
@@ -88,10 +89,13 @@ private fun handle(action: Action) {
}
ShopAction.LoadProducts -> {
scope.launch {
- val result = shopService.getProducts()
- result.onSuccess {
- store.dispatchOnMain(ShopAction.LoadProducts.Success(it))
- }
+ shopService.getProducts().fold(
+ onSuccess = { store.dispatchOnMain(ShopAction.LoadProducts.Success(it)) },
+ onFailure = {
+ Timber.e(it)
+ store.dispatchOnMain(ShopAction.LoadProducts.Failure)
+ }
+ )
}
}
is ShopAction.CheckIfGooglePayAvailable -> {
diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt
index ee12f53109..001c48a46f 100644
--- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt
@@ -65,5 +65,6 @@ private fun internalReduce(action: Action, state: ShopState): ShopState {
}
ShopAction.FinishSuccessfulOrder -> state
ShopAction.ResetState -> ShopState()
+ ShopAction.LoadProducts.Failure -> state
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt
index 7e1e4bee95..79f171b03f 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensAction.kt
@@ -28,8 +28,6 @@ sealed class TokensAction : Action {
val wallets: List, val derivationStyle: DerivationStyle?
) : TokensAction()
- data class SetNonRemovableCurrencies(val wallets: List) : TokensAction()
-
data class SaveChanges(
val addedTokens: List,
val addedBlockchains: List
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
index aab99a3d58..2ec703e629 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt
@@ -30,7 +30,7 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.tokens.LoadAvailableCoinsService
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -44,13 +44,13 @@ class TokensMiddleware {
when (action) {
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action.scanResponse)
is TokensAction.SaveChanges -> handleSaveChanges(action)
- is TokensAction.PrepareAndNavigateToAddCustomToken -> handleAddingCustomToken(
- action
- )
+ is TokensAction.PrepareAndNavigateToAddCustomToken -> {
+ handleAddingCustomToken(action)
+ }
is TokensAction.SetSearchInput -> {
handleLoadCurrencies(
scanResponse = store.state.globalState.scanResponse,
- action.searchInput
+ newSearchInput = action.searchInput
)
}
is TokensAction.LoadMore -> {
@@ -138,7 +138,9 @@ class TokensMiddleware {
)
)
- if (tokensToAdd.isEmpty() && blockchainsToAdd.isEmpty()) {
+ if (tokensToAdd.isEmpty() && tokensToRemove.isEmpty()
+ && blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
+ ) {
store.dispatchDebugErrorNotification("Nothing to save")
store.dispatch(NavigationAction.PopBackTo())
return
@@ -308,9 +310,10 @@ class TokensMiddleware {
private fun removeCurrenciesIfNeeded(currencies: List) {
if (currencies.isNotEmpty()) {
currencies.forEach { currency ->
- store.state.walletState.getWalletData(currency)?.let {
- store.dispatch(WalletAction.MultiWallet.RemoveWallet(it))
- }
+ store.dispatch(WalletAction.MultiWallet.RemoveWallet(
+ currency = currency,
+ fromScreen = AppScreen.AddTokens
+ ))
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt
index 374f6ad820..007a1e9711 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensReducer.kt
@@ -43,13 +43,6 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
derivationStyle = action.derivationStyle
)
}
- is TokensAction.SetNonRemovableCurrencies -> {
- tokensState.copy(
- nonRemovableBlockchains = action.wallets.toNonCustomBlockchains(tokensState.derivationStyle),
- nonRemovableTokens = action.wallets.toTokensContractAddresses(),
- )
- }
-
is TokensAction.AllowToAddTokens -> {
tokensState.copy(allowToAdd = action.allow)
}
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt
index 1c60655d24..ab1b3731a5 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensState.kt
@@ -14,8 +14,6 @@ data class TokensState(
val addedWallets: List = emptyList(),
val addedTokens: List = emptyList(),
val addedBlockchains: List = emptyList(),
- val nonRemovableTokens: List = emptyList(),
- val nonRemovableBlockchains: List = emptyList(),
val currencies: List = emptyList(),
val searchInput: String? = null,
val allowToAdd: Boolean = true,
@@ -34,18 +32,18 @@ data class TokensState(
typealias ContractAddress = String
fun List.toTokensContractAddresses(): List {
- return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token?.contractAddress }.distinct()
+ return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.models.Currency.Token)?.token?.contractAddress }.distinct()
}
fun List.toNonCustomTokens(derivationStyle: DerivationStyle?): List {
return filter { !it.currency.isCustomCurrency(derivationStyle) }
- .mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token }
+ .mapNotNull { (it.currency as? com.tangem.tap.features.wallet.models.Currency.Token)?.token }
.distinct()
}
fun List.toNonCustomTokensWithBlockchains(derivationStyle: DerivationStyle?): List {
return mapNotNull {
- if (it.currency !is com.tangem.tap.features.wallet.redux.Currency.Token) return@mapNotNull null
+ if (it.currency !is com.tangem.tap.features.wallet.models.Currency.Token) return@mapNotNull null
if (it.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
TokenWithBlockchain(it.currency.token, it.currency.blockchain)
}.distinct()
@@ -56,7 +54,7 @@ fun List.toNonCustomBlockchains(derivationStyle: DerivationStyle?):
if (it.currency.isCustomCurrency(derivationStyle)) {
null
} else {
- (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Blockchain)?.blockchain
+ (it.currency as? com.tangem.tap.features.wallet.models.Currency.Blockchain)?.blockchain
}
}.distinct()
}
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CollapsedCurrencyItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CollapsedCurrencyItem.kt
deleted file mode 100644
index 5ac13c1815..0000000000
--- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CollapsedCurrencyItem.kt
+++ /dev/null
@@ -1,155 +0,0 @@
-package com.tangem.tap.features.tokens.ui.compose
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import coil.compose.SubcomposeAsyncImage
-import coil.request.ImageRequest
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.domain.common.extensions.fromNetworkId
-import com.tangem.tap.common.extensions.getGreyedOutIconRes
-import com.tangem.tap.common.extensions.getRoundIconRes
-import com.tangem.tap.domain.tokens.Currency
-import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
-import com.tangem.wallet.R
-
-@Composable
-fun CollapsedCurrencyItem(
- currency: Currency,
- addedTokens: List,
- addedBlockchains: List,
- onCurrencyClick: (String) -> Unit
-) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .clickable(onClick = { onCurrencyClick(currency.id) })
- ) {
- val blockchain = Blockchain.fromNetworkId(currency.id)
-
- SubcomposeAsyncImage(
- model = ImageRequest.Builder(LocalContext.current)
- .data(currency.iconUrl)
- .crossfade(true)
- .build(),
- contentDescription = currency.id,
- loading = { CurrencyPlaceholderIcon(currency.id) },
- error = { CurrencyPlaceholderIcon(currency.id) },
- modifier = Modifier
- .clip(CircleShape)
- .padding(16.dp)
- .size(46.dp),
- )
- Column(
- modifier = Modifier
- .fillMaxHeight()
- .weight(1f)
- .align(Alignment.CenterVertically)
- ) {
- Text(
- text = currency.fullName,
- fontSize = 17.sp,
- fontWeight = FontWeight.Normal,
- color = Color(0xFF1C1C1E),
- )
- Spacer(modifier = Modifier.size(6.dp))
- Row {
- if (!currency.contracts.isNullOrEmpty()) {
- currency.contracts.map { contract ->
- if (contract.address == null) {
- BlockchainNetworkItem(
- blockchain = contract.blockchain,
- isMainNetwork = true,
- addedBlockchains = addedBlockchains
- )
- } else {
- val added =
- addedTokens.map { it.token.contractAddress }
- .contains(contract.address)
- val icon = if (added) {
- contract.blockchain.getRoundIconRes()
- } else {
- contract.blockchain.getGreyedOutIconRes()
- }
- Image(
- painter = painterResource(id = icon),
- contentDescription = null,
- Modifier
- .size(20.dp)
- )
- Spacer(modifier = Modifier.size(5.dp))
- }
-
- }
- } else {
- BlockchainNetworkItem(
- blockchain = blockchain,
- isMainNetwork = false,
- addedBlockchains = addedBlockchains
- )
- }
- }
- }
- Image(
- painter = painterResource(id = R.drawable.ic_arrow_collapsed),
- contentDescription = null,
- Modifier
- .padding(20.dp)
- .align(Alignment.CenterVertically)
-
- )
- }
-}
-
-@Composable
-fun BlockchainNetworkItem(
- blockchain: Blockchain?,
- isMainNetwork: Boolean,
- addedBlockchains: List
-) {
- val added = addedBlockchains.contains(blockchain)
- val icon =
- if (added) blockchain?.getRoundIconRes() else blockchain?.getGreyedOutIconRes()
- if (icon != null) {
- Box(Modifier
- .size(20.dp)) {
- Image(
- painter = painterResource(id = icon),
- contentDescription = null,
- Modifier
- .size(20.dp)
- )
- if (isMainNetwork) {
- Box(
- modifier = Modifier
- .align(Alignment.TopEnd)
- .size(7.dp)
- .clip(CircleShape)
- .background(Color.White)
- ) {
- Box(
- modifier = Modifier
- .align(Alignment.Center)
- .size(5.dp)
- .clip(CircleShape)
- .background(Color(0xFF1ACE80))
- )
- }
- }
- }
- Spacer(modifier = Modifier.size(5.dp))
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt
index ef87846c21..49886cbf86 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt
@@ -21,6 +21,8 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.tap.common.compose.Keyboard
+import com.tangem.tap.common.compose.extensions.addAndNotify
+import com.tangem.tap.common.compose.extensions.removeAndNotify
import com.tangem.tap.common.compose.keyboardAsState
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.pixelsToDp
@@ -30,6 +32,7 @@ import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.LoadCoinsState
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.tap.features.tokens.redux.TokensState
+import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -40,30 +43,30 @@ fun CurrenciesScreen(
onNetworkItemClicked: (ContractAddress) -> Unit,
onLoadMore: () -> Unit
) {
- val context = LocalContext.current
+ val tokensAddedOnMainScreen = remember { tokensState.value.addedTokens }
+ val blockchainsAddedOnMainScreen = remember { tokensState.value.addedBlockchains }
+
val addedTokensState = remember { mutableStateOf(tokensState.value.addedTokens) }
val addedBlockchainsState = remember { mutableStateOf(tokensState.value.addedBlockchains) }
val isKeyboardOpen by keyboardAsState()
val onAddCurrencyToggleClick = { currency: Currency, token: TokenWithBlockchain? ->
- if (token != null) {
- val mutableList = addedTokensState.value.toMutableList()
- if (mutableList.contains(token)) {
- mutableList.remove(token)
- } else {
- mutableList.add(token)
- }
- addedTokensState.value = mutableList
- } else {
- val blockchain = Blockchain.fromNetworkId(currency.id)
- val mutableList = addedBlockchainsState.value.toMutableList()
- if (mutableList.contains(blockchain)) {
- mutableList.remove(blockchain)
- } else {
- blockchain?.let { mutableList.add(blockchain) }
- }
- addedBlockchainsState.value = mutableList
+ val blockchain = Blockchain.fromNetworkId(currency.id)
+ if (blockchain != null && token == null) {
+ toggleBlockchain(
+ blockchain,
+ blockchainsAddedOnMainScreen,
+ addedBlockchainsState,
+ addedTokensState.value,
+ )
+ } else if (token != null) {
+ toggleToken(
+ token,
+ tokensAddedOnMainScreen,
+ addedTokensState,
+ tokensState.value,
+ )
}
}
@@ -100,23 +103,11 @@ fun CurrenciesScreen(
ListOfCurrencies(
header = { if (showHeader) CurrenciesWarning() },
currencies = tokensState.value.currencies,
- nonRemovableTokens = tokensState.value.nonRemovableTokens,
- nonRemovableBlockchains = tokensState.value.nonRemovableBlockchains,
addedTokens = addedTokensState.value,
addedBlockchains = addedBlockchainsState.value,
allowToAdd = tokensState.value.allowToAdd,
onAddCurrencyToggled = { currency, token ->
onAddCurrencyToggleClick(currency, token)
- token?.let {
- if (!tokensState.value.canHandleToken(it)) {
- val dialog = AppDialog.SimpleOkDialog(
- header = context.getString(R.string.common_warning),
- message = context.getString(R.string.alert_manage_tokens_unsupported_message)
- ) { onAddCurrencyToggleClick(currency, it) }
- store.dispatchDialogShow(dialog)
- }
- }
-
},
onNetworkItemClicked = onNetworkItemClicked,
onLoadMore = onLoadMore
@@ -127,6 +118,68 @@ fun CurrenciesScreen(
}
}
+private fun toggleBlockchain(
+ blockchain: Blockchain,
+ blockchainsAddedOnMainScreen: List,
+ addedBlockchainsState: MutableState>,
+ addedTokens: List
+) {
+ val isTryingToRemove = addedBlockchainsState.value.contains(blockchain)
+ val isAddedOnMainScreen = blockchainsAddedOnMainScreen.contains(blockchain)
+ val isTokenWithSameBlockchainFound = addedTokens.any { it.blockchain == blockchain }
+
+ if (isTryingToRemove) {
+ if (isTokenWithSameBlockchainFound) {
+ store.dispatchDialogShow(WalletDialog.TokensAreLinkedDialog(
+ currencyTitle = blockchain.name,
+ currencySymbol = blockchain.currency
+ ))
+ } else {
+ if (isAddedOnMainScreen) {
+ store.dispatchDialogShow(WalletDialog.RemoveWalletDialog(
+ currencyTitle = blockchain.name,
+ onOk = { addedBlockchainsState.removeAndNotify(blockchain) }
+ ))
+ } else {
+ addedBlockchainsState.removeAndNotify(blockchain)
+ }
+ }
+ } else {
+ addedBlockchainsState.addAndNotify(blockchain)
+ }
+}
+
+private fun toggleToken(
+ token: TokenWithBlockchain,
+ tokensAddedOnMainScreen: List,
+ addedTokensState: MutableState>,
+ tokensState: TokensState,
+) {
+ val isTryingToRemove = addedTokensState.value.contains(token)
+ val isAddedOnMainScreen = tokensAddedOnMainScreen.contains(token)
+ val isUnsupportedToken = !tokensState.canHandleToken(token)
+
+ if (isTryingToRemove) {
+ if (isAddedOnMainScreen) {
+ store.dispatchDialogShow(WalletDialog.RemoveWalletDialog(
+ currencyTitle = token.token.name,
+ onOk = { addedTokensState.removeAndNotify(token) }
+ ))
+ } else {
+ addedTokensState.removeAndNotify(token)
+ }
+ } else {
+ if (isUnsupportedToken) {
+ store.dispatchDialogShow(AppDialog.SimpleOkDialogRes(
+ headerId = R.string.common_warning,
+ messageId = R.string.alert_manage_tokens_unsupported_message,
+ ))
+ } else {
+ addedTokensState.addAndNotify(token)
+ }
+ }
+}
+
@Composable
fun SaveChangesButton(keyboardState: Keyboard, onSaveChanges: () -> Unit) {
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ExpandedCurrencyItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ExpandedCurrencyItem.kt
index ef8c92c104..e69de29bb2 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ExpandedCurrencyItem.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ExpandedCurrencyItem.kt
@@ -1,159 +0,0 @@
-package com.tangem.tap.features.tokens.ui.compose
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.Divider
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import coil.compose.SubcomposeAsyncImage
-import coil.request.ImageRequest
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.tap.domain.tokens.Currency
-import com.tangem.tap.features.tokens.redux.ContractAddress
-import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
-import com.tangem.wallet.R
-
-@Composable
-fun ExpandedCurrencyItem(
- currency: Currency,
- nonRemovableTokens: List,
- nonRemovableBlockchains: List,
- addedTokens: List,
- addedBlockchains: List,
- allowToAdd: Boolean,
- onCurrencyClick: (String) -> Unit,
- onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
- onNetworkItemClicked: (ContractAddress) -> Unit
-) {
- Column {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .clickable(onClick = { onCurrencyClick(currency.id) })
- ) {
- SubcomposeAsyncImage(
- model = ImageRequest.Builder(LocalContext.current)
- .data(currency.iconUrl)
- .crossfade(true)
- .build(),
- contentDescription = currency.id,
- loading = { CurrencyPlaceholderIcon(currency.id) },
- error = { CurrencyPlaceholderIcon(currency.id) },
- modifier = Modifier
- .clip(CircleShape)
- .padding(start = 16.dp, top = 16.dp, end = 16.dp)
- .size(46.dp),
- )
- Column(
- modifier = Modifier
- .fillMaxHeight()
- .weight(1f)
- .padding(top = 14.dp)
- .align(Alignment.CenterVertically)
- ) {
- Text(
- text = currency.fullName,
- fontSize = 17.sp,
- fontWeight = FontWeight.Normal,
- color = Color(0xFF1C1C1E),
- )
- Spacer(modifier = Modifier.size(6.dp))
- Text(
- text = stringResource(id = R.string.currency_subtitle_expanded),
- fontSize = 13.sp,
- fontWeight = FontWeight.Normal,
- color = Color(0xFF8E8E93),
- )
- }
- Image(
- painter = painterResource(id = R.drawable.ic_arrow_extended),
- contentDescription = null,
- Modifier
- .padding(20.dp)
- .align(Alignment.CenterVertically)
- )
- }
-
- val blockchains = currency.contracts.map { it.blockchain }
-
- Row {
- Box(
- modifier = Modifier
- .fillMaxHeight()
- ) {
- if (blockchains.size > 1) {
- val dividerHeight = if (blockchains.size == 2) {
- 60
- } else if (blockchains.size == 3) {
- 110
- } else {
- 43 * blockchains.size
- }
- Divider(
- color = Color(0xFFDEDEDE),
- modifier = Modifier
- .height(dividerHeight.dp)
- .padding(start = 37.5.dp)
- .width(1.dp)
- )
- }
-
- Column {
- blockchains.map {
- Image(
- painter = painterResource(id = R.drawable.ic_link),
- contentDescription = null,
- Modifier
- .padding(
- start = 37.dp,
- )
- )
- Spacer(modifier = Modifier.size(13.5.dp))
- }
- }
- }
-
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 6.dp),
- ) {
- blockchains.map { blockchain ->
- val contract = currency.contracts.firstOrNull { it.blockchain == blockchain }
- ?: return@map
- val added = if (contract.address != null) {
- addedTokens.map { it.token.contractAddress }.contains(contract.address)
- } else {
- addedBlockchains.contains(blockchain)
- }
- val canBeRemoved = if (contract.address != null) {
- !nonRemovableTokens.contains(contract.address)
- } else {
- !nonRemovableBlockchains.contains(blockchain)
- }
- NetworkItem(
- currency = currency,
- contract = contract,
- blockchain = blockchain, allowToAdd = allowToAdd,
- added = added,
- canBeRemoved = canBeRemoved,
- onAddCurrencyToggled = onAddCurrencyToggled,
- onNetworkItemClicked = onNetworkItemClicked,
- )
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt
index 4892cf5e13..613f741c55 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/ListOfCurrencies.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.features.tokens.ui.compose
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@@ -16,20 +17,18 @@ import com.tangem.tap.common.compose.extensions.OnBottomReached
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
-
+import com.tangem.tap.features.tokens.ui.compose.currencyItem.CurrencyItem
@Composable
fun ListOfCurrencies(
header: @Composable () -> Unit,
currencies: List,
- nonRemovableTokens: List,
- nonRemovableBlockchains: List,
addedTokens: List,
addedBlockchains: List,
allowToAdd: Boolean,
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
onNetworkItemClicked: (ContractAddress) -> Unit,
- onLoadMore: () -> Unit
+ onLoadMore: () -> Unit,
) {
val expandedCurrencies = remember { mutableStateOf(listOf("")) }
@@ -52,21 +51,20 @@ fun ListOfCurrencies(
state = listState,
modifier = Modifier
.fillMaxSize(),
- contentPadding = PaddingValues(bottom = 90.dp)
+ contentPadding = PaddingValues(bottom = 90.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp),
) {
item { header() }
itemsIndexed(currencies) { index, currency ->
CurrencyItem(
currency = currency,
- nonRemovableTokens = nonRemovableTokens,
- nonRemovableBlockchains = nonRemovableBlockchains,
addedTokens = addedTokens,
addedBlockchains = addedBlockchains,
allowToAdd = allowToAdd,
- expanded = expandedCurrencies.value.contains(currency.id),
+ isExpanded = expandedCurrencies.value.contains(currency.id),
onCurrencyClick = onCurrencyClick,
onAddCurrencyToggled = onAddCurrencyToggled,
- onNetworkItemClicked = onNetworkItemClicked
+ onNetworkItemClicked = onNetworkItemClicked,
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt
new file mode 100644
index 0000000000..cded760ba9
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt
@@ -0,0 +1,60 @@
+package com.tangem.tap.features.tokens.ui.compose.currencyItem
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.shrinkVertically
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tap.domain.tokens.Currency
+import com.tangem.tap.features.tokens.redux.ContractAddress
+import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
+
+@Composable
+fun CurrencyExpandedContent(
+ currency: Currency,
+ addedTokens: List,
+ addedBlockchains: List,
+ allowToAdd: Boolean,
+ isExpanded: Boolean,
+ onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
+ onNetworkItemClicked: (ContractAddress) -> Unit,
+) {
+ AnimatedVisibility(
+ visible = isExpanded,
+ enter = fadeIn() + expandVertically(),
+ exit = fadeOut() + shrinkVertically(),
+ ) {
+ val blockchains = currency.contracts.map { it.blockchain }
+ Column(
+ modifier = Modifier
+ .fillMaxWidth(),
+ ) {
+ blockchains.mapIndexed { index, blockchain ->
+ val contract = currency.contracts.firstOrNull { it.blockchain == blockchain }
+ ?: return@mapIndexed
+
+ val added = if (contract.address != null) {
+ addedTokens.map { it.token.contractAddress }.contains(contract.address)
+ } else {
+ addedBlockchains.contains(blockchain)
+ }
+ NetworkItem(
+ currency = currency,
+ contract = contract,
+ blockchain = blockchain,
+ allowToAdd = allowToAdd,
+ added = added,
+ index = index,
+ size = blockchains.size,
+ onAddCurrencyToggled = onAddCurrencyToggled,
+ onNetworkItemClicked = onNetworkItemClicked,
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrencyItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItem.kt
similarity index 50%
rename from app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrencyItem.kt
rename to app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItem.kt
index 9fd962bce7..6758c71e82 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrencyItem.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItem.kt
@@ -1,5 +1,6 @@
-package com.tangem.tap.features.tokens.ui.compose
+package com.tangem.tap.features.tokens.ui.compose.currencyItem
+import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.domain.tokens.Currency
@@ -9,31 +10,30 @@ import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
@Composable
fun CurrencyItem(
currency: Currency,
- nonRemovableTokens: List,
- nonRemovableBlockchains: List,
addedTokens: List,
addedBlockchains: List,
allowToAdd: Boolean,
- expanded: Boolean,
+ isExpanded: Boolean,
onCurrencyClick: (String) -> Unit,
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
- onNetworkItemClicked: (ContractAddress) -> Unit
+ onNetworkItemClicked: (ContractAddress) -> Unit,
) {
- if (expanded) {
- ExpandedCurrencyItem(
+ Column {
+ CurrencyItemHeader(
+ currency = currency,
+ addedTokens = addedTokens,
+ addedBlockchains = addedBlockchains,
+ isExpanded = isExpanded,
+ onCurrencyClick = onCurrencyClick,
+ )
+ CurrencyExpandedContent(
currency = currency,
- nonRemovableTokens = nonRemovableTokens,
- nonRemovableBlockchains = nonRemovableBlockchains,
addedTokens = addedTokens,
addedBlockchains = addedBlockchains,
allowToAdd = allowToAdd,
- onCurrencyClick = onCurrencyClick, onAddCurrencyToggled = onAddCurrencyToggled,
- onNetworkItemClicked = onNetworkItemClicked
- )
- } else {
- CollapsedCurrencyItem(
- currency = currency, addedTokens = addedTokens,
- addedBlockchains = addedBlockchains, onCurrencyClick
+ isExpanded = isExpanded,
+ onAddCurrencyToggled = onAddCurrencyToggled,
+ onNetworkItemClicked = onNetworkItemClicked,
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemArrow.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemArrow.kt
new file mode 100644
index 0000000000..4be02043d5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemArrow.kt
@@ -0,0 +1,91 @@
+package com.tangem.tap.features.tokens.ui.compose.currencyItem
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+@Composable
+fun CurrencyItemArrow(
+ rowHeight: Dp,
+ isLastArrow: Boolean,
+) {
+ Box(Modifier.height(rowHeight)) {
+ if (!isLastArrow) {
+ MiddleArrowView(rowHeight)
+ } else {
+ LastArrowView(rowHeight)
+ }
+ }
+}
+
+@Composable
+private fun MiddleArrowView(rowHeight: Dp) {
+ Box {
+ Box(
+ modifier = Modifier
+ .padding(start = 0.5.dp),
+ ) {
+ Box(
+ modifier = Modifier
+ .background(color = Color(0xFFDEDEDE))
+ .width(0.8.dp)
+ .fillMaxHeight(),
+ )
+ }
+
+ LastArrowView(rowHeight)
+ }
+}
+
+@Composable
+private fun LastArrowView(rowHeight: Dp) {
+ Image(
+ modifier = Modifier
+ .height(rowHeight / 2 + 3.5.dp),
+ painter = painterResource(id = R.drawable.ic_link),
+ contentDescription = null,
+ )
+}
+
+@Preview
+@Composable
+fun ArrowViewPreview() {
+ Box(
+ Modifier
+ .background(color = Color.White),
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .padding(start = 37.dp),
+ ) {
+ CurrencyItemArrow(
+ rowHeight = 62.dp,
+ isLastArrow = false,
+ )
+ CurrencyItemArrow(
+ rowHeight = 62.dp,
+ isLastArrow = true,
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt
new file mode 100644
index 0000000000..f3d40a401b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyItemHeader.kt
@@ -0,0 +1,208 @@
+package com.tangem.tap.features.tokens.ui.compose.currencyItem
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.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.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import coil.compose.SubcomposeAsyncImage
+import coil.request.ImageRequest
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.domain.common.extensions.fromNetworkId
+import com.tangem.tap.common.extensions.getGreyedOutIconRes
+import com.tangem.tap.common.extensions.getRoundIconRes
+import com.tangem.tap.domain.tokens.Currency
+import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
+import com.tangem.tap.features.tokens.ui.compose.CurrencyPlaceholderIcon
+import com.tangem.tap.features.tokens.ui.compose.fullName
+import com.tangem.wallet.R
+
+@Composable
+fun CurrencyItemHeader(
+ currency: Currency,
+ addedTokens: List,
+ addedBlockchains: List,
+ isExpanded: Boolean,
+ onCurrencyClick: (String) -> Unit,
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(62.dp)
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ onClick = { onCurrencyClick(currency.id) },
+ ),
+ ) {
+ Box(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .align(Alignment.CenterVertically)
+ .clip(RoundedCornerShape(6.dp)),
+ ) {
+ SubcomposeAsyncImage(
+ modifier = Modifier
+ .size(46.dp),
+ model = ImageRequest.Builder(LocalContext.current)
+ .data(currency.iconUrl)
+ .crossfade(true)
+ .build(),
+ contentDescription = currency.id,
+ loading = { CurrencyPlaceholderIcon(currency.id) },
+ error = { CurrencyPlaceholderIcon(currency.id) },
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .align(Alignment.CenterVertically),
+ ) {
+ Text(
+ text = currency.fullName,
+ fontSize = 17.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF1C1C1E),
+ )
+ Spacer(modifier = Modifier.size(6.dp))
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(20.dp),
+ ) {
+ if (isExpanded) {
+ Text(
+ modifier = Modifier.align(Alignment.TopStart),
+ text = stringResource(id = R.string.currency_subtitle_expanded),
+ fontSize = 13.sp,
+ fontWeight = FontWeight.Normal,
+ color = Color(0xFF8E8E93),
+ )
+ } else {
+ NetworksRow(
+ currency = currency,
+ addedTokens = addedTokens,
+ addedBlockchains = addedBlockchains,
+ )
+ }
+ }
+ }
+
+ val expandCollapseArrow = if (isExpanded) R.drawable.ic_arrow_extended else R.drawable.ic_arrow_collapsed
+ Image(
+ painter = painterResource(id = expandCollapseArrow),
+ contentDescription = null,
+ Modifier
+ .padding(20.dp)
+ .align(Alignment.CenterVertically),
+ )
+ }
+}
+
+@Composable
+private fun NetworksRow(
+ currency: Currency,
+ addedTokens: List,
+ addedBlockchains: List,
+) {
+ Row {
+ if (currency.contracts.isNotEmpty()) {
+ currency.contracts.map { contract ->
+ if (contract.address == null) {
+ BlockchainNetworkItem(
+ blockchain = contract.blockchain,
+ isMainNetwork = true,
+ addedBlockchains = addedBlockchains,
+ )
+ } else {
+ val isAdded = addedTokens.map { it.token.contractAddress }.contains(contract.address)
+ val iconResId = if (isAdded) {
+ contract.blockchain.getRoundIconRes()
+ } else {
+ contract.blockchain.getGreyedOutIconRes()
+ }
+ Image(
+ modifier = Modifier
+ .size(20.dp),
+ painter = painterResource(id = iconResId),
+ contentDescription = null,
+ )
+ Spacer(Modifier.size(5.dp))
+ }
+
+ }
+ } else {
+ BlockchainNetworkItem(
+ blockchain = Blockchain.fromNetworkId(currency.id),
+ isMainNetwork = false,
+ addedBlockchains = addedBlockchains,
+ )
+ }
+ }
+}
+
+@Composable
+private fun BlockchainNetworkItem(
+ blockchain: Blockchain?,
+ isMainNetwork: Boolean,
+ addedBlockchains: List,
+) {
+ val added = addedBlockchains.contains(blockchain)
+ val icon =
+ if (added) blockchain?.getRoundIconRes() else blockchain?.getGreyedOutIconRes()
+ if (icon != null) {
+ Box(
+ Modifier
+ .size(20.dp),
+ ) {
+ Image(
+ painter = painterResource(id = icon),
+ contentDescription = null,
+ Modifier
+ .size(20.dp),
+ )
+ if (isMainNetwork) {
+ Box(
+ modifier = Modifier
+ .align(Alignment.TopEnd)
+ .size(7.dp)
+ .clip(CircleShape)
+ .background(Color.White),
+ ) {
+ Box(
+ modifier = Modifier
+ .align(Alignment.Center)
+ .size(5.dp)
+ .clip(CircleShape)
+ .background(Color(0xFF1ACE80)),
+ )
+ }
+ }
+ }
+ Spacer(modifier = Modifier.size(5.dp))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/NetworkItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt
similarity index 68%
rename from app/src/main/java/com/tangem/tap/features/tokens/ui/compose/NetworkItem.kt
rename to app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt
index 9b75a86359..2928ace0d8 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/NetworkItem.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/NetworkItem.kt
@@ -1,10 +1,18 @@
-package com.tangem.tap.features.tokens.ui.compose
+package com.tangem.tap.features.tokens.ui.compose.currencyItem
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Switch
import androidx.compose.material.SwitchDefaults
@@ -31,20 +39,27 @@ import com.tangem.tap.domain.tokens.Contract
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
+import com.tangem.tap.features.tokens.ui.compose.CurrencyPlaceholderIcon
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun NetworkItem(
- currency: Currency, contract: Contract,
- blockchain: Blockchain, allowToAdd: Boolean,
- added: Boolean, canBeRemoved: Boolean,
+ currency: Currency,
+ contract: Contract,
+ blockchain: Blockchain,
+ allowToAdd: Boolean,
+ added: Boolean,
+ index: Int,
+ size: Int,
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
- onNetworkItemClicked: (ContractAddress) -> Unit
+ onNetworkItemClicked: (ContractAddress) -> Unit,
) {
+ val rowHeight = 53.dp
Row(
modifier = Modifier
.fillMaxWidth()
+ .height(rowHeight)
.combinedClickable(
enabled = allowToAdd,
onLongClick = {
@@ -52,13 +67,22 @@ fun NetworkItem(
},
onClick = {},
indication = null,
- interactionSource = remember { MutableInteractionSource() }
- )
+ interactionSource = remember { MutableInteractionSource() },
+ ),
) {
+ Box(
+ Modifier
+ .width(78.dp)
+ .padding(start = 38.dp),
+ ) {
+ CurrencyItemArrow(
+ rowHeight = rowHeight,
+ isLastArrow = index == size - 1,
+ )
+ }
Box(
modifier = Modifier
- .align(Alignment.CenterVertically)
- .padding(start = 8.dp, top = 16.dp, bottom = 16.dp, end = 6.dp)
+ .align(Alignment.CenterVertically),
) {
SubcomposeAsyncImage(
model = if (added) blockchain.getRoundIconRes() else blockchain.getGreyedOutIconRes(),
@@ -66,7 +90,7 @@ fun NetworkItem(
loading = { CurrencyPlaceholderIcon(blockchain.id) },
error = { CurrencyPlaceholderIcon(blockchain.id) },
modifier = Modifier
- .size(20.dp)
+ .size(20.dp),
)
if (contract.address == null) {
Box(
@@ -74,34 +98,31 @@ fun NetworkItem(
.align(Alignment.TopEnd)
.size(7.dp)
.clip(CircleShape)
- .background(Color.White)
+ .background(Color.White),
) {
Box(
modifier = Modifier
.align(Alignment.Center)
.size(5.dp)
.clip(CircleShape)
- .background(Color(0xFF1ACE80))
+ .background(Color(0xFF1ACE80)),
)
}
}
}
- Row(
+ Spacer(Modifier.width(6.dp))
+ Text(
modifier = Modifier
- .fillMaxHeight()
.weight(1f)
- .align(Alignment.CenterVertically)
- ) {
- Text(
- text = prepareNetworkNameSpannableText(
- blockchain = blockchain,
- contractAddress = contract.address
- ),
- fontWeight = FontWeight.SemiBold,
- fontSize = 13.sp,
- color = if (added) Color.Black else Color(0xFF848488),
- )
- }
+ .align(Alignment.CenterVertically),
+ text = prepareNetworkNameSpannableText(
+ blockchain = blockchain,
+ contractAddress = contract.address,
+ ),
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 13.sp,
+ color = if (added) Color.Black else Color(0xFF848488),
+ )
if (allowToAdd) {
val token = if (contract.address != null) {
@@ -115,8 +136,7 @@ fun NetworkItem(
} else {
null
}
- val tokenWithBlockchain =
- token?.let { TokenWithBlockchain(token, contract.blockchain) }
+ val tokenWithBlockchain = token?.let { TokenWithBlockchain(it, contract.blockchain) }
val currencyToSave = if (contract.address == null) {
currency.copy(id = contract.networkId)
@@ -125,23 +145,23 @@ fun NetworkItem(
}
Switch(
+ modifier = Modifier
+ .fillMaxHeight()
+ .padding(start = 16.dp, end = 16.dp),
checked = added,
- enabled = canBeRemoved,
onCheckedChange = { onAddCurrencyToggled(currencyToSave, tokenWithBlockchain) },
- modifier = Modifier.padding(start = 16.dp, end = 16.dp),
colors = SwitchDefaults.colors(
- checkedThumbColor = Color(0xFF1ACE80)
- )
+ checkedThumbColor = Color(0xFF1ACE80),
+ ),
)
}
}
}
-
@Composable
-fun prepareNetworkNameSpannableText(
+private fun prepareNetworkNameSpannableText(
blockchain: Blockchain,
- contractAddress: String?
+ contractAddress: String?,
): AnnotatedString {
val blockchainName = blockchain.fullNameWithoutTestnet.uppercase()
@@ -157,11 +177,11 @@ fun prepareNetworkNameSpannableText(
AnnotatedString.Range(
SpanStyle(
fontWeight = FontWeight.Normal,
- color = if (contractAddress != null) Color(0xFF8E8E93) else Color(0xFF1ACE80)
+ color = if (contractAddress != null) Color(0xFF8E8E93) else Color(0xFF1ACE80),
),
start = startOfAdditionalText,
- end = text.length
- )
+ end = text.length,
+ ),
)
return AnnotatedString(text = text, spanStyles = spanStyles)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt
new file mode 100644
index 0000000000..75f752b278
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt
@@ -0,0 +1,95 @@
+package com.tangem.tap.features.wallet.models
+
+import com.tangem.blockchain.common.DerivationStyle
+import com.tangem.domain.common.extensions.toCoinId
+import com.tangem.domain.features.addCustomToken.CustomCurrency
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
+
+sealed interface Currency {
+
+ val coinId: String?
+ get() = when (this) {
+ is Blockchain -> blockchain.toCoinId()
+ is Token -> token.id
+ }
+ val blockchain: com.tangem.blockchain.common.Blockchain
+ val currencySymbol: CryptoCurrencyName
+ val derivationPath: String?
+
+ val currencyName: String
+ get() = when (this) {
+ is Blockchain -> blockchain.fullName
+ is Token -> token.name
+ }
+
+ data class Token(
+ val token: com.tangem.blockchain.common.Token,
+ override val blockchain: com.tangem.blockchain.common.Blockchain,
+ override val derivationPath: String?
+ ) : Currency {
+ override val currencySymbol = token.symbol
+ }
+
+ data class Blockchain(
+ override val blockchain: com.tangem.blockchain.common.Blockchain,
+ override val derivationPath: String?
+ ) : Currency {
+ override val currencySymbol: CryptoCurrencyName = blockchain.currency
+ }
+
+ fun isCustomCurrency(derivationStyle: DerivationStyle?): Boolean {
+ if (this is Token && this.token.id == null) return true
+
+ if (derivationPath == null || derivationStyle == null) return false
+
+ return derivationPath != blockchain.derivationPath(derivationStyle)?.rawPath
+ }
+
+ fun isBlockchain(): Boolean = this is Blockchain
+
+ fun isToken(): Boolean = this is Token
+
+ companion object {
+ fun fromBlockchainNetwork(
+ blockchainNetwork: BlockchainNetwork,
+ token: com.tangem.blockchain.common.Token? = null
+ ): Currency {
+ return if (token != null) {
+ Token(
+ token = token,
+ blockchain = blockchainNetwork.blockchain,
+ derivationPath = blockchainNetwork.derivationPath
+ )
+ } else {
+ Blockchain(
+ blockchain = blockchainNetwork.blockchain,
+ derivationPath = blockchainNetwork.derivationPath
+ )
+ }
+ }
+
+ fun fromCustomCurrency(customCurrency: CustomCurrency): Currency {
+ return when (customCurrency) {
+ is CustomCurrency.CustomBlockchain -> Blockchain(
+ blockchain = customCurrency.network,
+ derivationPath = customCurrency.derivationPath?.rawPath
+ )
+ is CustomCurrency.CustomToken -> Token(
+ token = customCurrency.token,
+ blockchain = customCurrency.network,
+ derivationPath = customCurrency.derivationPath?.rawPath,
+ )
+ }
+ }
+
+ fun fromTokenWithBlockchain(tokenWithBlockchain: TokenWithBlockchain): Token {
+ return Token(
+ token = tokenWithBlockchain.token,
+ blockchain = tokenWithBlockchain.blockchain,
+ derivationPath = null
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
index c90af2af53..e079a6b114 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
@@ -11,16 +11,18 @@ import com.tangem.common.card.Card
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction
+import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.wallet.R
-import java.math.BigDecimal
import org.rekotlin.Action
+import java.math.BigDecimal
sealed class WalletAction : Action {
- object ResetState : WalletAction()
+ data class ResetState(val newCardId: String) : WalletAction()
data class SetIfTestnetCard(val isTestnet: Boolean) : WalletAction()
@@ -63,7 +65,9 @@ sealed class WalletAction : Action {
MultiWallet()
data class AddToken(val token: Token, val blockchain: BlockchainNetwork) : MultiWallet()
- data class SaveCurrencies(val blockchainNetworks: List) : MultiWallet()
+ data class SaveCurrencies(
+ val blockchainNetworks: List, val cardId: String? = null
+ ) : MultiWallet()
// object FindTokensInUse : MultiWallet()
// object FindBlockchainsInUse : MultiWallet()
@@ -74,9 +78,18 @@ sealed class WalletAction : Action {
) : MultiWallet()
data class SelectWallet(val walletData: WalletData?) : MultiWallet()
- data class RemoveWallet(val walletData: WalletData) : MultiWallet()
+
+ data class TryToRemoveWallet(val currency: Currency) : MultiWallet()
+ data class RemoveWallet(
+ val currency: Currency,
+ val fromScreen: AppScreen
+ ) : MultiWallet()
+
data class SetPrimaryBlockchain(val blockchain: Blockchain) : MultiWallet()
data class SetPrimaryToken(val token: Token) : MultiWallet()
+
+ data class ShowWalletBackupWarning(val show: Boolean) : MultiWallet()
+ object BackupWallet : MultiWallet()
}
sealed class Warnings : WalletAction() {
@@ -97,8 +110,6 @@ sealed class WalletAction : Action {
}
class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings()
-
- object RestoreFundsWarningClosed : Warnings()
}
data class LoadFiatRate(
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
index 749f84b926..48c886d939 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
@@ -1,26 +1,20 @@
package com.tangem.tap.features.wallet.redux
import android.graphics.Bitmap
-import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.extensions.isZero
-import com.tangem.domain.common.extensions.toCoinId
-import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.common.entities.Button
-import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.toQrCode
-import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.toggleWidget.WidgetState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
-import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.models.WalletRent
@@ -32,13 +26,13 @@ import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount
import com.tangem.tap.features.wallet.redux.reducers.findProgressState
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
-import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.store
+import org.rekotlin.StateType
import java.math.BigDecimal
import kotlin.properties.ReadOnlyProperty
-import org.rekotlin.StateType
data class WalletState(
+ val cardId: String = "",
val state: ProgressState = ProgressState.Done,
val error: ErrorType? = null,
val cardImage: Artwork? = null,
@@ -52,25 +46,20 @@ data class WalletState(
val primaryToken: Token? = null,
val isTestnet: Boolean = false,
val totalBalance: TotalBalance? = null,
+ val showBackupWarning: Boolean = false,
) : StateType {
// if you do not delegate - the application crashes on startup,
// because twinCardsState has not been created yet
- val twinCardsState: TwinCardsState by ReadOnlyProperty { thisRef, property ->
+ val twinCardsState: TwinCardsState by ReadOnlyProperty { _, _ ->
store.state.twinCardsState
}
val isTangemTwins: Boolean
get() = store.state.globalState.scanResponse?.isTangemTwins() == true
- val primaryWallet: WalletData? = wallets.firstOrNull()
- ?.walletsData?.firstOrNull()
- val primaryWalletManager: WalletManager? =
- if (wallets.isNotEmpty()) wallets[0].walletManager else null
-
- val shouldShowDetails: Boolean =
- primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
- primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
+ val isExchangeServiceFeatureOn: Boolean
+ get() = store.state.globalState.exchangeManager.featureIsSwitchedOn()
val blockchains: List
get() = wallets.mapNotNull { it.walletManager?.wallet?.blockchain }
@@ -84,6 +73,14 @@ data class WalletState(
val walletManagers: List
get() = wallets.mapNotNull { it.walletManager }
+ val primaryWallet: WalletData? = wallets.firstOrNull()?.walletsData?.firstOrNull()
+
+ val primaryWalletManager: WalletManager? = if (wallets.isNotEmpty()) wallets[0].walletManager else null
+
+ val shouldShowDetails: Boolean =
+ primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
+ primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
+
fun getWalletManager(currency: Currency?): WalletManager? {
if (currency?.blockchain == null) return null
return getWalletStore(currency)?.walletManager
@@ -256,32 +253,6 @@ data class WalletState(
return updatedWallets + remainingWallets
}
- fun updateTradeCryptoState(
- exchangeManager: CurrencyExchangeManager?,
- walletData: WalletData
- ): WalletData {
- return walletData.copy(
- tradeCryptoState = TradeCryptoState.from(
- exchangeManager,
- walletData
- )
- )
- }
-
- fun updateTradeCryptoState(
- exchangeManager: CurrencyExchangeManager?,
- walletDataList: List
- ): List {
- return walletDataList.map {
- it.copy(
- tradeCryptoState = TradeCryptoState.from(
- exchangeManager,
- it
- )
- )
- }
- }
-
private fun updateTotalBalance(): WalletState {
val walletsData = this.wallets
.flatMap(WalletStore::walletsData)
@@ -319,17 +290,6 @@ data class WalletState(
}
}
-sealed interface WalletDialog : StateDialog {
- data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog
- object SignedHashesMultiWalletDialog : WalletDialog
- object ChooseTradeActionDialog : WalletDialog
- data class CurrencySelectionDialog(
- val currenciesList: List,
- val currentAppCurrency: FiatCurrency,
- ) : WalletDialog
- object RussianCardholdersWarningDialog : WalletDialog
-}
-
enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error }
enum class ErrorType { NoInternetConnection }
@@ -371,39 +331,24 @@ data class Artwork(
}
}
-data class TradeCryptoState(
- val isAvailableToSell: () -> Boolean = { false },
- val isAvailableToBuy: () -> Boolean = { false },
-) {
- companion object {
- fun from(
- exchangeManager: CurrencyExchangeManager?,
- walletData: WalletData
- ): TradeCryptoState {
- val exchanger = exchangeManager ?: return walletData.tradeCryptoState
- val currency = walletData.currency
-
- return TradeCryptoState(
- isAvailableToSell = { exchanger.availableForSell(currency) },
- isAvailableToBuy = { exchanger.availableForBuy(currency) },
- )
- }
- }
-}
-
data class WalletData(
val pendingTransactions: List = emptyList(),
val hashesCountVerified: Boolean? = null,
val walletAddresses: WalletAddresses? = null,
val currencyData: BalanceWidgetData = BalanceWidgetData(),
val updatingWallet: Boolean = false,
- val tradeCryptoState: TradeCryptoState = TradeCryptoState(),
val fiatRateString: String? = null,
val fiatRate: BigDecimal? = null,
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
val currency: Currency,
val walletRent: WalletRent? = null,
) {
+ val isAvailableToBuy: Boolean
+ get() = store.state.globalState.exchangeManager.availableForBuy(currency)
+
+ val isAvailableToSell: Boolean
+ get() = store.state.globalState.exchangeManager.availableForSell(currency)
+
fun shouldShowMultipleAddress(): Boolean {
val listOfAddresses = walletAddresses?.list ?: return false
return listOfAddresses.size > 1
@@ -436,87 +381,6 @@ data class WalletData(
private fun tokenAmountIsEmpty(): Boolean = currencyData.amount?.isZero() == true
}
-sealed interface Currency {
-
- val coinId: String?
- get() = when (this) {
- is Blockchain -> blockchain.toCoinId()
- is Token -> token.id
- }
- val blockchain: com.tangem.blockchain.common.Blockchain
- val currencySymbol: CryptoCurrencyName
- val derivationPath: String?
-
- data class Token(
- val token: com.tangem.blockchain.common.Token,
- override val blockchain: com.tangem.blockchain.common.Blockchain,
- override val derivationPath: String?
- ) : Currency {
- override val currencySymbol = token.symbol
- }
-
- data class Blockchain(
- override val blockchain: com.tangem.blockchain.common.Blockchain,
- override val derivationPath: String?
- ) : Currency {
- override val currencySymbol: CryptoCurrencyName = blockchain.currency
- }
-
- fun isCustomCurrency(derivationStyle: DerivationStyle?): Boolean {
- if (this is Token && this.token.id == null) return true
-
- if (derivationPath == null || derivationStyle == null) return false
-
- return derivationPath != blockchain.derivationPath(derivationStyle)?.rawPath
- }
-
- fun isBlockchain(): Boolean = this is Blockchain
-
- fun isToken(): Boolean = this is Token
-
- companion object {
- fun fromBlockchainNetwork(
- blockchainNetwork: BlockchainNetwork,
- token: com.tangem.blockchain.common.Token? = null
- ): Currency {
- return if (token != null) {
- Token(
- token = token,
- blockchain = blockchainNetwork.blockchain,
- derivationPath = blockchainNetwork.derivationPath
- )
- } else {
- Blockchain(
- blockchain = blockchainNetwork.blockchain,
- derivationPath = blockchainNetwork.derivationPath
- )
- }
- }
-
- fun fromCustomCurrency(customCurrency: CustomCurrency): Currency {
- return when (customCurrency) {
- is CustomCurrency.CustomBlockchain -> Blockchain(
- blockchain = customCurrency.network,
- derivationPath = customCurrency.derivationPath?.rawPath
- )
- is CustomCurrency.CustomToken -> Token(
- token = customCurrency.token,
- blockchain = customCurrency.network,
- derivationPath = customCurrency.derivationPath?.rawPath,
- )
- }
- }
-
- fun fromTokenWithBlockchain(tokenWithBlockchain: TokenWithBlockchain): Token {
- return Token(
- token = tokenWithBlockchain.token,
- blockchain = tokenWithBlockchain.blockchain,
- derivationPath = null
- )
- }
- }
-}
-
data class WalletStore(
val walletManager: WalletManager?,
val blockchainNetwork: BlockchainNetwork,
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt
index d339e49202..f1fb6371cc 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt
@@ -9,7 +9,7 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.wallet.redux.WalletAction
-import com.tangem.tap.features.wallet.redux.WalletDialog
+import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.persistence.FiatCurrenciesPrefStorage
import com.tangem.tap.scope
import com.tangem.tap.store
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
index ace70c1988..4030bae09b 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
@@ -3,18 +3,24 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.WalletManager
+import com.tangem.common.extensions.guard
+import com.tangem.tap.common.extensions.dispatchDialogShow
+import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.safeUpdate
+import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
+import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.demo.isDemoCard
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.Dispatchers
@@ -27,7 +33,6 @@ class MultiWalletMiddleware {
action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?,
) {
val globalState = globalState ?: return
-// val tapWalletManager = globalState.tapWalletManager
when (action) {
is WalletAction.MultiWallet.AddBlockchains -> {
@@ -55,51 +60,93 @@ class MultiWalletMiddleware {
blockchainNetwork = action.blockchain
)
}
- store.dispatch(WalletAction.LoadFiatRate(
- coinsList = listOf(
- Currency.Blockchain(
- action.blockchain.blockchain,
- action.blockchain.derivationPath
+ store.dispatch(
+ WalletAction.LoadFiatRate(
+ coinsList = listOf(
+ Currency.Blockchain(
+ action.blockchain.blockchain,
+ action.blockchain.derivationPath
+ )
)
)
- ))
- store.dispatch(WalletAction.LoadWallet(
- action.blockchain, action.walletManager
- ))
+ )
+ store.dispatch(
+ WalletAction.LoadWallet(
+ action.blockchain, action.walletManager
+ )
+ )
}
is WalletAction.MultiWallet.SaveCurrencies -> {
- globalState.scanResponse?.card?.cardId?.let {
- currenciesRepository.saveCurrencies(it, action.blockchainNetworks)
+ val cardId = action.cardId ?: globalState.scanResponse?.card?.cardId ?: return
+ currenciesRepository.saveCurrencies(cardId, action.blockchainNetworks)
+ }
+ is WalletAction.MultiWallet.TryToRemoveWallet -> {
+ val currency = action.currency
+ val walletManager = walletState?.getWalletManager(currency).guard {
+ store.dispatchErrorNotification(TapError.UnsupportedState("walletManager is NULL"))
+ store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
+ return
+ }
+
+ if (currency.isBlockchain() && walletManager.cardTokens.isNotEmpty()) {
+ store.dispatchDialogShow(WalletDialog.TokensAreLinkedDialog(
+ currencyTitle = currency.currencyName,
+ currencySymbol = currency.currencySymbol
+ ))
+ } else {
+ store.dispatchDialogShow(WalletDialog.RemoveWalletDialog(
+ currencyTitle = currency.currencyName,
+ onOk = {
+ store.dispatch(WalletAction.MultiWallet.RemoveWallet(
+ currency = currency,
+ fromScreen = AppScreen.WalletDetails
+ ))
+ store.dispatch(NavigationAction.PopBackTo())
+ }
+ ))
}
}
is WalletAction.MultiWallet.RemoveWallet -> {
- val cardId = globalState.scanResponse?.card?.cardId
- when (val currency = action.walletData.currency) {
+ val currency = action.currency
+ val cardId = globalState.scanResponse?.card?.cardId.guard {
+ store.dispatchErrorNotification(TapError.UnsupportedState("cardId is NULL"))
+ store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
+ return
+ }
+
+ when (currency) {
is Currency.Blockchain -> {
- cardId?.let {
- currenciesRepository.removeBlockchain(
- cardId = it,
- blockchainNetwork = BlockchainNetwork(
- currency.blockchain, currency.derivationPath,
- emptyList()
- )
+ currenciesRepository.removeBlockchain(
+ cardId = cardId,
+ blockchainNetwork = BlockchainNetwork(
+ blockchain = currency.blockchain,
+ derivationPath = currency.derivationPath,
+ tokens = emptyList()
)
- }
+ )
}
is Currency.Token -> {
val walletManager = walletState?.getWalletManager(currency)
if (walletManager != null) {
walletManager.removeToken(currency.token)
- cardId?.let {
- currenciesRepository.removeToken(
- cardId = it,
- token = currency.token,
- blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
- )
- }
+ currenciesRepository.removeToken(
+ cardId = cardId,
+ token = currency.token,
+ blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
+ )
}
}
}
+ if (action.fromScreen == AppScreen.AddTokens) {
+ store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
+ }
+ }
+ is WalletAction.MultiWallet.ShowWalletBackupWarning -> Unit
+ is WalletAction.MultiWallet.BackupWallet -> {
+ store.state.globalState.scanResponse?.let {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
+ store.dispatch(GlobalAction.Onboarding.Start(it, fromHomeScreen = false))
+ }
}
// is WalletAction.MultiWallet.FindBlockchainsInUse -> {
// val scanResponse = globalState.scanResponse ?: return
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt
index ebbaae8538..319c940a52 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt
@@ -11,7 +11,7 @@ import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.send.redux.SendAction
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens
@@ -37,19 +37,18 @@ class TradeCryptoMiddleware {
action: WalletAction.TradeCryptoAction.Buy,
) {
if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
- store.dispatchOnMain(
- WalletAction.DialogAction.RussianCardholdersWarningDialog
- )
+ store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog)
return
}
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
- val exchangeManager = store.state.globalState.exchangeManager ?: return
- val appCurrency = store.state.globalState.appCurrency
+ val card = store.state.globalState.scanResponse?.card ?: return
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
if (addresses.isEmpty()) return
+ val exchangeManager = store.state.globalState.exchangeManager
+ val appCurrency = store.state.globalState.appCurrency
val currency = selectedWalletData.currency
if (currency is Currency.Token && currency.blockchain.isTestnet()) {
@@ -59,7 +58,13 @@ class TradeCryptoMiddleware {
return
}
- scope.launch { exchangeManager.buyErc20TestnetTokens(walletManager, currency.token) }
+ scope.launch {
+ exchangeManager.buyErc20TestnetTokens(
+ card = card,
+ walletManager = walletManager,
+ token = currency.token
+ )
+ }
return
}
@@ -74,15 +79,13 @@ class TradeCryptoMiddleware {
private fun proceedSellAction() {
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
- val exchangeManager = store.state.globalState.exchangeManager ?: return
- val appCurrency = store.state.globalState.appCurrency
+ val appCurrency = store.state.globalState.appCurrency
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
if (addresses.isEmpty()) return
val currency = selectedWalletData.currency
-
- exchangeManager.getUrl(
+ store.state.globalState.exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Sell,
blockchain = currency.blockchain,
cryptoCurrencyName = currency.currencySymbol,
@@ -93,8 +96,8 @@ class TradeCryptoMiddleware {
private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) {
val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
- val walletManager =
- store.state.walletState.getWalletManager(selectedWalletData.currency)
+
+ val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency)
store.dispatchOnMain(PrepareSendScreen(
coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin),
coinRate = selectedWalletData.fiatRate,
@@ -109,11 +112,10 @@ class TradeCryptoMiddleware {
}
private fun openReceiptUrl(transactionId: String) {
- val exchangeManager = store.state.globalState.exchangeManager ?: return
-
store.dispatchOnMain(NavigationAction.PopBackTo())
- exchangeManager.getSellCryptoReceiptUrl(CurrencyExchangeManager.Action.Sell, transactionId)?.let {
- store.dispatchOnMain(NavigationAction.OpenUrl(it))
- }
+ store.state.globalState.exchangeManager.getSellCryptoReceiptUrl(
+ action = CurrencyExchangeManager.Action.Sell,
+ transactionId = transactionId,
+ )?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt
index e760c446f2..d53809be9c 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt
@@ -4,7 +4,7 @@ import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.features.wallet.redux.WalletAction
-import com.tangem.tap.features.wallet.redux.WalletDialog
+import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.store
class WalletDialogsMiddleware {
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
index e4f7b6f659..7c63b551f9 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
@@ -1,8 +1,5 @@
package com.tangem.tap.features.wallet.redux.middlewares
-import android.content.Intent
-import android.net.Uri
-import androidx.core.content.ContextCompat
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
@@ -15,12 +12,7 @@ import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.analytics.Analytics
-import com.tangem.tap.common.extensions.copyToClipboard
-import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
-import com.tangem.tap.common.extensions.dispatchOnMain
-import com.tangem.tap.common.extensions.onCardScanned
-import com.tangem.tap.common.extensions.shareText
-import com.tangem.tap.common.extensions.stripZeroPlainString
+import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
@@ -31,9 +23,10 @@ import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.wallet.models.PendingTransactionType
+import com.tangem.tap.features.wallet.models.filterByCoin
import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.features.wallet.models.getSendableAmounts
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletState
@@ -232,9 +225,7 @@ class WalletMiddleware {
action.context.shareText(action.address)
}
is WalletAction.ExploreAddress -> {
- val uri = Uri.parse(action.exploreUrl)
- val intent = Intent(Intent.ACTION_VIEW, uri)
- ContextCompat.startActivity(action.context, intent, null)
+ store.dispatchOpenUrl(action.exploreUrl)
}
is WalletAction.Send -> {
val newAction = prepareSendAction(action.amount, store.state.walletState)
@@ -332,7 +323,10 @@ class WalletMiddleware {
}
val balance = walletManager.wallet.fundsAvailable(AmountType.Coin)
- val outgoingTxs = walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing)
+ val outgoingTxs = walletManager.wallet.getPendingTransactions(
+ PendingTransactionType.Outgoing
+ ).filterByCoin()
+
val rentExempt = result.data
val show = if (outgoingTxs.isEmpty()) {
isNeedToShowWarning(balance, rentExempt)
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
index 5197928d6e..7aa45f997d 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
@@ -8,7 +8,6 @@ import com.tangem.common.card.Card
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isTestCard
-import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.isGreaterThan
@@ -26,15 +25,15 @@ import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.wallet.R
+import java.math.BigDecimal
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import java.math.BigDecimal
class WarningsMiddleware {
fun handle(action: WalletAction.Warnings, globalState: GlobalState?) {
when (action) {
- WalletAction.Warnings.Update -> setWarningMessages()
+ is WalletAction.Warnings.Update -> setWarningMessages()
is WalletAction.Warnings.CheckIfNeeded -> {
showCardWarningsIfNeeded(globalState)
val readyToShow = preferencesStorage.appRatingLaunchObserver.isReadyToShow()
@@ -66,9 +65,11 @@ class WarningsMiddleware {
)
}
}
- is WalletAction.Warnings.RestoreFundsWarningClosed -> {
- preferencesStorage.saveRestoreFundsWarningClosed()
- }
+ is WalletAction.Warnings.AppRating,
+ is WalletAction.Warnings.CheckHashesCount,
+ is WalletAction.Warnings.CheckHashesCount.ConfirmHashesCount,
+ is WalletAction.Warnings.CheckHashesCount.NeedToCheckHashesCountOnline,
+ is WalletAction.Warnings.Set -> Unit
}
}
@@ -93,9 +94,6 @@ class WarningsMiddleware {
addWarningMessage(WarningMessagesManager.testCardWarning(), autoUpdate = true)
return@let
}
- if (card.useOldStyleDerivation && !preferencesStorage.wasRestoreFundsWarningClosed()) {
- addWarningMessage(warning = WarningMessagesManager.restoreFundsWarning())
- }
showWarningLowRemainingSignaturesIfNeeded(card)
if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) {
@@ -190,6 +188,7 @@ class WarningsMiddleware {
true
)
}
+ null -> Unit
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt
new file mode 100644
index 0000000000..68718acd45
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt
@@ -0,0 +1,35 @@
+package com.tangem.tap.features.wallet.redux.models
+
+import com.tangem.blockchain.common.Amount
+import com.tangem.tap.common.entities.FiatCurrency
+import com.tangem.tap.common.redux.StateDialog
+import com.tangem.wallet.R
+
+sealed interface WalletDialog : StateDialog {
+ data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog
+ object SignedHashesMultiWalletDialog : WalletDialog
+ object ChooseTradeActionDialog : WalletDialog
+ data class CurrencySelectionDialog(
+ val currenciesList: List,
+ val currentAppCurrency: FiatCurrency,
+ ) : WalletDialog
+
+ data class RemoveWalletDialog(
+ val currencyTitle: String,
+ val onOk: () -> Unit
+ ) : WalletDialog {
+ val messageRes: Int = R.string.token_details_hide_alert_message
+ val titleRes: Int = R.string.token_details_hide_alert_title
+ val primaryButtonRes: Int = R.string.token_details_hide_alert_hide
+ }
+
+ data class TokensAreLinkedDialog(
+ val currencyTitle: String,
+ val currencySymbol: String
+ ) : WalletDialog {
+ val messageRes: Int = R.string.token_details_unable_hide_alert_message
+ val titleRes: Int = R.string.token_details_unable_hide_alert_title
+ }
+
+ object RussianCardholdersWarningDialog : WalletDialog
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
index 80ccfca757..e8f9551910 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
@@ -7,9 +7,7 @@ import com.tangem.tap.common.extensions.toFiatString
import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
-import com.tangem.tap.features.wallet.models.filterByToken
-import com.tangem.tap.features.wallet.models.getPendingTransactions
-import com.tangem.tap.features.wallet.models.removeUnknownTransactions
+import com.tangem.tap.features.wallet.models.*
import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
import com.tangem.tap.features.wallet.ui.BalanceStatus
@@ -103,10 +101,10 @@ class MultiWalletReducer {
}
is WalletAction.MultiWallet.TokenLoaded -> {
val currency = Currency.fromBlockchainNetwork(action.blockchain, action.token)
- val wallet = state.getWalletManager(currency)?.wallet.guard {
- throw NullPointerException("MultiWallet.TokenLoaded: WalletManager must be no NULL")
+ val walletManager = state.getWalletManager(currency).guard {
+ throw NullPointerException("MultiWallet.TokenLoaded: WalletManager must be not NULL")
}
-
+ val wallet = walletManager.wallet
val pendingTransactions = wallet.getPendingTransactions()
val tokenPendingTransactions = pendingTransactions.filterByToken(action.token)
val tokenBalanceStatus = when {
@@ -136,7 +134,8 @@ class MultiWalletReducer {
token = action.token,
blockchain = action.blockchain.blockchain,
derivationPath = action.blockchain.derivationPath
- )
+ ),
+ walletRent = findWalletRent(state.getWalletStore(walletManager.wallet))
)
state.updateWalletData(newTokenWalletData)
}
@@ -145,20 +144,28 @@ class MultiWalletReducer {
is WalletAction.MultiWallet.SelectWallet ->
state.copy(selectedCurrency = action.walletData?.currency)
-
+ is WalletAction.MultiWallet.TryToRemoveWallet -> state
is WalletAction.MultiWallet.RemoveWallet -> {
- state.removeWallet(action.walletData)
+ state.removeWallet(state.getWalletData(action.currency))
}
is WalletAction.MultiWallet.SetPrimaryBlockchain ->
state.copy(primaryBlockchain = action.blockchain)
is WalletAction.MultiWallet.SetPrimaryToken ->
state.copy(primaryToken = action.token)
-// is WalletAction.MultiWallet.FindTokensInUse -> state
-// is WalletAction.MultiWallet.FindBlockchainsInUse -> state
is WalletAction.MultiWallet.SaveCurrencies -> state
+ is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy(
+ showBackupWarning = action.show
+ )
+ is WalletAction.MultiWallet.BackupWallet -> state
}
}
+
+ private fun findWalletRent(walletStore: WalletStore?): WalletRent? {
+ return walletStore?.walletsData?.firstOrNull {
+ it.walletRent != null
+ }?.walletRent
+ }
}
private fun addTokens(
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
index 50f4a1cec0..63d378718f 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
@@ -9,10 +9,13 @@ import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.common.extensions.toFormattedFiatValue
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.filterByToken
import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
-import com.tangem.tap.features.wallet.redux.*
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.tap.features.wallet.redux.WalletMainButton
+import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
@@ -37,8 +40,6 @@ class OnWalletLoadedReducer {
val walletData = walletState.getWalletData(blockchainNetwork) ?: return walletState
val fiatCurrency = store.state.globalState.appCurrency
- val exchangeManager = store.state.globalState.exchangeManager
-
val coinAmountValue = wallet.amounts[AmountType.Coin]?.value
val formattedAmount = coinAmountValue?.toFormattedCurrencyString(
wallet.blockchain.decimals(),
@@ -70,7 +71,6 @@ class OnWalletLoadedReducer {
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(isCoinSendButtonEnabled),
currency = Currency.fromBlockchainNetwork(blockchainNetwork),
- tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData),
)
val tokens = wallet.getTokens().mapNotNull { token ->
@@ -103,7 +103,6 @@ class OnWalletLoadedReducer {
),
pendingTransactions = tokenPendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled),
- tradeCryptoState = TradeCryptoState.from(exchangeManager, tokenWalletData),
)
}
val newWallets = tokens + newWalletData
@@ -117,8 +116,6 @@ class OnWalletLoadedReducer {
if (wallet.blockchain != walletState.primaryBlockchain) return walletState
val fiatCurrencyName = store.state.globalState.appCurrency.code
- val exchangeManager = store.state.globalState.exchangeManager
-
val token = wallet.getFirstToken()
val tokenData = if (token != null) {
val tokenAmount = wallet.getTokenAmount(token)
@@ -166,7 +163,6 @@ class OnWalletLoadedReducer {
),
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(sendButtonEnabled),
- tradeCryptoState = TradeCryptoState.from(exchangeManager, walletState.primaryWallet),
)
val wallets = listOfNotNull(walletData)
val updatedStore = walletState.getWalletStore(walletData?.currency)?.updateWallets(wallets)
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
index 3e39e7ed42..a4b679512e 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
@@ -6,17 +6,30 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.common.extensions.mapNotNullValues
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tap.common.entities.FiatCurrency
-import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.extensions.toFiatRateString
+import com.tangem.tap.common.extensions.toFiatString
+import com.tangem.tap.common.extensions.toFiatValue
+import com.tangem.tap.common.extensions.toFormattedCurrencyString
+import com.tangem.tap.common.extensions.toFormattedFiatValue
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.getArtworkUrl
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.WalletRent
-import com.tangem.tap.features.wallet.redux.*
+import com.tangem.tap.features.wallet.redux.AddressData
+import com.tangem.tap.features.wallet.redux.Artwork
+import com.tangem.tap.features.wallet.redux.ErrorType
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletAddresses
+import com.tangem.tap.features.wallet.redux.WalletData
+import com.tangem.tap.features.wallet.redux.WalletMainButton
+import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.features.wallet.redux.WalletStore
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
-import com.tangem.tap.store
import org.rekotlin.Action
import java.math.BigDecimal
@@ -34,14 +47,13 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
if (action !is WalletAction) return state.walletState
- val exchangeManager = store.state.globalState.exchangeManager
var newState = state.walletState
when (action) {
is WalletAction.Warnings -> newState = handleCheckSignedHashesActions(action, newState)
is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState)
- is WalletAction.ResetState -> newState = WalletState()
+ is WalletAction.ResetState -> newState = WalletState(cardId = action.newCardId)
is WalletAction.SetIfTestnetCard -> newState = newState.copy(isTestnet = action.isTestnet)
is WalletAction.EmptyWallet -> {
newState = newState.copy(
@@ -143,10 +155,6 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
currencySymbol = walletData.currencyData.currencySymbol,
),
mainButton = WalletMainButton.SendButton(false),
- tradeCryptoState = TradeCryptoState.from(
- exchangeManager,
- walletData
- )
)
}
)
@@ -170,13 +178,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
currencySymbol = wallet.currencyData.currencySymbol,
),
mainButton = WalletMainButton.SendButton(false),
- tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet)
)
}
- val wallets = newState.updateTradeCryptoState(
- exchangeManager,
- newState.replaceSomeWallets(newWallets)
- )
+ val wallets = newState.replaceSomeWallets(newWallets)
val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(wallets)
newState = newState.updateWalletStore(walletStore)
}
@@ -209,14 +213,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
)
}
- var updatedWalletStore = newState.getWalletStore(action.blockchain)
+ val updatedWalletStore = newState.getWalletStore(action.blockchain)
?.updateWallets(listOfNotNull(walletData))
- updatedWalletStore =
- updatedWalletStore?.updateWallets(
- newState.updateTradeCryptoState(exchangeManager, updatedWalletStore.walletsData)
- )
-
newState = newState.updateWalletStore(updatedWalletStore)
}
@@ -247,11 +246,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
)
}
- val updatedWallets =
- newState.updateTradeCryptoState(
- exchangeManager,
- walletStore!!.updateWallets(listOfNotNull(newWalletData) + tokenWallets).walletsData
- )
+ val updatedWallets = walletStore!!.updateWallets(listOfNotNull(newWalletData) + tokenWallets).walletsData
newState = newState.updateWalletsData(updatedWallets)
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
index 1cf3a038b2..ab07dd33d5 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
@@ -1,7 +1,11 @@
package com.tangem.tap.features.wallet.ui
import android.os.Bundle
-import android.view.*
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.annotation.ColorRes
@@ -11,20 +15,32 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
+import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.TestActions
-import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.extensions.appendIfNotNull
+import com.tangem.tap.common.extensions.beginDelayedTransition
+import com.tangem.tap.common.extensions.fitChipsByGroupWidth
+import com.tangem.tap.common.extensions.getColor
+import com.tangem.tap.common.extensions.getString
+import com.tangem.tap.common.extensions.hide
+import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.extensions.toQrCode
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.onboarding.getQRReceiveMessage
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.PendingTransaction
-import com.tangem.tap.features.wallet.redux.*
+import com.tangem.tap.features.wallet.redux.ErrorType
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletData
+import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter
-import com.tangem.tap.features.wallet.ui.images.loadCurrencyIcon
import com.tangem.tap.features.wallet.ui.test.TestWalletDetails
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -124,7 +140,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
setupAddressCard(selectedWallet)
setupNoInternetHandling(state)
setupBalanceData(selectedWallet.currencyData)
- setupButtons(selectedWallet)
+ setupButtons(selectedWallet, state.isExchangeServiceFeatureOn)
handleCurrencyIcon(selectedWallet)
handleWarnings(selectedWallet)
@@ -138,7 +154,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
selectedWallet.currency.derivationPath,
emptyList()
)
- ))
+ )
+ )
}
}
@@ -163,15 +180,13 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
private fun setupCurrency(currencyData: BalanceWidgetData, currency: Currency) = with(binding) {
tvCurrencyTitle.text = currencyData.currency
- if (currency is Currency.Token) {
- tvCurrencySubtitle.text = currency.blockchain.tokenDisplayName()
- tvCurrencySubtitle.show()
- } else {
- tvCurrencySubtitle.hide()
- }
+ tvCurrencySubtitle.text = tvCurrencySubtitle.getString(
+ R.string.wallet_currency_subtitle,
+ currency.blockchain.fullName
+ )
}
- private fun setupButtons(selectedWallet: WalletData) = with(binding) {
+ private fun setupButtons(selectedWallet: WalletData, isExchangeServiceFeatureOn: Boolean) = with(binding) {
lWalletDetails.btnCopy.setOnClickListener {
selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString ->
store.dispatch(WalletAction.CopyAddress(addressString, requireContext()))
@@ -184,8 +199,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
}
rowButtons.updateButtonsVisibility(
- buyAllowed = selectedWallet.tradeCryptoState.isAvailableToBuy(),
- sellAllowed = selectedWallet.tradeCryptoState.isAvailableToSell(),
+ exchangeServiceFeatureOn = isExchangeServiceFeatureOn,
+ buyAllowed = selectedWallet.isAvailableToBuy,
+ sellAllowed = selectedWallet.isAvailableToSell,
sendAllowed = selectedWallet.mainButton.enabled,
)
}
@@ -199,11 +215,12 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
}
private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) {
- loadCurrencyIcon(
- currencyImageView = ivCurrency,
- currencyTextView = tvTokenLetter,
- blockchain = wallet.currency.blockchain,
- token = (wallet.currency as? Currency.Token)?.token
+ ivCurrency.load(
+ currency = wallet.currency,
+ derivationStyle = store.state.globalState
+ .scanResponse
+ ?.card
+ ?.derivationStyle,
)
}
@@ -295,7 +312,10 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
lBalance.root.show()
lBalance.groupBalance.hide()
lBalance.tvError.show()
- lBalance.tvError.setWarningStatus(R.string.wallet_balance_blockchain_unreachable, data.errorMessage)
+ lBalance.tvError.setWarningStatus(
+ R.string.wallet_balance_blockchain_unreachable,
+ data.errorMessage
+ )
}
BalanceStatus.NoAccount -> {
lBalance.root.hide()
@@ -314,9 +334,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
return when (item.itemId) {
R.id.menu_remove -> {
store.state.walletState.getSelectedWalletData()?.let { walletData ->
- store.dispatch(WalletAction.MultiWallet.RemoveWallet(walletData))
- store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
- store.dispatch(NavigationAction.PopBackTo())
+ store.dispatch(WalletAction.MultiWallet.TryToRemoveWallet(walletData.currency))
true
}
false
@@ -327,10 +345,6 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.wallet_details, menu)
- val walletCanBeRemoved = store.state.walletState.canBeRemoved(
- store.state.walletState.getSelectedWalletData()
- )
- menu.getItem(0).isEnabled = walletCanBeRemoved
}
private fun TextView.setWarningStatus(mainMessage: Int, error: String? = null) {
@@ -346,7 +360,11 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
setStatus(getString(mainMessage), R.color.darkGray4, null)
}
- private fun TextView.setStatus(text: String, @ColorRes color: Int, @DrawableRes drawable: Int?) {
+ private fun TextView.setStatus(
+ text: String,
+ @ColorRes color: Int,
+ @DrawableRes drawable: Int?
+ ) {
this.text = text
setTextColor(getColor(color))
setCompoundDrawablesWithIntrinsicBounds(drawable ?: 0, 0, 0, 0)
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
index 176fa48a14..1389600f94 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
@@ -13,6 +13,8 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import coil.load
+import coil.size.Scale
+import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.tap.MainActivity
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
@@ -31,6 +33,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView
+import com.tangem.tap.features.wallet.ui.wallet.SaltPaySingleWalletView
import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView
import com.tangem.tap.features.wallet.ui.wallet.WalletView
import com.tangem.tap.store
@@ -112,16 +115,25 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber {
+ walletView = SaltPaySingleWalletView()
+ walletView.changeWalletView(this, binding)
+ }
+ state.isMultiwalletAllowed && state.primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
+ walletView !is MultiWalletView -> {
+ walletView = MultiWalletView()
+ walletView.changeWalletView(this, binding)
+ }
+ !state.isMultiwalletAllowed && walletView !is SingleWalletView -> {
+ walletView = SingleWalletView()
+ walletView.changeWalletView(this, binding)
+ }
+ else -> {} // we keep the same view unless we scan a card that requires a different view
}
+
walletView.onNewState(state)
if (!state.shouldShowDetails) {
@@ -133,7 +145,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber R.drawable.ic_arrow_right_20
PendingTransactionType.Unknown -> return
}
- binding.tvPendingTransaction.text = binding.root.getString(transactionDescriptionRes)
+ binding.tvPendingTransaction.text =
+ binding.root.getString(transactionDescriptionRes).let { "$it " }
transaction.amountValueUi?.let { binding.tvPendingTransactionAmount.text = "$it " }
- binding.tvPendingTransactionCurrency.text = "${transaction.currency}"
+ binding.tvPendingTransactionCurrency.text = transaction.currency
if (transaction.address != null) {
binding.tvPendingTransactionAddress.text =
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt
index 26f5b8da86..4be9104bb1 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt
@@ -12,11 +12,9 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
-import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.ui.BalanceStatus
-import com.tangem.tap.features.wallet.ui.images.loadCurrencyIcon
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.ItemCurrencyWalletBinding
@@ -66,12 +64,6 @@ class WalletAdapter
// Skip changes when on refreshing status
if (status == BalanceStatus.Refreshing) return@with
- val isCustomCurrency = wallet.currency.isCustomCurrency(
- derivationStyle = store.state.globalState
- .scanResponse
- ?.card
- ?.derivationStyle
- )
val statusMessage = when (status) {
BalanceStatus.TransactionInProgress -> {
root.getString(R.string.wallet_balance_tx_in_progress)
@@ -86,7 +78,8 @@ class WalletAdapter
BalanceStatus.UnknownBlockchain,
BalanceStatus.Loading,
BalanceStatus.Refreshing,
- null -> null
+ null,
+ -> null
}
if (status == null || status == BalanceStatus.Loading) {
@@ -97,11 +90,12 @@ class WalletAdapter
lContent.root.show()
}
- loadCurrencyIcon(
- currencyImageView = ivCurrency,
- currencyTextView = tvTokenLetter,
- token = (wallet.currency as? Currency.Token)?.token,
- blockchain = wallet.currency.blockchain,
+ ivCurrency.load(
+ currency = wallet.currency,
+ derivationStyle = store.state.globalState
+ .scanResponse
+ ?.card
+ ?.derivationStyle,
)
lContent.tvCurrency.text = wallet.currencyData.currency
@@ -115,8 +109,6 @@ class WalletAdapter
lContent.tvExchangeRate.text = wallet.fiatRateString
?: root.getString(id = R.string.token_item_no_rate)
- badgeCustomBalance.isVisible = isCustomCurrency
-
cardWallet.setOnClickListener {
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet))
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt
index 9014d5d4ff..dee8fef67c 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt
@@ -1,25 +1,21 @@
package com.tangem.tap.features.wallet.ui.adapters
-import android.content.res.Resources
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.core.os.ConfigurationCompat
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.play.core.review.ReviewManagerFactory
import com.tangem.tap.common.analytics.AnalyticsEvent
-import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.getActivity
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.feedback.RateCanBeBetterEmail
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
-import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
-import com.tangem.tap.features.feedback.RateCanBeBetterEmail
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -89,26 +85,12 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
binding.btnClose.hide()
val buttonAction =
- when {
- warning.titleResId == R.string.warning_important_security_info -> {
+ when (warning.titleResId) {
+ R.string.warning_important_security_info -> {
View.OnClickListener {
store.dispatch(WalletAction.DialogAction.SignedHashesMultiWalletDialog)
}
}
- warning.messageResId == R.string.alert_funds_restoration_message -> {
- binding.btnClose.show()
- binding.btnClose.setOnClickListener {
- store.dispatch(GlobalAction.HideWarningMessage(warning))
- store.dispatch(WalletAction.Warnings.RestoreFundsWarningClosed)
- }
- val locale = ConfigurationCompat
- .getLocales(Resources.getSystem().configuration)
- .get(0)
- val url = WarningMessagesManager.getRestoreFundsGuideUrl(locale.language)
- View.OnClickListener {
- store.dispatchOpenUrl(url)
- }
- }
else -> {
View.OnClickListener {
store.dispatch(GlobalAction.HideWarningMessage(warning))
@@ -135,7 +117,7 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE)
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
store.dispatch(GlobalAction.HideWarningMessage(warning))
- store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail()))
+ store.dispatch(GlobalAction.SendEmail(RateCanBeBetterEmail()))
}
binding.btnReallyCool.setOnClickListener {
val activity = binding.root.context.getActivity() ?: return@setOnClickListener
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt
index 2f9e597096..ff82942f2b 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt
@@ -5,16 +5,12 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.*
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.blockchain.common.Amount
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.wallet.redux.WalletAction
-import com.tangem.tap.features.wallet.redux.WalletDialog
+import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.DialogWalletSendBinding
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt
index 496a8ff731..91afcc3819 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt
@@ -3,8 +3,8 @@ package com.tangem.tap.features.wallet.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tap.common.extensions.dispatchDialogHide
+import com.tangem.tap.common.feedback.ScanFailsEmail
import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.features.feedback.ScanFailsEmail
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -19,7 +19,7 @@ class ScanFailsDialog {
setTitle(context.getString(R.string.common_warning))
setMessage(R.string.alert_troubleshooting_scan_card_title)
setPositiveButton(R.string.alert_button_request_support) { _, _ ->
- store.dispatch(GlobalAction.SendFeedback(ScanFailsEmail()))
+ store.dispatch(GlobalAction.SendEmail(ScanFailsEmail()))
}
setNeutralButton(R.string.alert_troubleshooting_scan_card_ok) { _, _ -> }
setOnDismissListener { store.dispatchDialogHide() }
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt
index 0561426bad..cd7959319e 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt
@@ -5,7 +5,6 @@ import android.widget.TextView
import androidx.constraintlayout.utils.widget.ImageFilterView
import coil.imageLoader
import coil.request.ImageRequest
-import coil.transform.RoundedCornersTransformation
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.IconsUtil
import com.tangem.blockchain.common.Token
@@ -20,22 +19,7 @@ import com.tangem.wallet.R
private const val QCX = "QCX"
private const val VOYR = "VOYRME"
-fun loadCurrencyIcon(
- currencyImageView: ImageFilterView,
- currencyTextView: TextView,
- token: Token?,
- blockchain: Blockchain,
-) {
- CurrencyIconLoader(
- currencyImageView = currencyImageView,
- currencyTextView = currencyTextView,
- token = token,
- blockchain = blockchain
- )
- .load()
-}
-
-private class CurrencyIconLoader(
+class CurrencyIconRequest(
private val currencyImageView: ImageFilterView,
private val currencyTextView: TextView,
private val token: Token?,
@@ -135,9 +119,6 @@ private inline fun ImageView.loadIcon(
.placeholder(placeholderRes)
.error(placeholderRes)
.fallback(placeholderRes)
- .transformations(
- RoundedCornersTransformation(radius = 8f)
- )
.listener(
onStart = { onStart() },
onSuccess = { _, _ -> onSuccess() },
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt
new file mode 100644
index 0000000000..ec9ff38519
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt
@@ -0,0 +1,71 @@
+package com.tangem.tap.features.wallet.ui.images
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.TextView
+import androidx.annotation.DrawableRes
+import androidx.constraintlayout.utils.widget.ImageFilterView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isVisible
+import com.tangem.blockchain.common.DerivationStyle
+import com.tangem.tangem_sdk_new.extensions.dpToPx
+import com.tangem.tap.common.extensions.getRoundIconRes
+import com.tangem.tap.features.wallet.models.Currency
+import com.tangem.wallet.databinding.ViewCurrencyIconBinding
+import kotlin.math.roundToInt
+
+class CurrencyIconView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+ private val binding = ViewCurrencyIconBinding.inflate(
+ LayoutInflater.from(context),
+ this,
+ )
+
+ private val currencyImageView: ImageFilterView
+ get() = binding.ivCurrency
+
+ private val currencyTextView: TextView
+ get() = binding.tvTokenLetter
+
+ private var isBlockchainIconVisible: Boolean
+ get() = binding.ivBlockchain.isVisible
+ set(value) = binding.ivBlockchain::isVisible.set(value)
+
+ private var isBadgeVisible: Boolean
+ get() = binding.badge.isVisible
+ set(value) = binding.badge::isVisible.set(value)
+
+ @DrawableRes
+ private var blockchainIconRes: Int? = null
+ set(value) {
+ if (value != null && value != field && isBlockchainIconVisible) {
+ binding.ivBlockchain.setImageResource(value)
+ field = value
+ }
+ }
+
+ init {
+ minWidth = dpToPx(48f).roundToInt()
+ minHeight = dpToPx(48f).roundToInt()
+ }
+
+ fun load(
+ currency: Currency,
+ derivationStyle: DerivationStyle?,
+ ) {
+ isBlockchainIconVisible = currency.isToken()
+ isBadgeVisible = currency.isCustomCurrency(derivationStyle)
+ blockchainIconRes = currency.blockchain.getRoundIconRes()
+
+ CurrencyIconRequest(
+ currencyImageView = currencyImageView,
+ currencyTextView = currencyTextView,
+ token = (currency as? Currency.Token)?.token,
+ blockchain = currency.blockchain,
+ ).load()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt
index 12efe010c8..582b5f178c 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/WalletDetailsButtonsRow.kt
@@ -14,9 +14,8 @@ internal class WalletDetailsButtonsRow @JvmOverloads constructor(
) : LinearLayout(context, attrs, defStyleAttr) {
private val binding = ViewWalletDetailsButtonsRowBinding.inflate(
LayoutInflater.from(context),
- this
+ this,
)
-
var onBuyClick: (() -> Unit)? = null
var onSellClick: (() -> Unit)? = null
var onTradeClick: (() -> Unit)? = null
@@ -34,10 +33,13 @@ internal class WalletDetailsButtonsRow @JvmOverloads constructor(
}
fun updateButtonsVisibility(
+ exchangeServiceFeatureOn: Boolean,
buyAllowed: Boolean,
sellAllowed: Boolean,
- sendAllowed: Boolean
+ sendAllowed: Boolean,
) = with(binding) {
+ containerExchangeButtons.isVisible = exchangeServiceFeatureOn
+
btnBuy.isVisible = (buyAllowed && !sellAllowed) || (!buyAllowed && !sellAllowed)
btnBuy.isEnabled = buyAllowed
btnSell.isVisible = !buyAllowed && sellAllowed
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/CurrencySelectionDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/CurrencySelectionDialog.kt
index f47a0b3ea1..6cce3b391b 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/CurrencySelectionDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/CurrencySelectionDialog.kt
@@ -3,7 +3,7 @@ package com.tangem.tap.features.wallet.ui.wallet
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tap.features.wallet.redux.WalletAction
-import com.tangem.tap.features.wallet.redux.WalletDialog
+import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.store
import com.tangem.wallet.R
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt
index 5646b437f0..73504ddbfc 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.features.wallet.ui.wallet
+import android.widget.Button
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.tangem.common.card.Card
@@ -20,33 +21,26 @@ import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.features.wallet.ui.adapters.WalletAdapter
+import com.tangem.tap.features.wallet.ui.view.WalletDetailsButtonsRow
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentWalletBinding
-
-class MultiWalletView : WalletView {
-
- private var fragment: WalletFragment? = null
- private var binding: FragmentWalletBinding? = null
-
+class MultiWalletView : WalletView() {
private lateinit var walletsAdapter: WalletAdapter
-
-
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
setFragment(fragment, binding)
onViewCreated()
showMultiWalletView(binding)
}
-
private fun showMultiWalletView(binding: FragmentWalletBinding) = with(binding) {
tvTwinCardNumber.hide()
rvPendingTransaction.hide()
lCardBalance.root.hide()
lAddress.root.hide()
- lButtonsShort.root.hide()
- lButtonsLong.root.hide()
+ rowButtons.hide()
+ lSingleWalletBalance.root.hide()
rvMultiwallet.show()
btnAddToken.show()
setupWalletCardNumber(binding)
@@ -64,16 +58,6 @@ class MultiWalletView : WalletView {
}
}
- override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
- this.fragment = fragment
- this.binding = binding
- }
-
- override fun removeFragment() {
- this.fragment = null
- this.binding = null
- }
-
override fun onViewCreated() {
setupWalletsRecyclerView()
}
@@ -91,6 +75,7 @@ class MultiWalletView : WalletView {
val binding = binding ?: return
handleTotalBalance(binding, state.totalBalance)
+ handleBackupWarning(binding, state.showBackupWarning)
walletsAdapter.submitList(state.walletsData, state.primaryBlockchain, state.primaryToken)
binding.btnAddToken.setOnClickListener {
@@ -99,27 +84,33 @@ class MultiWalletView : WalletView {
TokensAction.LoadCurrencies(
supportedBlockchains = currenciesRepository.getBlockchains(
card.firmwareVersion,
- card.isTestCard
+ card.isTestCard,
),
- scanResponse = store.state.globalState.scanResponse
- )
+ scanResponse = store.state.globalState.scanResponse,
+ ),
)
store.dispatch(TokensAction.AllowToAddTokens(true))
store.dispatch(
TokensAction.SetAddedCurrencies(
wallets = state.walletsData,
- derivationStyle = card.derivationStyle
- )
- )
- store.dispatch(
- TokensAction.SetNonRemovableCurrencies(
- state.walletsData.filterNot { state.canBeRemoved(it) })
+ derivationStyle = card.derivationStyle,
+ ),
)
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
}
handleErrorStates(state = state, binding = binding, fragment = fragment)
}
+ private fun handleBackupWarning(
+ binding: FragmentWalletBinding,
+ showBackupWarning: Boolean,
+ ) = with(binding.lWalletBackupWarning) {
+ root.isVisible = showBackupWarning
+ root.setOnClickListener {
+ store.dispatch(WalletAction.MultiWallet.BackupWallet)
+ }
+ }
+
private fun handleTotalBalance(
binding: FragmentWalletBinding,
totalBalance: TotalBalance?,
@@ -135,11 +126,11 @@ class MultiWalletView : WalletView {
veilBalance.unVeil()
}
tvProcessing.animateVisibility(
- show = totalBalance.state == ProgressState.Error
+ show = totalBalance.state == ProgressState.Error,
)
tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString(
- currencySymbol = totalBalance.fiatCurrency.symbol
+ currencySymbol = totalBalance.fiatCurrency.symbol,
)
tvCurrencyName.text = totalBalance.fiatCurrency.code
@@ -152,14 +143,14 @@ class MultiWalletView : WalletView {
private fun handleErrorStates(
state: WalletState,
binding: FragmentWalletBinding,
- fragment: WalletFragment
+ fragment: WalletFragment,
) {
when (state.primaryWallet?.currencyData?.status) {
BalanceStatus.EmptyCard -> {
showErrorState(
binding,
fragment.getText(R.string.wallet_error_empty_card),
- fragment.getString(R.string.wallet_error_empty_card_subtitle)
+ fragment.getString(R.string.wallet_error_empty_card_subtitle),
)
configureButtonsForEmptyWalletState(binding)
}
@@ -167,7 +158,7 @@ class MultiWalletView : WalletView {
showErrorState(
binding,
fragment.getText(R.string.wallet_error_unsupported_blockchain),
- fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
+ fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle),
)
}
else -> { /* no-op */
@@ -191,9 +182,21 @@ class MultiWalletView : WalletView {
private fun configureButtonsForEmptyWalletState(binding: FragmentWalletBinding) =
with(binding) {
- lButtonsLong.root.show()
- lButtonsLong.btnConfirmLong.setOnClickListener { store.dispatch(WalletAction.CreateWallet) }
- lButtonsLong.btnConfirmLong.text =
- fragment?.getText(R.string.wallet_button_create_wallet)
+ rowButtons.btnBuy.hide()
+ rowButtons.btnSell.hide()
+ rowButtons.btnTrade.hide()
+ rowButtons.show()
+
+ rowButtons.btnSend.text = fragment?.getText(R.string.wallet_button_create_wallet)
+ rowButtons.onSendClick = { store.dispatch(WalletAction.CreateWallet) }
}
-}
\ No newline at end of file
+}
+
+private val WalletDetailsButtonsRow.btnBuy: Button
+ get() = this.findViewById(R.id.btn_buy)
+private val WalletDetailsButtonsRow.btnSell: Button
+ get() = this.findViewById(R.id.btn_sell)
+private val WalletDetailsButtonsRow.btnTrade: Button
+ get() = this.findViewById(R.id.btn_trade)
+private val WalletDetailsButtonsRow.btnSend: Button
+ get() = this.findViewById(R.id.btn_send)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt
new file mode 100644
index 0000000000..95509f5b37
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt
@@ -0,0 +1,51 @@
+package com.tangem.tap.features.wallet.ui.wallet
+
+import com.tangem.tap.common.entities.FiatCurrency
+import com.tangem.tap.common.extensions.animateVisibility
+import com.tangem.tap.common.extensions.formatAmountAsSpannedString
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+import com.tangem.wallet.databinding.LayoutSingleWalletBalanceBinding
+import java.math.BigDecimal
+
+data class SaltPayBalanceWidgetData(
+ val state: ProgressState? = null,
+ val currencySymbol: String? = null,
+ val currency: String? = null,
+ val fiatAmount: BigDecimal? = null,
+ val fiatCurrency: FiatCurrency? = null,
+)
+
+class SaltPayBalanceWidget(
+ private val binding: LayoutSingleWalletBalanceBinding,
+ private val data: SaltPayBalanceWidgetData,
+) {
+ fun setup(): Unit = with(binding) {
+ if (data.state == ProgressState.Loading) {
+ veilBalance.veil()
+ veilBalanceCrypto.veil()
+ } else {
+ veilBalance.unVeil()
+ veilBalanceCrypto.unVeil()
+ }
+ tvProcessing.animateVisibility(
+ show = data.state == ProgressState.Error,
+ )
+ veilBalanceCrypto.animateVisibility(
+ show = data.state != ProgressState.Error,
+ )
+ tvBalance.text = data.fiatAmount?.formatAmountAsSpannedString(
+ currencySymbol = data.fiatCurrency?.symbol ?: "",
+ )
+ tvBalanceCrypto.text = data.currency
+
+ tvCurrencyName.text = data.fiatCurrency?.code
+
+ tvCurrencyName.setOnClickListener {
+ store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
+ }
+ }
+}
+
+
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt
new file mode 100644
index 0000000000..09d00b294e
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt
@@ -0,0 +1,51 @@
+package com.tangem.tap.features.wallet.ui.wallet
+
+import com.tangem.tap.common.extensions.hide
+import com.tangem.tap.common.extensions.show
+import com.tangem.tap.features.wallet.redux.WalletData
+import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.features.wallet.ui.WalletFragment
+import com.tangem.tap.store
+import com.tangem.wallet.databinding.FragmentWalletBinding
+
+class SaltPaySingleWalletView : WalletView() {
+ override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
+ setFragment(fragment, binding)
+ onViewCreated()
+ showSingleWalletView(binding)
+ }
+
+ private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) {
+ rvMultiwallet.hide()
+ btnAddToken.hide()
+ rvPendingTransaction.hide()
+ tvTwinCardNumber.hide()
+ lCardBalance.root.hide()
+ lAddress.root.hide()
+ lSingleWalletBalance.root.show()
+ }
+
+ override fun onViewCreated() {
+ }
+
+ override fun onNewState(state: WalletState) {
+ val binding = binding ?: return
+ state.primaryWallet ?: return
+
+ setupBalance(state, state.primaryWallet, binding)
+ }
+
+ private fun setupBalance(state: WalletState, primaryWallet: WalletData, binding: FragmentWalletBinding) {
+ binding.lSingleWalletBalance.root.show()
+ SaltPayBalanceWidget(
+ binding = binding.lSingleWalletBalance,
+ data = SaltPayBalanceWidgetData(
+ state = state.state,
+ currencySymbol = primaryWallet.currencyData.currencySymbol,
+ currency = primaryWallet.currencyData.amountFormatted,
+ fiatAmount = primaryWallet.currencyData.fiatAmount,
+ fiatCurrency = store.state.globalState.appCurrency,
+ ),
+ ).setup()
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
index 7c431c33b7..ab271db8fb 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
@@ -2,7 +2,6 @@ package com.tangem.tap.features.wallet.ui.wallet
import android.view.View
import android.view.ViewGroup
-import android.widget.Button
import androidx.recyclerview.widget.LinearLayoutManager
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tap.common.extensions.beginDelayedTransition
@@ -10,32 +9,23 @@ import com.tangem.tap.common.extensions.fitChipsByGroupWidth
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.PendingTransaction
-import com.tangem.tap.features.wallet.redux.*
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletData
+import com.tangem.tap.features.wallet.redux.WalletMainButton
+import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.BalanceWidget
import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
+import com.tangem.tap.features.wallet.ui.view.WalletDetailsButtonsRow
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentWalletBinding
-class SingleWalletView : WalletView {
-
+class SingleWalletView : WalletView() {
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
- private var fragment: WalletFragment? = null
- private var binding: FragmentWalletBinding? = null
-
- override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
- this.fragment = fragment
- this.binding = binding
- }
-
- override fun removeFragment() {
- fragment = null
- binding = null
- }
-
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
setFragment(fragment, binding)
onViewCreated()
@@ -48,13 +38,13 @@ class SingleWalletView : WalletView {
rvPendingTransaction.hide()
lCardBalance.root.show()
lAddress.root.show()
+ lSingleWalletBalance.root.hide()
}
override fun onViewCreated() {
setupTransactionsRecyclerView()
}
-
private fun setupTransactionsRecyclerView() {
val fragment = fragment ?: return
pendingTransactionAdapter = PendingTransactionsAdapter()
@@ -64,12 +54,11 @@ class SingleWalletView : WalletView {
}
override fun onNewState(state: WalletState) {
- val fragment = fragment ?: return
val binding = binding ?: return
state.primaryWallet ?: return
setupTwinCards(state.twinCardsState, binding)
- setupButtons(state.primaryWallet, state.isTangemTwins, binding)
+ setupButtons(state.primaryWallet, binding, state.isExchangeServiceFeatureOn)
setupAddressCard(state.primaryWallet, binding)
showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions)
setupBalance(state, state.primaryWallet)
@@ -80,7 +69,6 @@ class SingleWalletView : WalletView {
binding?.rvPendingTransaction?.show(pendingTransactions.isNotEmpty())
}
-
private fun setupBalance(state: WalletState, primaryWallet: WalletData) {
val fragment = fragment ?: return
binding?.apply {
@@ -89,14 +77,12 @@ class SingleWalletView : WalletView {
binding = this.lCardBalance,
fragment = fragment,
data = primaryWallet.currencyData,
- isTwinCard = state.isTangemTwins
+ isTwinCard = state.isTangemTwins,
).setup()
}
}
- private fun setupTwinCards(
- twinCardsState: TwinCardsState?, binding: FragmentWalletBinding
- ) = with(binding) {
+ private fun setupTwinCards(twinCardsState: TwinCardsState?, binding: FragmentWalletBinding) = with(binding) {
twinCardsState?.cardNumber?.let { cardNumber ->
tvTwinCardNumber.show()
val number = when (cardNumber) {
@@ -112,101 +98,53 @@ class SingleWalletView : WalletView {
}
private fun setupButtons(
- state: WalletData, isTwinsWallet: Boolean, binding: FragmentWalletBinding
+ walletData: WalletData,
+ binding: FragmentWalletBinding,
+ isExchangeServiceFeatureEnabled: Boolean,
) = with(binding) {
-
- setupButtonsType(state, binding)
-
- val tradeState = state.tradeCryptoState
- val btnConfirm = if (tradeState.isAvailableToSell() || tradeState.isAvailableToBuy()) {
- lButtonsShort.btnConfirm
- } else {
- lButtonsLong.btnConfirmLong
- }
-
- setupConfirmButton(state, btnConfirm, isTwinsWallet)
+ setupRowButtons(walletData, rowButtons, isExchangeServiceFeatureEnabled)
lAddress.btnCopy.setOnClickListener {
- state.walletAddresses?.selectedAddress?.address?.let { addressString ->
+ walletData.walletAddresses?.selectedAddress?.address?.let { addressString ->
store.dispatch(WalletAction.CopyAddress(addressString, fragment!!.requireContext()))
}
}
lAddress.btnShowQr.setOnClickListener {
- state.walletAddresses?.selectedAddress?.let { selectedAddress ->
+ walletData.walletAddresses?.selectedAddress?.let { selectedAddress ->
store.dispatch(
WalletAction.DialogAction.QrCode(
- currency = state.currency,
- selectedAddress = selectedAddress
- )
+ currency = walletData.currency,
+ selectedAddress = selectedAddress,
+ ),
)
}
}
-
- setupTradeButton(binding, state.tradeCryptoState)
}
- private fun setupTradeButton(binding: FragmentWalletBinding, tradeCryptoState: TradeCryptoState) {
- val allowedToBuy = tradeCryptoState.isAvailableToBuy()
- val allowedToSell = tradeCryptoState.isAvailableToSell()
- val action = when {
- allowedToBuy && !allowedToSell -> WalletAction.TradeCryptoAction.Buy()
- !allowedToBuy && allowedToSell -> WalletAction.TradeCryptoAction.Sell
- allowedToBuy && allowedToSell -> WalletAction.DialogAction.ChooseTradeActionDialog
- else -> null
- }
- val text = when {
- allowedToBuy && !allowedToSell -> R.string.wallet_button_buy
- !allowedToBuy && allowedToSell -> R.string.wallet_button_sell
- allowedToBuy && allowedToSell -> R.string.wallet_button_trade
- else -> R.string.wallet_button_trade
- }
- val icon = when {
- allowedToBuy && !allowedToSell -> R.drawable.ic_arrow_up
- !allowedToBuy && allowedToSell -> R.drawable.ic_arrow_down
- allowedToBuy && allowedToSell -> R.drawable.ic_arrows_up_down
- else -> null
- }
- with(binding) {
- lButtonsShort.btnTrade.text = fragment?.getText(text)
- icon?.let { lButtonsShort.btnTrade.setIconResource(it) }
- lButtonsShort.btnTrade.setOnClickListener { if (action != null) store.dispatch(action) }
- }
- }
-
- private fun setupButtonsType(state: WalletData, binding: FragmentWalletBinding) = with(binding) {
- if (state.tradeCryptoState.isAvailableToSell() || state.tradeCryptoState.isAvailableToBuy()) {
- lButtonsLong.root.hide()
- lButtonsShort.root.show()
- } else {
- lButtonsLong.root.show()
- lButtonsShort.root.hide()
- }
- }
-
- private fun setupConfirmButton(
- state: WalletData, btnConfirm: Button, isTwinsWallet: Boolean,
+ private fun setupRowButtons(
+ walletData: WalletData,
+ rowButtons: WalletDetailsButtonsRow,
+ isExchangeServiceFeatureEnabled: Boolean,
) {
- val buttonTitle = when (state.mainButton) {
- is WalletMainButton.SendButton -> R.string.wallet_button_send
- is WalletMainButton.CreateWalletButton -> {
- if (!isTwinsWallet) {
- R.string.wallet_button_create_wallet
- } else {
- R.string.wallet_button_create_twin_wallet
- }
- }
- }
- btnConfirm.text = fragment?.getString(buttonTitle)
- btnConfirm.isEnabled = state.mainButton.enabled
- btnConfirm.setOnClickListener {
- when (state.mainButton) {
+ rowButtons.updateButtonsVisibility(
+ exchangeServiceFeatureOn = isExchangeServiceFeatureEnabled,
+ buyAllowed = walletData.isAvailableToBuy,
+ sellAllowed = walletData.isAvailableToSell,
+ sendAllowed = walletData.mainButton.enabled,
+ )
+
+ rowButtons.onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) }
+ rowButtons.onSendClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
+ rowButtons.onTradeClick = { store.dispatch(WalletAction.DialogAction.ChooseTradeActionDialog) }
+
+ rowButtons.onSendClick = {
+ when (walletData.mainButton) {
is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send())
is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet)
}
}
}
-
private fun setupAddressCard(state: WalletData, binding: FragmentWalletBinding) = with(binding.lAddress) {
if (state.walletAddresses != null && state.currency is Currency.Blockchain) {
binding.lAddress.root.show()
@@ -214,7 +152,6 @@ class SingleWalletView : WalletView {
(binding.lAddress.root as? ViewGroup)?.beginDelayedTransition()
chipGroupAddressType.show()
chipGroupAddressType.fitChipsByGroupWidth()
-
val checkedId =
MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId)
@@ -233,8 +170,8 @@ class SingleWalletView : WalletView {
store.dispatch(
WalletAction.ExploreAddress(
state.walletAddresses.selectedAddress.exploreUrl,
- fragment!!.requireContext()
- )
+ fragment!!.requireContext(),
+ ),
)
}
} else {
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt
index 1d9ea0eac0..a3b09aab12 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt
@@ -4,16 +4,20 @@ import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.wallet.databinding.FragmentWalletBinding
-interface WalletView {
+abstract class WalletView {
+ protected var fragment: WalletFragment? = null
+ protected var binding: FragmentWalletBinding? = null
+ fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
+ this.fragment = fragment
+ this.binding = binding
+ }
- fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding)
-
- fun removeFragment()
-
- fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding)
-
- fun onViewCreated()
-
- fun onNewState(state: WalletState)
+ fun removeFragment() {
+ fragment = null
+ binding = null
+ }
+ abstract fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding)
+ abstract fun onViewCreated()
+ abstract fun onNewState(state: WalletState)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt
new file mode 100644
index 0000000000..a7f7b9aec2
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt
@@ -0,0 +1,60 @@
+package com.tangem.tap.network.exchangeServices
+
+import com.tangem.common.card.Card
+import com.tangem.domain.common.TapWorkarounds.isStart2Coin
+import com.tangem.tap.features.demo.isDemoCard
+import com.tangem.tap.features.wallet.models.Currency
+
+/**
+[REDACTED_AUTHOR]
+ */
+class CardExchangeRules(
+ val cardProvider: () -> Card?,
+) : ExchangeRules {
+
+ override fun featureIsSwitchedOn(): Boolean {
+ val card = cardProvider() ?: return false
+
+ return !card.isStart2Coin
+ }
+
+ override fun isBuyAllowed(): Boolean {
+ val card = cardProvider() ?: return false
+
+ return when {
+ card.isDemoCard() -> true
+ card.isStart2Coin -> false
+ else -> true
+ }
+ }
+
+ override fun isSellAllowed(): Boolean {
+ val card = cardProvider() ?: return false
+
+ return when {
+ card.isDemoCard() -> false
+ card.isStart2Coin -> false
+ else -> true
+ }
+ }
+
+ override fun availableForBuy(currency: Currency): Boolean {
+ val card = cardProvider() ?: return false
+
+ return when {
+ card.isDemoCard() -> true
+ card.isStart2Coin -> false
+ else -> true
+ }
+ }
+
+ override fun availableForSell(currency: Currency): Boolean {
+ val card = cardProvider() ?: return false
+
+ return when {
+ card.isDemoCard() -> false
+ card.isStart2Coin -> false
+ else -> true
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt
index 745ee01053..04c92fc542 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt
@@ -7,11 +7,12 @@ import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.extensions.Result
+import com.tangem.common.card.Card
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import java.math.BigDecimal
@@ -22,17 +23,26 @@ import java.math.BigDecimal
class CurrencyExchangeManager(
private val buyService: ExchangeService,
private val sellService: ExchangeService,
+ private val primaryRules: ExchangeRules,
) : ExchangeService, ExchangeUrlBuilder {
+ override fun featureIsSwitchedOn(): Boolean = primaryRules.featureIsSwitchedOn()
+
override suspend fun update() {
buyService.update()
sellService.update()
}
- override fun isBuyAllowed(): Boolean = buyService.isBuyAllowed()
- override fun isSellAllowed(): Boolean = sellService.isSellAllowed()
- override fun availableForBuy(currency: Currency): Boolean = buyService.availableForBuy(currency)
- override fun availableForSell(currency: Currency): Boolean = sellService.availableForSell(currency)
+ override fun isBuyAllowed(): Boolean = primaryRules.isBuyAllowed() && buyService.isBuyAllowed()
+ override fun isSellAllowed(): Boolean = primaryRules.isSellAllowed() && sellService.isSellAllowed()
+
+ override fun availableForBuy(currency: Currency): Boolean {
+ return primaryRules.availableForBuy(currency) && buyService.availableForBuy(currency)
+ }
+
+ override fun availableForSell(currency: Currency): Boolean {
+ return primaryRules.availableForSell(currency) && sellService.availableForSell(currency)
+ }
override fun getUrl(
action: Action,
@@ -66,9 +76,21 @@ class CurrencyExchangeManager(
}
enum class Action { Buy, Sell }
+
+ companion object {
+ fun dummy(): CurrencyExchangeManager = CurrencyExchangeManager(
+ buyService = ExchangeService.dummy(),
+ sellService = ExchangeService.dummy(),
+ primaryRules = ExchangeRules.dummy(),
+ )
+ }
}
-suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(walletManager: EthereumWalletManager, token: Token) {
+suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(
+ card: Card,
+ walletManager: EthereumWalletManager,
+ token: Token,
+) {
walletManager.safeUpdate()
val amountToSend = Amount(walletManager.wallet.blockchain)
@@ -85,7 +107,9 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(walletManager: Ethereu
val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress)
val signer = TangemSigner(
- tangemSdk = tangemSdk, Message()
+ card = card,
+ tangemSdk = tangemSdk,
+ initialMessage = Message(),
) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt
index e08e007029..8d6e8e2187 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt
@@ -1,16 +1,44 @@
package com.tangem.tap.network.exchangeServices
import com.tangem.blockchain.common.Blockchain
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.common.feature.Feature
+import com.tangem.tap.features.wallet.models.Currency
-interface ExchangeService {
- suspend fun update()
+interface Exchanger {
fun isBuyAllowed(): Boolean
fun isSellAllowed(): Boolean
fun availableForBuy(currency: Currency):Boolean
fun availableForSell(currency: Currency):Boolean
}
+interface ExchangeService: Feature, Exchanger {
+ suspend fun update()
+
+ companion object {
+ fun dummy(): ExchangeService = object : ExchangeService {
+ override fun featureIsSwitchedOn(): Boolean = false
+ override suspend fun update() {}
+ override fun isBuyAllowed(): Boolean = false
+ override fun isSellAllowed(): Boolean = false
+ override fun availableForBuy(currency: Currency): Boolean = false
+ override fun availableForSell(currency: Currency): Boolean = false
+ }
+ }
+}
+
+interface ExchangeRules: Feature, Exchanger {
+
+ companion object {
+ fun dummy(): ExchangeRules = object : ExchangeRules {
+ override fun featureIsSwitchedOn(): Boolean = false
+ override fun isBuyAllowed(): Boolean = false
+ override fun isSellAllowed(): Boolean = false
+ override fun availableForBuy(currency: Currency): Boolean = false
+ override fun availableForSell(currency: Currency): Boolean = false
+ }
+ }
+}
+
interface ExchangeUrlBuilder {
fun getUrl(
action: CurrencyExchangeManager.Action,
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt
index 45494e0114..87565c3640 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt
@@ -4,15 +4,6 @@ import com.squareup.moshi.Json
import retrofit2.http.GET
import retrofit2.http.Path
-/**
-[REDACTED_AUTHOR]
- */
-
-
-
-private val CurrenciesUrl = "https://api.mercuryo.io/v1.6/lib/currencies"
-
-
interface MercuryoApi {
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
index 2d446490ec..9e02287ff0 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
@@ -8,7 +8,7 @@ import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.network.common.createRetrofitInstance
import com.tangem.tap.common.redux.global.CryptoCurrencyName
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
@@ -28,6 +28,8 @@ class MercuryoService(
private val blockchainsAvailableToBuy = mutableListOf()
private val tokensAvailableToBy = mutableMapOf>()
+ override fun featureIsSwitchedOn(): Boolean = true
+
override suspend fun update() {
when (val result = performRequest { api.currencies(apiVersion) }) {
is Result.Success -> {
@@ -130,6 +132,7 @@ class MercuryoService(
private fun blockchainFromCurrencyName(currencyName: String): Blockchain? = when (currencyName) {
"BNB" -> Blockchain.BSC
"ETH" -> Blockchain.Ethereum
+ "ADA" -> Blockchain.CardanoShelley
else -> Blockchain.values().find { it.currency.lowercase() == currencyName.lowercase() }
}
}
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
index 161cfc568a..00db516c5b 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
@@ -9,7 +9,7 @@ import com.tangem.domain.common.extensions.withIOContext
import com.tangem.network.common.createRetrofitInstance
import com.tangem.tap.common.extensions.urlEncode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
-import com.tangem.tap.features.wallet.redux.Currency
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
@@ -29,6 +29,8 @@ class MoonPayService(
private var status: MoonPayStatus? = null
+ override fun featureIsSwitchedOn(): Boolean = true
+
override suspend fun update() {
withIOContext {
performRequest {
diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
index f676467852..aeab14497b 100644
--- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
+++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
@@ -25,6 +25,10 @@ class PreferencesStorage(applicationContext: Application) {
fiatCurrenciesPrefStorage.migrate()
}
+ var chatFirstLaunchTime: Long?
+ get() = preferences.getLong(CHAT_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L }
+ set(value) = preferences.edit { putLong(CHAT_FIRST_LAUNCH_KEY, value ?: 0) }
+
fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1)
@Deprecated("Use UsedCardsPrefStorage instead")
@@ -53,20 +57,12 @@ class PreferencesStorage(applicationContext: Application) {
preferences.edit { putInt(APP_LAUNCH_COUNT_KEY, ++count) }
}
- fun wasRestoreFundsWarningClosed(): Boolean {
- return preferences.getBoolean(RESTORE_FUNDS_CLOSED_KEY, false)
- }
-
- fun saveRestoreFundsWarningClosed() {
- preferences.edit { putBoolean(RESTORE_FUNDS_CLOSED_KEY, true) }
- }
-
companion object {
private const val PREFERENCES_NAME = "tapPrefs"
private const val DISCLAIMER_ACCEPTED_KEY = "disclaimerAccepted"
private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown"
private const val APP_LAUNCH_COUNT_KEY = "launchCount"
- private const val RESTORE_FUNDS_CLOSED_KEY = "restoreFundsClosed"
+ private const val CHAT_FIRST_LAUNCH_KEY = "chatFirstLaunchKey"
}
}
diff --git a/app/src/main/res/drawable/currency0.png b/app/src/main/res/drawable/currency0.png
new file mode 100644
index 0000000000..0d293ec8bb
Binary files /dev/null and b/app/src/main/res/drawable/currency0.png differ
diff --git a/app/src/main/res/drawable/currency1.png b/app/src/main/res/drawable/currency1.png
new file mode 100644
index 0000000000..37144efffe
Binary files /dev/null and b/app/src/main/res/drawable/currency1.png differ
diff --git a/app/src/main/res/drawable/currency2.png b/app/src/main/res/drawable/currency2.png
new file mode 100644
index 0000000000..bd75790221
Binary files /dev/null and b/app/src/main/res/drawable/currency2.png differ
diff --git a/app/src/main/res/drawable/currency3.png b/app/src/main/res/drawable/currency3.png
new file mode 100644
index 0000000000..c517578807
Binary files /dev/null and b/app/src/main/res/drawable/currency3.png differ
diff --git a/app/src/main/res/drawable/currency4.png b/app/src/main/res/drawable/currency4.png
new file mode 100644
index 0000000000..9a39d5651d
Binary files /dev/null and b/app/src/main/res/drawable/currency4.png differ
diff --git a/app/src/main/res/drawable/dapps0.png b/app/src/main/res/drawable/dapps0.png
new file mode 100644
index 0000000000..6152b1215b
Binary files /dev/null and b/app/src/main/res/drawable/dapps0.png differ
diff --git a/app/src/main/res/drawable/dapps1.png b/app/src/main/res/drawable/dapps1.png
new file mode 100644
index 0000000000..2d4effaf8b
Binary files /dev/null and b/app/src/main/res/drawable/dapps1.png differ
diff --git a/app/src/main/res/drawable/dapps2.png b/app/src/main/res/drawable/dapps2.png
new file mode 100644
index 0000000000..f7643c4ec7
Binary files /dev/null and b/app/src/main/res/drawable/dapps2.png differ
diff --git a/app/src/main/res/drawable/dapps3.png b/app/src/main/res/drawable/dapps3.png
new file mode 100644
index 0000000000..9e0110179b
Binary files /dev/null and b/app/src/main/res/drawable/dapps3.png differ
diff --git a/app/src/main/res/drawable/dapps4.png b/app/src/main/res/drawable/dapps4.png
new file mode 100644
index 0000000000..4eae3c0985
Binary files /dev/null and b/app/src/main/res/drawable/dapps4.png differ
diff --git a/app/src/main/res/drawable/dapps5.png b/app/src/main/res/drawable/dapps5.png
new file mode 100644
index 0000000000..0c581624c7
Binary files /dev/null and b/app/src/main/res/drawable/dapps5.png differ
diff --git a/app/src/main/res/drawable/ic_accepted.xml b/app/src/main/res/drawable/ic_accepted.xml
new file mode 100644
index 0000000000..b94c8e5cf4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_accepted.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_alert.xml b/app/src/main/res/drawable/ic_alert.xml
new file mode 100644
index 0000000000..befebf5833
--- /dev/null
+++ b/app/src/main/res/drawable/ic_alert.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml
new file mode 100644
index 0000000000..f42551a533
--- /dev/null
+++ b/app/src/main/res/drawable/ic_back.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_backup.xml b/app/src/main/res/drawable/ic_backup.xml
new file mode 100644
index 0000000000..b377ea3f2a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_backup.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_card_settings.xml b/app/src/main/res/drawable/ic_card_settings.xml
new file mode 100644
index 0000000000..c9ac2c388d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_card_settings.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_cards.xml b/app/src/main/res/drawable/ic_cards.xml
new file mode 100644
index 0000000000..9b226dff8e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cards.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_chat.xml b/app/src/main/res/drawable/ic_chat.xml
new file mode 100644
index 0000000000..465c9f741d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chat.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_chevron.xml b/app/src/main/res/drawable/ic_chevron.xml
new file mode 100644
index 0000000000..31e861dd95
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chevron.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml
index 16d6d37dd9..d276f8584c 100644
--- a/app/src/main/res/drawable/ic_close.xml
+++ b/app/src/main/res/drawable/ic_close.xml
@@ -1,10 +1,5 @@
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_comment.xml b/app/src/main/res/drawable/ic_comment.xml
new file mode 100644
index 0000000000..53e56127b2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_comment.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_currency.xml b/app/src/main/res/drawable/ic_currency.xml
new file mode 100644
index 0000000000..7508a16997
--- /dev/null
+++ b/app/src/main/res/drawable/ic_currency.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_facebook.xml b/app/src/main/res/drawable/ic_facebook.xml
new file mode 100644
index 0000000000..9343c46fe7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_facebook.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml
new file mode 100644
index 0000000000..e2c70a78fa
--- /dev/null
+++ b/app/src/main/res/drawable/ic_filter.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_github.xml b/app/src/main/res/drawable/ic_github.xml
new file mode 100644
index 0000000000..57bbd79618
--- /dev/null
+++ b/app/src/main/res/drawable/ic_github.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_gnosis_no_color.xml b/app/src/main/res/drawable/ic_gnosis_no_color.xml
new file mode 100644
index 0000000000..a8d4069093
--- /dev/null
+++ b/app/src/main/res/drawable/ic_gnosis_no_color.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_gnosis_round.xml b/app/src/main/res/drawable/ic_gnosis_round.xml
new file mode 100644
index 0000000000..8734b7177b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_gnosis_round.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_instagram.xml b/app/src/main/res/drawable/ic_instagram.xml
new file mode 100644
index 0000000000..844639d9e3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_instagram.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_linkedin.xml b/app/src/main/res/drawable/ic_linkedin.xml
new file mode 100644
index 0000000000..36600f605d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_linkedin.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml
new file mode 100644
index 0000000000..51ac9d64af
--- /dev/null
+++ b/app/src/main/res/drawable/ic_lock.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml
new file mode 100644
index 0000000000..2ea52e3dbc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_more.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_more_cards.xml b/app/src/main/res/drawable/ic_more_cards.xml
new file mode 100644
index 0000000000..0cc559021b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_more_cards.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_overlay.xml b/app/src/main/res/drawable/ic_overlay.xml
index 586397f855..5487da88f8 100644
--- a/app/src/main/res/drawable/ic_overlay.xml
+++ b/app/src/main/res/drawable/ic_overlay.xml
@@ -4,10 +4,18 @@
-
-
-
+
+
+
diff --git a/app/src/main/res/drawable/ic_plus.xml b/app/src/main/res/drawable/ic_plus.xml
new file mode 100644
index 0000000000..e8b4a25be1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_plus.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_reset.xml b/app/src/main/res/drawable/ic_reset.xml
new file mode 100644
index 0000000000..8c0845dbb0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_reset.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_reset_background.xml b/app/src/main/res/drawable/ic_reset_background.xml
new file mode 100644
index 0000000000..8ce3137945
--- /dev/null
+++ b/app/src/main/res/drawable/ic_reset_background.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_scan.xml b/app/src/main/res/drawable/ic_scan.xml
index 476424f743..1e4cd949d6 100644
--- a/app/src/main/res/drawable/ic_scan.xml
+++ b/app/src/main/res/drawable/ic_scan.xml
@@ -1,16 +1,8 @@
-
-
-
-
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_security.xml b/app/src/main/res/drawable/ic_security.xml
new file mode 100644
index 0000000000..ba3c214505
--- /dev/null
+++ b/app/src/main/res/drawable/ic_security.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml
index 41a82ede8b..46fe7fa730 100644
--- a/app/src/main/res/drawable/ic_settings.xml
+++ b/app/src/main/res/drawable/ic_settings.xml
@@ -1,10 +1,5 @@
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_tangem.xml b/app/src/main/res/drawable/ic_tangem.xml
new file mode 100644
index 0000000000..5140d0444c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_tangem.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_telegram.xml b/app/src/main/res/drawable/ic_telegram.xml
new file mode 100644
index 0000000000..5d2351d73e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_telegram.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_text.xml b/app/src/main/res/drawable/ic_text.xml
new file mode 100644
index 0000000000..3e1f8a08a9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_text.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_trash.xml b/app/src/main/res/drawable/ic_trash.xml
new file mode 100644
index 0000000000..38e732373b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_trash.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_twitter.xml b/app/src/main/res/drawable/ic_twitter.xml
new file mode 100644
index 0000000000..de09724525
--- /dev/null
+++ b/app/src/main/res/drawable/ic_twitter.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_unticked.xml b/app/src/main/res/drawable/ic_unticked.xml
new file mode 100644
index 0000000000..8eb2fe34df
--- /dev/null
+++ b/app/src/main/res/drawable/ic_unticked.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_walletconnect.xml b/app/src/main/res/drawable/ic_walletconnect.xml
new file mode 100644
index 0000000000..925a858a8a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_walletconnect.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_youtube.xml b/app/src/main/res/drawable/ic_youtube.xml
new file mode 100644
index 0000000000..81eb8d7e8c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_youtube.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/img_warning_triangle_24.xml b/app/src/main/res/drawable/img_warning_triangle_24.xml
new file mode 100644
index 0000000000..c54d7d8f8b
--- /dev/null
+++ b/app/src/main/res/drawable/img_warning_triangle_24.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_badge_custom_currency.xml b/app/src/main/res/drawable/shape_badge_custom_currency.xml
index 2c3b7f7bd4..cfd740103d 100644
--- a/app/src/main/res/drawable/shape_badge_custom_currency.xml
+++ b/app/src/main/res/drawable/shape_badge_custom_currency.xml
@@ -7,7 +7,7 @@
android:height="12dp" />
diff --git a/app/src/main/res/drawable/shape_rectangle_rounded_8.xml b/app/src/main/res/drawable/shape_rectangle_rounded_8.xml
index a788f39093..4f20cb0e64 100644
--- a/app/src/main/res/drawable/shape_rectangle_rounded_8.xml
+++ b/app/src/main/res/drawable/shape_rectangle_rounded_8.xml
@@ -5,4 +5,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/font/roboto.ttf b/app/src/main/res/font/roboto.ttf
new file mode 100644
index 0000000000..ddf4bfacb3
Binary files /dev/null and b/app/src/main/res/font/roboto.ttf differ
diff --git a/app/src/main/res/font/roboto_medium.ttf b/app/src/main/res/font/roboto_medium.ttf
new file mode 100644
index 0000000000..ac0f908b9c
Binary files /dev/null and b/app/src/main/res/font/roboto_medium.ttf differ
diff --git a/app/src/main/res/layout/card_total_balance.xml b/app/src/main/res/layout/card_total_balance.xml
deleted file mode 100644
index 1f97649e18..0000000000
--- a/app/src/main/res/layout/card_total_balance.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml
deleted file mode 100644
index 58ac038846..0000000000
--- a/app/src/main/res/layout/fragment_details.xml
+++ /dev/null
@@ -1,309 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_details_security.xml b/app/src/main/res/layout/fragment_details_security.xml
deleted file mode 100644
index 25d17cf8cc..0000000000
--- a/app/src/main/res/layout/fragment_details_security.xml
+++ /dev/null
@@ -1,229 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
deleted file mode 100644
index 7a95116cd8..0000000000
--- a/app/src/main/res/layout/fragment_home.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_onboarding_main.xml b/app/src/main/res/layout/fragment_onboarding_main.xml
index 82b94a5def..faa2c0c829 100644
--- a/app/src/main/res/layout/fragment_onboarding_main.xml
+++ b/app/src/main/res/layout/fragment_onboarding_main.xml
@@ -6,6 +6,7 @@
android:layout_height="match_parent"
android:background="@color/backgroundWhite"
android:clipChildren="false"
+ android:clipToPadding="false"
android:fitsSystemWindows="true"
android:orientation="vertical">
diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml
index faf0d33f9f..d5daad21ea 100644
--- a/app/src/main/res/layout/fragment_wallet.xml
+++ b/app/src/main/res/layout/fragment_wallet.xml
@@ -18,14 +18,14 @@
app:liftOnScroll="true">
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:menu="@menu/wallet"
+ app:navigationIcon="@drawable/ic_wallet_24"
+ app:navigationIconTint="@color/darkGray6"
+ app:title="@string/wallet_title"
+ app:titleCentered="true" />
@@ -43,43 +43,44 @@
android:overScrollMode="never">
+ android:id="@+id/cl_wallet"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:paddingBottom="92dp">
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@drawable/card_placeholder_black" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/l_wallet_backup_warning"
+ app:layout_goneMarginTop="16dp" />
+ app:layout_constraintTop_toBottomOf="@id/l_card_total_balance"
+ app:layout_goneMarginTop="12dp" />
+
-
-
-
+ app:cornerRadius="8dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/rv_multiwallet"
+ app:layout_constraintVertical_bias="0.0"
+ tools:layout_editor_absoluteX="16dp" />
-
-
-
+
diff --git a/app/src/main/res/layout/item_currency_wallet.xml b/app/src/main/res/layout/item_currency_wallet.xml
index de692cfa83..0c6ef1fa3f 100644
--- a/app/src/main/res/layout/item_currency_wallet.xml
+++ b/app/src/main/res/layout/item_currency_wallet.xml
@@ -6,75 +6,52 @@
android:layout_height="wrap_content">
+ android:id="@+id/card_wallet"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginBottom="8dp"
+ android:background="@android:color/white"
+ android:elevation="3dp">
+
+
-
-
-
-
-
-
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+ layout="@layout/item_currency_wallet_content"
+ android:id="@+id/l_content"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginStart="8dp"
+ android:visibility="gone"
+ app:layout_constraintStart_toEndOf="@id/iv_currency"
+ app:layout_constraintEnd_toEndOf="parent"
+ tools:visibility="visible" />
+ layout="@layout/item_currency_wallet_shimmer"
+ android:id="@+id/l_shimmer"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginStart="8dp"
+ app:layout_constraintStart_toEndOf="@id/iv_currency"
+ app:layout_constraintEnd_toEndOf="parent" />
diff --git a/app/src/main/res/layout/layout_balance_wallet_details.xml b/app/src/main/res/layout/layout_balance_wallet_details.xml
index 781b8f5145..0ae3a9e463 100644
--- a/app/src/main/res/layout/layout_balance_wallet_details.xml
+++ b/app/src/main/res/layout/layout_balance_wallet_details.xml
@@ -39,28 +39,13 @@
app:layout_constraintTop_toBottomOf="@id/tv_fiat_amount"
tools:text="0.43 BTC" />
-
-
-
+ android:layout_margin="16dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/layout_card_total_balance.xml b/app/src/main/res/layout/layout_card_total_balance.xml
new file mode 100644
index 0000000000..fe3484f373
--- /dev/null
+++ b/app/src/main/res/layout/layout_card_total_balance.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_single_wallet_balance.xml b/app/src/main/res/layout/layout_single_wallet_balance.xml
new file mode 100644
index 0000000000..119baed8cf
--- /dev/null
+++ b/app/src/main/res/layout/layout_single_wallet_balance.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_wallet_backup_warning.xml b/app/src/main/res/layout/layout_wallet_backup_warning.xml
new file mode 100644
index 0000000000..119ab12734
--- /dev/null
+++ b/app/src/main/res/layout/layout_wallet_backup_warning.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_wallet_long_buttons.xml b/app/src/main/res/layout/layout_wallet_long_buttons.xml
index f670b7fd5d..5fc11bef41 100644
--- a/app/src/main/res/layout/layout_wallet_long_buttons.xml
+++ b/app/src/main/res/layout/layout_wallet_long_buttons.xml
@@ -17,11 +17,8 @@
android:id="@+id/btn_confirm_long"
style="@style/TapButtonWithIcon"
android:layout_width="0dp"
- android:layout_marginTop="30dp"
- android:layout_marginEnd="16dp"
- android:layout_marginBottom="8dp"
android:text="@string/wallet_button_send"
- app:icon="@drawable/ic_send"
+ app:icon="@drawable/ic_send"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/guideline"
diff --git a/app/src/main/res/layout/layout_wallet_short_buttons.xml b/app/src/main/res/layout/layout_wallet_short_buttons.xml
index ed60601395..e235511583 100644
--- a/app/src/main/res/layout/layout_wallet_short_buttons.xml
+++ b/app/src/main/res/layout/layout_wallet_short_buttons.xml
@@ -6,38 +6,32 @@
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"
+ app:iconGravity="textEnd"
+ android:textAlignment="center"
+ app:layout_constraintVertical_bias="1" />
+ app:layout_constraintStart_toEndOf="@id/btn_trade"
+ app:layout_constraintVertical_bias="1" />
diff --git a/app/src/main/res/layout/view_currency_icon.xml b/app/src/main/res/layout/view_currency_icon.xml
new file mode 100644
index 0000000000..801ac76c97
--- /dev/null
+++ b/app/src/main/res/layout/view_currency_icon.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/view_wallet_details_buttons_row.xml b/app/src/main/res/layout/view_wallet_details_buttons_row.xml
index bb5bd190b0..0a6a51bfdd 100644
--- a/app/src/main/res/layout/view_wallet_details_buttons_row.xml
+++ b/app/src/main/res/layout/view_wallet_details_buttons_row.xml
@@ -1,50 +1,56 @@
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ tools:orientation="horizontal"
+ tools:parentTag="android.widget.LinearLayout">
-
+
+
-
-
+
+
+
+ android:id="@+id/btn_send"
+ style="@style/TapButtonWithIcon"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:text="@string/wallet_button_send"
+ app:icon="@drawable/ic_send" />
diff --git a/app/src/main/res/menu/wallet_details.xml b/app/src/main/res/menu/wallet_details.xml
index 08ba8df389..d49dd52b30 100644
--- a/app/src/main/res/menu/wallet_details.xml
+++ b/app/src/main/res/menu/wallet_details.xml
@@ -3,6 +3,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 3e7c059c3a..cf8a46c64c 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -6,6 +6,7 @@
Erledigt
Abbrechen
Akzeptieren
+ Entfernen
Der Kamerazugriff wurde verweigert
Sie haben keinen Zugang zur Kamera erteilt, bitte passen Sie Ihre Datenschutzeinstellungen an
@@ -34,10 +35,10 @@
Diese Karte wird nicht unterstützt
Ihre Tangem-Karte wurde für die Arbeit mit einer anderen App ausgestellt. Bitte sehen Sie sich den Namen und die Anleitung auf Ihrer Karte an und installieren Sie eine korrekte App
Die Transaktion läuft…
- Senden\u0020
- an %s
- Erhalten\u0020
- von %s
+ Senden
+ an %s
+ Erhalten
+ von %s
Adresse erkunden
PayString erstellen
%s Wallet
@@ -58,11 +59,6 @@
Gebühr
Gesamt
Höchstbetrag
- Ungültige Adresse
- Ungültiger Betrag
- Der Betrag geht über die Bilanz hinaus
- Der Gesamtbetrag geht über die Bilanz hinaus
- Die Gebühr geht über die Bilanz hinaus
Niedrig
Normal
Priorität
@@ -72,7 +68,6 @@
%s %s und %s %s werden gesendet
Bilanz: %s %s
Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert
- Um ein Konto zu erstellen, senden Sie 1+ XLM an diese Adresse
Details
KartenID
Emittent
@@ -164,21 +159,12 @@ RECHTLICHER HAFTUNGSAUSSCHLUSS
Nutzungsbedingungen
Keine Internetverbindung
- Das Zielkonto ist nicht erstellt. Der abzusendende Betrag soll %1$s %2$s + Gebühr oder mehr sein
- Erhalt der Gebühr fehlgeschlagen
-
- Unbekannter Fehler
Verifikation der PayString ist fehlgeschlagen
PayString wird von der Blockchain nicht unterstützt
PayString ist nicht registriert
PayString-Anfrage ist fehlgeschlagen
Die Adresse stimmt mit der Adresse Ihrer Brieftasche überein
- Nicht genug Geld
- Interner Fehler der Blockchain
- Falsche Gebühr
- Minimaler Betrag ist %s
- Restbestand zu klein
Die Adresse wurde erfolgreich kopiert
@@ -191,8 +177,4 @@ RECHTLICHER HAFTUNGSAUSSCHLUSS
Ihr Kontostand in dieser Brieftasche ist nicht Null, oder Sie haben unbestätigte Transaktionen. Es ist unmöglich die Funktion der Brieftasche zu löschen.
Dies ist die derzeit aktive Wahl
- Damit Sie beim nächsten Aufladen Ihrer Brieftasche keine erhöhte Provision zahlen, soll der Betrag um %s XTZ reduziert werden
- Um %s XTZ reduzieren
- Nein, alles senden
-
diff --git a/app/src/main/res/values-de/strings_blockchain.xml b/app/src/main/res/values-de/strings_blockchain.xml
new file mode 100644
index 0000000000..2f52667c19
--- /dev/null
+++ b/app/src/main/res/values-de/strings_blockchain.xml
@@ -0,0 +1,20 @@
+
+
+ Nicht genug Geld
+ Interner Fehler der Blockchain
+ Falsche Gebühr
+ Minimaler Betrag ist %s
+ Restbestand zu klein
+ Das Zielkonto ist nicht erstellt. Der abzusendende Betrag soll %s %s + Gebühr oder mehr sein
+ Um ein Konto zu erstellen, senden Sie 1+ XLM an diese Adresse
+ Erhalt der Gebühr fehlgeschlagen
+ Unbekannter Fehler
+ Damit Sie beim nächsten Aufladen Ihrer Brieftasche keine erhöhte Provision zahlen, soll der Betrag um %s XTZ reduziert werden
+ Um %s XTZ reduzieren
+ Nein, alles senden
+ Ungültige Adresse
+ Ungültiger Betrag
+ Der Betrag geht über die Bilanz hinaus
+ Der Gesamtbetrag geht über die Bilanz hinaus
+ Die Gebühr geht über die Bilanz hinaus
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml
index de09eb54fd..96c0876eff 100644
--- a/app/src/main/res/values-de/strings_final.xml
+++ b/app/src/main/res/values-de/strings_final.xml
@@ -23,7 +23,9 @@
Revolutionary Hardware Wallet
Store your crypto assets secure while keeping private keys contained in your card
Ultra Secure Backup
- Up to 3 physical cards to one wallet
+ Up to
+ 3 physical cards
+ to one wallet
Thousands of Currencies
A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card
DeFi Compatible
@@ -45,5 +47,52 @@
If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds
Custom
+ Notice
+ Attention
+ The server is not available, please try again later
+ Hide token
+ Hide %s
+ Hide
+ You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.
+ Unable to hide %s
+ The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.
+
+ %s network not found. Please, add it first and try again.
+ This network is not supported. Please select another network.
+ %s network
+
+ Card Settings
+ Security Mode
+ Selected application protection method
+ Change access code
+ Access code will be changed on this card only
+ Get your card ready!
+ Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet.
+ Scan Card
+ App settings
+ Enable biometric authentication
+ Go to settings to enable biometric authentication in the Tangem app
+ Keep the wallet in the app
+ Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card.
+ Save Access Code
+ Biometric authentication will be requested instead of the access code for interactions with your card.
+ Removing the saved card deletes all the saved wallets and their access codes.
+ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code.
+ WalletConnect
+ Connect to Dapps
+
+ Link More Cards
+ You can synchronize up to three cards into one wallet. It can only be done once.
+
+ Privacy policy
+
+ Reset to factory settings
+ This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
+ I understand that after performing this action, I will no longer have access to the current wallet
+ Reset the card
+ Reset to Factory Settings
+ Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
+
+ Select network
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 069163f054..d39650f3b7 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -1,112 +1,107 @@
- Sauvegarder les modifications
- Alerte
- Réessayer
- Exécuté
- Annuler
+ Sauvegarder les modifications
+ Alerte
+ Réessayer
+ Exécuté
+ Annuler
J\'accepte
+ Supprimer
- L\'accès à la caméra est refusé
- Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité
- Paramètres
+ L\'accès à la caméra est refusé
+ Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité
+ Paramètres
Bienvenue à Tangem. Avez-vous une de nos cartes
- Encore une fois, bienvenue dans Tangem. \nScannez votre carte pour commencer.
- Oui! Scannez-la.
- Magasin
+ Encore une fois, bienvenue dans Tangem. \nScannez votre carte pour commencer.
+ Oui! Scannez-la.
+ Magasin
Préparez-vous à appuyer votre carte au dos de votre téléphone.
- Appuyez
- Scannez la carte
- Scanner
- Envoyer
- Créer un portefeuille
+ Appuyez
+ Scannez la carte
+ Scanner
+ Envoyer
+ Créer un portefeuille
Tangem
- Choisir une variante de portefeuille
- Jeton d\'authentification
- Jetons d\'authentification
- Solde confirmé
- Solde est en cours de téléchargement …
- Blockchain est inaccessible
- Compte n\'est pas créé
- Téléchargez %s +%s pour créer un compte
- Carte vide
- Créez un portefeuille pour commencer à utiliser votre carte Tangem
- Cette carte n\'est pas prise en charge
- Votre carte Tangem a été créée pour fonctionner avec une autre application. Regardez le nom et les instructions sur votre carte et installez l\'application pertinente
+ Choisir une variante de portefeuille
+ Jeton d\'authentification
+ Jetons d\'authentification
+ Solde confirmé
+ Solde est en cours de téléchargement …
+ Blockchain est inaccessible
+ Compte n\'est pas créé
+ Téléchargez %s +%s pour créer un compte
+ Carte vide
+ Créez un portefeuille pour commencer à utiliser votre carte Tangem
+ Cette carte n\'est pas prise en charge
+ Votre carte Tangem a été créée pour fonctionner avec une autre application. Regardez le nom et les instructions sur votre carte et installez l\'application pertinente
Transaction en cours…
- Envoi\u0020
- jusqu\'à %s
- Réception \u0020
- de %s
- Explorer l\'adresse
+ Envoi
+ jusqu\'à %s
+ Réception
+ de %s
+ Explorer l\'adresse
Créer PayString
Portefeuille %s
Créer PayString
Carte: %s
Dénomination PayString
$payid.tangem.com
- Créer
+ Créer
Votre PayString est une information qui vous est propre, comme un numéro de téléphone, une adresse du courrier électronique ou un ABN.
- PayString a été créé avec succès et copié dans le presse-papiers
- Mauvaise réponse lors de la création de PayString
+ PayString a été créé avec succès et copié dans le presse-papiers
+ Mauvaise réponse lors de la création de PayString
Ce PayString existe déjà. Essayez une autre variante.
Adresse ou PayString
Adresse
- Envoyer
- Commissions du réseau
- Somme
- Commissions
- Total
- Somme maximale
- Adresse incorrecte
- Somme incorrecte
- Le montant dépasse le solde
- Le total dépasse le solde
- Les commissions dépassent le solde
- Bas
- Normal
- Priorité
- Inclure les commissions
+ Envoyer
+ Commissions du réseau
+ Somme
+ Commissions
+ Total
+ Somme maximale
+ Bas
+ Normal
+ Priorité
+ Inclure les commissions
Sera envoyé %s %s
≈ %s (incl. les commissions : %s)
Sera envoyé %s %s et %s %s
Solde : %s %s
- La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps
- Pour créer un compte, envoyez 1+ XLM à cette adresse
- Référénces
- ID de la carte
- Emetteur
- Signé
- %s hashes
- Monnaie de l\'application
- Paramètres
- Carte
- Valider la carte
- Gestion de la sécurité
- Annuler le portefeuille
+ La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps
+ Référénces
+ ID de la carte
+ Emetteur
+ Signé
+ %s hashes
+ Monnaie de l\'application
+ Paramètres
+ Carte
+ Valider la carte
+ Gestion de la sécurité
+ Annuler le portefeuille
Cette action est irréversible. Si, après avoir annulé le portefeuille, quelqu\'un lui envoie des fonds, vous ne pourrez pas les retirer.
- Si vous oubliez le code, vous perdrez la possibilité d\'utiliser la carte. Si vous perdez le code, il sera impossible de le restaurer ou modifier.
- Gestion de la sécurité
+ Si vous oubliez le code, vous perdrez la possibilité d\'utiliser la carte. Si vous perdez le code, il sera impossible de le restaurer ou modifier.
+ Gestion de la sécurité
Tenez la carte fermement
Ce mécanisme protège contre les attaques sans contact sur la carte. Il y a un délai entre la réception et l\'exécution de la commande. Après la première transaction signée, ce téléphone sera associé à la carte et les transactions seront signées immédiatement.
- Mot de passe
+ Mot de passe
Avant d\'exécuter une commande qui modifie l\'état de la carte, vous devrez entrer un mot de passe.
- Code d\'accès
- Vous devrez entrer le mot de passe correct avant de scanner la carte
- Vous pouvez rencontrer des problèmes NFC avec certains iPhone 7/7 + lors de l\'extraction
+ Code d\'accès
+ Vous devrez entrer le mot de passe correct avant de scanner la carte
+ Vous pouvez rencontrer des problèmes NFC avec certains iPhone 7/7 + lors de l\'extraction
Cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable.
Attention
Cette carte n\'est pas conçue pour fonctionner avec Tangem
- La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement
- Les cartes Tangem émises avant septembre 2019 ne peuvent actuellement pas être extraites à l\'aide de l\'iPhone. Nous travaillons activement avec Apple pour rendre cela possible dans les futures versions d\'iOS.
- Touchez pour signer
- Appuyez votre carte au téléphone comme indiqué ci-dessus
- Touchez, pour annuler un portefeuille
- Touchez, pour créer un portefeuille
- Touchez, pour modifier le code d\'accès
- Touchez, pour modifier le mot de passe
- PayString incorrect
+ La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement
+ Les cartes Tangem émises avant septembre 2019 ne peuvent actuellement pas être extraites à l\'aide de l\'iPhone. Nous travaillons activement avec Apple pour rendre cela possible dans les futures versions d\'iOS.
+ Touchez pour signer
+ Appuyez votre carte au téléphone comme indiqué ci-dessus
+ Touchez, pour annuler un portefeuille
+ Touchez, pour créer un portefeuille
+ Touchez, pour modifier le code d\'accès
+ Touchez, pour modifier le mot de passe
+ PayString incorrect
@@ -159,23 +154,14 @@ Avertissement
\n\nCette avertissement a été modifié, pour la dernière fois, le 1 octobre 2020.
- Conditions d\'utilisation
+ Conditions d\'utilisation
Pas de connexion internet
- Le compte cible n\'a pas été créé. Le montant à envoyer doit être de %1$s %2$s + commissions ou plus
- Échec de réception des commissions
-
- Erreur inconnue
La vérification de PayString a échoué
PayString non pris en charge par la blockchain
PayString non enregistré
La demande de PayString a échoué
L\'adresse est la même que celle de votre portefeuille
- Solde insuffisant
- Erreur interne de la blockchain
- Commission non valide
- Le montant minimal est de %ы
- Le reste est trop petit
L\'adresse a été copiée avec succès
Saisissez d\'abord le PayString requis
Posez pour scanner
@@ -183,8 +169,5 @@ Avertissement
Votre solde sur ce portefeuille n\'est pas nul, ou vous avez des transactions non confirmées. Impossible de supprimer la fonction du portefeuille
Cette option est déjà active
- Pour ne pas payer une commission élevée la prochaine fois que vous rechargez votre portefeuille, veuillez réduire le montant de %s XTZ
- Réduire de% s XTZ
- Non, envoyer toute la somme
diff --git a/app/src/main/res/values-fr/strings_blockchain.xml b/app/src/main/res/values-fr/strings_blockchain.xml
new file mode 100644
index 0000000000..f6e2cdef63
--- /dev/null
+++ b/app/src/main/res/values-fr/strings_blockchain.xml
@@ -0,0 +1,20 @@
+
+
+ Solde insuffisant
+ Erreur interne de la blockchain
+ Commission non valide
+ Le montant minimal est de %s
+ Le reste est trop petit
+ Le compte cible n\'a pas été créé. Le montant à envoyer doit être de %s %s + commissions ou plus
+ Pour créer un compte, envoyez 1+ XLM à cette adresse
+ Échec de réception des commissions
+ Erreur inconnue
+ Adresse incorrecte
+ Somme incorrecte
+ Le montant dépasse le solde
+ Le total dépasse le solde
+ Les commissions dépassent le solde
+ Pour ne pas payer une commission élevée la prochaine fois que vous rechargez votre portefeuille, veuillez réduire le montant de %s XTZ
+ Réduire de %s XTZ
+ Non, envoyer toute la somme
+
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml
index de09eb54fd..3fe6eb9f17 100644
--- a/app/src/main/res/values-fr/strings_final.xml
+++ b/app/src/main/res/values-fr/strings_final.xml
@@ -23,7 +23,9 @@
Revolutionary Hardware Wallet
Store your crypto assets secure while keeping private keys contained in your card
Ultra Secure Backup
- Up to 3 physical cards to one wallet
+ Up to
+ 3 physical cards
+ to one wallet
Thousands of Currencies
A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card
DeFi Compatible
@@ -45,5 +47,52 @@
If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds
Custom
+ The server is not available, please try again later
+ Notice
+ Attention
+ Hide token
+ Hide %s
+ Hide
+ You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.
+ Unable to hide %s
+ The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.
+
+ %s network not found. Please, add it first and try again.
+ This network is not supported. Please select another network.
+ %s network
+
+ Card Settings
+ Security Mode
+ Selected application protection method
+ Change access code
+ Access code will be changed on this card only
+ Get your card ready!
+ Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet.
+ Scan Card
+ App settings
+ Enable biometric authentication
+ Go to settings to enable biometric authentication in the Tangem app
+ Keep the wallet in the app
+ Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card.
+ Save Access Code
+ Biometric authentication will be requested instead of the access code for interactions with your card.
+ Removing the saved card deletes all the saved wallets and their access codes.
+ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code.
+ WalletConnect
+ Connect to Dapps
+
+ Link More Cards
+ You can synchronize up to three cards into one wallet. It can only be done once.
+
+ Privacy policy
+
+ Reset to factory settings
+ This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
+ I understand that after performing this action, I will no longer have access to the current wallet
+ Reset the card
+ Reset to Factory Settings
+ Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
+
+ Select network
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 7ad1cddc70..7eba948ed7 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -6,6 +6,7 @@
Riprova
Fatto
Annulla
+ Rimuovere
Accesso alla videocamera negato
Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy
Impostazioni
@@ -33,10 +34,10 @@
Questa carta non è supportata
La tua carta Tangem è stata creata per funzionare con un\'altra applicazione. Leggere il nome e le istruzioni sulla carta e installare l\'applicazione corretta
Transazione in corso…
- Invia\u0020
- su %s
- Ricevi\u0020
- da %s
+ Invia
+ su %s
+ Ricevi
+ da %s
Cerca indirizzo
Crea PayString
portafoglio %s
@@ -57,10 +58,7 @@
Commissione
Totale
Importo totale
- Indirizzo non valido
- Importo non valido
- Il totale supera il saldo
- La commissione supera il saldo
+
Insufficiente
Normale
Prioritario
@@ -70,7 +68,6 @@
Sarà inviato %1$s %2$s e %1$s %2$s
Saldo: %1$s %2$s
La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo
- Per creare un account, invia 1+ XLM a questo indirizzo
Requisiti
ID carta
Emittente
@@ -158,26 +155,16 @@ Salvo quanto diversamente previsto dalla legge, i proprietari o i contributori d
Questa nota legale è stata modificata l\'ultima volta 01.10.2020.
-
- Per creare un account, invia %1$s %2$s a questo indirizzo
- Impossibile ottenere la commissione
Accetta
- L\'importo supera il saldo
Termini del servizio
Nessuna connessione a Internet
- Errore sconosciuto
Verifica del PayString fallita
PayString non supportato dalla blockchain
PayString non registrato
Richiesta PayString fallita
L\'indirizzo corrisponde all\'indirizzo del tuo portafoglio
- Saldo insufficiente
- Errore interno della blockchain
- Commissione non valida
- L\'importo minimo è di %s
- L\'importo residuo è molto basso
L\'indirizzo è stato copiato con successo
@@ -190,8 +177,4 @@ Questa nota legale è stata modificata l\'ultima volta 01.10.2020.
Il tuo saldo su questo portafoglio non è pari a zero o hai transazioni non confermate. Non è possibile eliminare la funzione portafoglio.
E\' l\'opzione attualmente attiva
- Per evitare di pagare una commissione maggiore la prossima volta che ricarichi il tuo portafoglio, riduci l\'importo di %s XTZ
- Riduci di %s XTZ
- No, invia l\'intero importo
-
diff --git a/app/src/main/res/values-it/strings_blockchain.xml b/app/src/main/res/values-it/strings_blockchain.xml
new file mode 100644
index 0000000000..cd0f690a23
--- /dev/null
+++ b/app/src/main/res/values-it/strings_blockchain.xml
@@ -0,0 +1,20 @@
+
+
+ Saldo insufficiente
+ Errore interno della blockchain
+ Commissione non valida
+ L\'importo minimo è di %s
+ L\'importo residuo è molto basso
+ Errore sconosciuto
+ L\'importo supera il saldo
+ Per creare un account, invia %s %s a questo indirizzo
+ Impossibile ottenere la commissione
+ Per creare un account, invia 1+ XLM a questo indirizzo
+ Indirizzo non valido
+ Importo non valido
+ Il totale supera il saldo
+ La commissione supera il saldo
+ Per evitare di pagare una commissione maggiore la prossima volta che ricarichi il tuo portafoglio, riduci l\'importo di %s XTZ
+ Riduci di %s XTZ
+ No, invia l\'intero importo
+
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml
index de09eb54fd..afe988cb58 100644
--- a/app/src/main/res/values-it/strings_final.xml
+++ b/app/src/main/res/values-it/strings_final.xml
@@ -23,7 +23,9 @@
Revolutionary Hardware Wallet
Store your crypto assets secure while keeping private keys contained in your card
Ultra Secure Backup
- Up to 3 physical cards to one wallet
+ Up to
+ 3 physical cards
+ to one wallet
Thousands of Currencies
A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card
DeFi Compatible
@@ -45,5 +47,53 @@
If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds
Custom
+ The server is not available, please try again later
+ Notice
+ Attention
+ Hide token
+ Hide %s
+ Hide
+ You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.
+ Unable to hide %s
+ The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.
+
+ %s network not found. Please, add it first and try again.
+ This network is not supported. Please select another network.
+
+ %s network
+
+ Card Settings
+ Security Mode
+ Selected application protection method
+ Change access code
+ Access code will be changed on this card only
+ Get your card ready!
+ Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet.
+ Scan Card
+ App settings
+ Enable biometric authentication
+ Go to settings to enable biometric authentication in the Tangem app
+ Keep the wallet in the app
+ Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card.
+ Save Access Code
+ Biometric authentication will be requested instead of the access code for interactions with your card.
+ Removing the saved card deletes all the saved wallets and their access codes.
+ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code.
+ WalletConnect
+ Connect to Dapps
+
+ Link More Cards
+ You can synchronize up to three cards into one wallet. It can only be done once.
+
+ Privacy policy
+
+ Reset to factory settings
+ This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
+ I understand that after performing this action, I will no longer have access to the current wallet
+ Reset the card
+ Reset to Factory Settings
+ Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
+
+ Select network
\ No newline at end of file
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000000..9be291c700
--- /dev/null
+++ b/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 3df2976282..d36add64ec 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -7,6 +7,7 @@
Готово
Отмена
Принять
+ Удалить
%s
Доступ к камере запрещен
Вы не предоставили доступ к своей камере, измените настройки конфиденциальности
@@ -37,9 +38,9 @@
Транзакция в процессе…
до %s
от %s
- Получение\u0020
- Отправка\u0020
- Посмотреть адрес
+ Получение
+ Отправка
+ Проверить адрес
Создать PayString
%s кошелек
Создать PayString
@@ -59,11 +60,6 @@
Комиссия
Всего
Максимальная сумма
- Неверный адрес
- Недопустимая сумма
- Сумма превышает баланс
- Общая сумма превышает баланс
- Комиссия превышает баланс
Низкий
Нормальный
Приоритетный
@@ -107,30 +103,19 @@
Приложите, чтобы изменить пароль
Неверный PayString
Условия использования
- Не удалось получить комиссию
- Целевая учетная запись не создана. Сумма для отправки должна быть %1$s %2$s + плата за создание или больше
Нет соединения с интернетом
- Неизвестная ошибка
Ошибка проверки PayString
PayString не поддерживается блокчейном
PayString не зарегистрирован
Не удалось выполнить запрос PayString
Адрес совпадает с адресом кошелька
- Недостаточный баланс
- Внутренняя ошибка блокчейна
- Неверная комиссия
- Минимальная сумма: %s
- Сдача слишком мала
- Для создания учетной записи отправьте 1+ XLM на этот адрес
+
Адрес скопирован в буфер обмена
Сначала введите желаемую PayString
Приложите, чтобы отсканировать
Приложите карту
Ваш баланс на этом кошельке не равен нулю, или у Вас есть неподтвержденные транзакции
Это текущая активная опция
- Чтобы не платить повышенную комиссию при следующем пополнении кошелька, уменьшите сумму на %s XTZ
- Уменьшить на %s XTZ
- Нет, отправить все
Ошибка сканирования карты. Попробуй снова.
Купить
Продать
@@ -175,7 +160,7 @@
Не удалось отправить электронное письмо
Причина: %s
Не могу отправить транзакцию
- Причина: %s. Хотите отправить отзыв?
+ Причина: %s
У Вас возникли трудности со сканированием вашей карты?
Пожалуйста, попробуйте приложить карту точно так, как показано на анимации, или обратитесь в поддержку
Отмена
@@ -197,7 +182,6 @@
Добавить пользовательский
Популярные токены
Добавлен
- Удалить токен
Значок токена
Добавить токен
Адрес контракта
@@ -324,7 +308,6 @@
Активный. Количество резервных карт: %d
Связано. Количество резервных карт: %d
Нет резервной копии
- Создать резервную копию
У Вас есть прерванное резервное копирование. Хотите возобновить?
Да, возобновить
Отказаться
@@ -340,7 +323,6 @@
Подготовьте резервную карту с номером %s
Подготовьте основную карту с номером %s
Сброс до заводских настроек
- Это действие необратимо. Если после сброса карты кто-то отправит на нее средства, то Вы не сможете их вывести.
Код доступа доступен только для карт с резервной копией
Сеть Solana взымает арендную плату в размере %s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %s, чтобы использовать его бесплатно.
%1s (%2s)
diff --git a/app/src/main/res/values-ru/strings_blockchain.xml b/app/src/main/res/values-ru/strings_blockchain.xml
new file mode 100644
index 0000000000..98b7ad7194
--- /dev/null
+++ b/app/src/main/res/values-ru/strings_blockchain.xml
@@ -0,0 +1,20 @@
+
+
+ Недостаточный баланс
+ Внутренняя ошибка блокчейна
+ Неверная комиссия
+ Минимальная сумма: %s
+ Сдача слишком мала
+ Для создания учетной записи отправьте 1+ XLM на этот адрес
+ Не удалось получить комиссию
+ Целевая учетная запись не создана. Сумма для отправки должна быть %1$s %2$s + плата за создание или больше
+ Неизвестная ошибка
+ Неверный адрес
+ Недопустимая сумма
+ Сумма превышает баланс
+ Общая сумма превышает баланс
+ Комиссия превышает баланс
+ Чтобы не платить повышенную комиссию при следующем пополнении кошелька, уменьшите сумму на %s XTZ
+ Уменьшить на %s XTZ
+ Нет, отправить все
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml
index cd706ff8af..359b5c0488 100644
--- a/app/src/main/res/values-ru/strings_final.xml
+++ b/app/src/main/res/values-ru/strings_final.xml
@@ -23,7 +23,9 @@
Революционный аппаратный кошелек
Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.
Все ключи в безопасности
- До трех карт с одним кошельком
+ До
+ трех карт
+ с одним кошельком
Тысячи криптовалют
Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте
Поддержка DeFi
@@ -45,6 +47,9 @@
Если вы совершили ошибку с выбором сети при переводе средств с биржи, эта инструкция поможет вам восстановить средства
Пользовательский
+ Уведомление
+ Внимание
+ Сервер недоступен, повторите попытку позднее
Баланс
В сумме учтены не все монеты
@@ -54,11 +59,14 @@
Скрыть токен
Скрыть %s
Скрыть
- Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно.
+ Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами.
Невозможно скрыть %s
- Токен %s является основной валютой в сети %s и не может быть скрыт, до тех пор пока у вас в списке есть другие токены этой сети.
+
+ Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети.
Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново.
+ Сеть не поддерживается. Пожалуйста, выберите другую сеть.
+
Сеть %s
Бэкап кошелька не был произведен
@@ -67,4 +75,41 @@
Карты банков РФ в данный момент не принимаются
У вас есть карта банка другой страны или платежной системы UnionPay?
Да
+
+ Чат
+ Tangem Bot
+
+ Настройки карты
+ Тип безопасности
+ Выбранный способ защиты приложения
+ Смена кода доступа
+ Код доступа будет изменен только на данной карте
+ Приготовьте свою карту
+ Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку.
+ Сканировать
+ Настройки приложения
+ Включите биометрическую аутентификацию
+ Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem
+ Cохранение кошелька
+ Если эта опция включена, кошельки всех отсканированных карт будут привязываться к приложению и при открытии приложения будет необходимо пройти биометрическую аутентификацию. Подпись транзакций все так же потребует карту.
+ Сохранение кода доступа
+ Если эта опция включена, пароли от карты будут хранится на телефоне в зашифрованном виде и при работе с картой вместо пароля будет запрашиваться биометрическая аутентификация
+ При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены.
+ Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком.
+ WalletConnect
+ Подключение к Dapps
+
+ Добавить еще карты
+ Вы можете объединить до трех карт в одном кошельке. Это можно сделать только один раз.
+
+ Privacy policy
+
+ Сброс к заводским настройкам
+ Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа
+ Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку
+ Сбросить карту
+ Сброс к заводским настройкам
+ Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа.
+
+ Выберите сеть
diff --git a/app/src/main/res/values-v29/zendesk_styles.xml b/app/src/main/res/values-v29/zendesk_styles.xml
new file mode 100644
index 0000000000..628e83211a
--- /dev/null
+++ b/app/src/main/res/values-v29/zendesk_styles.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 70a942b055..b00c6603a8 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -64,4 +64,82 @@
#CA0F03
#EFEFF0
+
+ #DE1010
+ #007AFF
+ #000000
+ #0029FF
+ #919191
+ #656565
+ #494949
+ #3B3B3B
+ #303030
+ #1E1E1E
+ #06311F
+ #ED7979
+ #F5F5F5
+ #EBEBEB
+ #D3D3D3
+ #C9C9C9
+ #B0B0B0
+ #A3EBCC
+ #1ACE80
+ #FFB71B
+ #FFFFFF
+
+
+ #000000
+ #FFFFFF
+ #FFFFFF
+ #F5F5F5
+
+
+
+ #EBEBEB
+ #1ACE80
+ #A3EBCC
+ #1E1E1E
+ #EBEBEB
+
+
+ #1ACE80
+ #FFFFFF
+ #EBEBEB
+
+
+
+ #EBEBEB
+ #F5F5F5
+
+
+
+ #1ACE80
+ #FFB71B
+ #C9C9C9
+ #B0B0B0
+ #000000
+ #FFFFFF
+ #656565
+ #DE1010
+
+
+
+ #EBEBEB
+ #663B3B3B
+ #FFFFFF
+
+
+
+ #1ACE80
+ #FFB71B
+ #FFFFFF
+ #C9C9C9
+ #1E1E1E
+ #FFFFFF
+ #656565
+ #919191
+ #DE1010
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5a05ac5fae..2c1ebffd92 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -8,6 +8,7 @@
Done
Cancel
Accept
+ Delete
%s
Camera access denied
@@ -44,8 +45,8 @@
Transaction is in progress…
to %s
from %s
- Receiving\u0020
- Sending\u0020
+ Receiving
+ Sending
Explore address
Create PayString
@@ -69,11 +70,6 @@
Fee
Total
Maximum amount
- Invalid address
- Invalid amount
- Amount Exceeds Balance
- Total Exceeds Balance
- Fee Exceeds Balance
Low
Normal
Priority
@@ -172,23 +168,14 @@
\nThis disclaimer was amended for the last time on October 1st, 2020.
Terms of service
- Failed to get fee
- Target account is not created. Amount to send should be %1$s %2$s + fee or more to create
- No internet connection
+ No internet connection
- Unknown error
PayString verification failed
PayString unsupported by blockchain
PayString not registered
PayString request failed
Address is the same as wallet address
- Insufficient balance
- Blockchain internal error
- Invalid Fee
- Minimum amount is %s
- Change is too small
- To create account send 1+ XLM to this address
Address was copied to clipboard
@@ -200,9 +187,4 @@
Your balance on this wallet is not zero, or you have unconfirmed transactions
This is the currently active option
-
-
- To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ
- Reduce by %s XTZ
- No, send all
diff --git a/app/src/main/res/values/strings_blockchain.xml b/app/src/main/res/values/strings_blockchain.xml
new file mode 100644
index 0000000000..35bc8cf334
--- /dev/null
+++ b/app/src/main/res/values/strings_blockchain.xml
@@ -0,0 +1,20 @@
+
+
+ Insufficient balance
+ Blockchain internal error
+ Invalid Fee
+ Minimum amount is %s
+ Change is too small
+ To create account send 1+ XLM to this address
+ Failed to get fee
+ Target account is not created. Amount to send should be %s %s + fee or more to create
+ Unknown error
+ Invalid address
+ Invalid amount
+ Amount Exceeds Balance
+ Total Exceeds Balance
+ Fee Exceeds Balance
+ To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ
+ Reduce by %s XTZ
+ No, send all
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml
index 3a16ec6ea0..ca04743898 100644
--- a/app/src/main/res/values/strings_final.xml
+++ b/app/src/main/res/values/strings_final.xml
@@ -23,7 +23,9 @@
Revolutionary Hardware Wallet
Store your crypto assets secure while keeping private keys contained in your card
Ultra Secure Backup
- Up to 3 physical cards to one wallet
+ Up to
+ 3 physical cards
+ to one wallet
Thousands of Currencies
A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card
DeFi Compatible
@@ -45,6 +47,9 @@
If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds
Custom
+ Notice
+ Attention
+ The server is not available, please try again later
Total balance
The amount does not include some of your funds
@@ -54,11 +59,13 @@
Hide token
Hide %s
Hide
- You are about to hide this token from the main screen. You can add it back anytime.
+ You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.
Unable to hide %s
The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.
%s network not found. Please, add it first and try again.
+ This network is not supported. Please select another network.
+
%s network
Your wallet has not been backed up
@@ -69,4 +76,42 @@
Do you have a bank card of another country or a UnionPay card?
Yes
No
+
+ Chat
+ Tangem Bot
+
+
+ Card Settings
+ Security Mode
+ Selected application protection method
+ Change access code
+ Access code will be changed on this card only
+ Get your card ready!
+ Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet.
+ Scan Card
+ App settings
+ Enable biometric authentication
+ Go to settings to enable biometric authentication in the Tangem app
+ Keep the wallet in the app
+ Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card.
+ Save Access Code
+ Biometric authentication will be requested instead of the access code for interactions with your card.
+ Removing the saved card deletes all the saved wallets and their access codes.
+ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code.
+ WalletConnect
+ Connect to Dapps
+
+ Link More Cards
+ You can synchronize up to three cards into one wallet. It can only be done once.
+
+ Privacy policy
+
+ Reset to factory settings
+ This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
+ I understand that after performing this action, I will no longer have access to the current wallet
+ Reset the card
+ Reset to Factory Settings
+ Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
+
+ Select network
diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml
index 1f498055db..405518a258 100644
--- a/app/src/main/res/values/strings_untranslated.xml
+++ b/app/src/main/res/values/strings_untranslated.xml
@@ -55,7 +55,7 @@
Failed to send email
Reason: %s
Can\'t send a transaction
- Reason: %s. Do you want to send feedback?
+ Reason: %s
Are you having difficulty scanning your card?
Please try to tap the card exactly as shown in the animation or request support.
I\'m okay
@@ -79,7 +79,6 @@
Custom tokens
Popular tokens
Added
- Remove token
Token icon
Add Token
@@ -132,7 +131,7 @@
There are only %s signatures available on this card. You must withdraw all of your funds.
//Wallet Connect
- Wallet Connect
+ WalletConnect
Wallet Connect Sessions
WalletConnect session opened with %s
Ooops. No Sessions.
@@ -284,7 +283,6 @@
Active. Backup cards count: %d
Linked. Backup cards count: %d
No backup
- Create backup
You have an interrupted backup. Do you want to resume?
Yes, resume
Discard
@@ -300,7 +298,6 @@
Prepare the backup card with number %s
Prepare the primary card with number %s
Reset to factory settings
- This action is irreversible. If, after resetting the card, someone sends funds to it, then you will not be able to withdraw them.
Access code is available only for cards with a backup.
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index f36d1cd4bf..09cdff0b35 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -5,10 +5,6 @@
- @color/colorPrimary
- @color/colorSecondary
- @color/darkGray6
- - true
- - true
- - true
- - @null
- @color/menu_accent_color
- @style/Widget.AppTheme.CardView
diff --git a/app/src/main/res/values/zendesk_styles.xml b/app/src/main/res/values/zendesk_styles.xml
new file mode 100644
index 0000000000..ebb4fcab6b
--- /dev/null
+++ b/app/src/main/res/values/zendesk_styles.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 0000000000..2bbb4f525b
--- /dev/null
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+ api.tangem.com
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 23b9e4d0bc..64f57c2248 100644
--- a/build.gradle
+++ b/build.gradle
@@ -22,6 +22,7 @@ allprojects {
mavenLocal()
maven { url "https://nexus.tangem-tech.com/repository/maven-releases/" }
maven { url 'https://jitpack.io' }
+ maven { url 'https://zendesk.jfrog.io/zendesk/repo' }
}
}
diff --git a/dependencies.gradle b/dependencies.gradle
index d463106208..e1c383532f 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,10 +1,13 @@
ext.versions = [
- kotlin : '1.6.10',
- build_gradle: '7.1.3',
+ kotlin : '1.6.10',
+ build_gradle : '7.1.3',
+ tangem_card_sdk : 'develop-159',
+ tangem_blockchain_sdk: 'develop-105',
+ // tangem_blockchain_sdk: '0.0.1',
]
ext.environmentConfig = [
- environment: "ENVIRONMENT",
+ environment : "ENVIRONMENT",
testActionEnabled: "TEST_ACTION_ENABLED",
logEnabled : "LOG_ENABLED",
-]
\ No newline at end of file
+]
diff --git a/domain/build.gradle b/domain/build.gradle
index c2e5c807db..83c9fe6ff1 100644
--- a/domain/build.gradle
+++ b/domain/build.gradle
@@ -59,10 +59,9 @@ dependencies {
implementation implementation(project(path: ':network'))
implementation implementation(project(path: ':common'))
- implementation 'com.tangem:blockchain:AND-1939_tron_fix_release-97'
-// implementation 'com.tangem:blockchain:0.0.1'
- implementation 'com.tangem.tangem-sdk-kotlin:core:develop-154'
- implementation 'com.tangem.tangem-sdk-kotlin:android:develop-154'
+ implementation "com.tangem:blockchain:${versions.tangem_blockchain_sdk}"
+ implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tangem_card_sdk}"
+ implementation "com.tangem.tangem-sdk-kotlin:android:${versions.tangem_card_sdk}"
// Kotlin coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
diff --git a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt
index 644cc7525e..a11441c51c 100644
--- a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt
+++ b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt
@@ -9,20 +9,18 @@ import java.util.*
[REDACTED_AUTHOR]
*/
object TapWorkarounds {
-
fun isStart2CoinIssuer(cardIssuer: String?): Boolean {
return cardIssuer?.lowercase(Locale.US) == START_2_COIN_ISSUER
}
val Card.isStart2Coin: Boolean
get() = isStart2CoinIssuer(issuer.name)
-
+ val Card.isSaltPay: Boolean
+ get() = false //TODO fix when we know which cards are SaltPay cards
val Card.isTestCard: Boolean
get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH)
-
val Card.useOldStyleDerivation: Boolean
get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95"
-
val Card.derivationStyle: DerivationStyle?
get() = if (!settings.isHDWalletAllowed) {
null
@@ -42,22 +40,19 @@ object TapWorkarounds {
return false
}
- @Deprecated("Use ScanResponse.isTangemNote")
- fun isTangemNote(card: Card): Boolean = tangemNoteBatches.contains(card.batchId)
-
+ fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId) || isSaltPay
fun isTangemWalletBatch(card: Card): Boolean = tangemWalletBatches.contains(card.batchId)
-
- fun Card.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[batchId]
+ fun Card.getTangemNoteBlockchain(): Blockchain? =
+ tangemNoteBatches[batchId] ?: if (isSaltPay) Blockchain.Gnosis else null
private const val START_2_COIN_ISSUER = "start2coin"
private const val TEST_CARD_BATCH = "99FF"
private const val TEST_CARD_ID_STARTS_WITH = "FF99"
-
private val excludedBatches = listOf(
"0027",
"0030",
"0031",
- "0035"
+ "0035",
)
private val excludedIssuers = listOf(
diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
index b69ffb1c49..8708f1a0a8 100644
--- a/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
+++ b/domain/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
@@ -37,6 +37,7 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
"tron" -> Blockchain.Tron
"tron/test" -> Blockchain.TronTestnet
"xrp", "ripple" -> Blockchain.XRP
+ "xdai" -> Blockchain.Gnosis
else -> null
}
}
@@ -78,6 +79,7 @@ fun Blockchain.toNetworkId(): String {
Blockchain.XRP -> "xrp"
Blockchain.Tron -> "tron"
Blockchain.TronTestnet -> "tron/test"
+ Blockchain.Gnosis -> "xdai"
}
}
@@ -102,6 +104,7 @@ fun Blockchain.toCoinId(): String {
Blockchain.XRP -> "ripple"
Blockchain.Dogecoin -> "dogecoin"
Blockchain.Tron, Blockchain.TronTestnet -> "tron"
- else -> "unknown"
+ Blockchain.Gnosis -> "xdai"
+ Blockchain.Unknown -> "unknown"
}
}
\ No newline at end of file
diff --git a/network/build.gradle b/network/build.gradle
index b90ff59980..e7fccceda6 100644
--- a/network/build.gradle
+++ b/network/build.gradle
@@ -12,7 +12,7 @@ dependencies {
implementation implementation(project(path: ':common'))
// Tangem sdk's
- implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142'
+ implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tangem_card_sdk}"
// Kotlin coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'