diff --git a/.editorconfig b/.editorconfig
index 25a2f7c928..0ab5ea61ec 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,5 +1,4 @@
[*.{kt,kts}]
-ktlint_ignore_back_ticked_identifier = true
indent_size = 4
indent_style = space
diff --git a/.gitignore b/.gitignore
index 25c98d5d1f..dcdbe5fc57 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,4 @@ local.properties
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output
+/buildSrc/build/
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 76a59bafa3..6527894333 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -12,6 +12,7 @@
+
@@ -199,4 +200,4 @@
-
\ No newline at end of file
+
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index 79ee123c2b..0f7bc519db 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index c914631447..ce1b62532a 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -61,8 +61,8 @@
-
-
+
+
diff --git a/app/build.gradle b/app/build.gradle
deleted file mode 100644
index 70b6edcebd..0000000000
--- a/app/build.gradle
+++ /dev/null
@@ -1,187 +0,0 @@
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-kapt'
-apply plugin: 'com.google.gms.google-services'
-apply plugin: 'com.google.firebase.crashlytics'
-apply from: '../dependencies.gradle'
-
-android {
- compileSdkVersion 33
- 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 33
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-
- buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false'
- buildConfigField 'Boolean', environmentConfig.logEnabled, 'false'
- }
- buildFeatures {
- compose true
- viewBinding true
- }
- buildTypes {
- release {
- debuggable false
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- signingConfig signingConfigs.debug
- buildConfigField 'String', environmentConfig.environment, '\"prod\"'
- buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false'
- buildConfigField 'Boolean', environmentConfig.logEnabled, 'false'
- }
- debug {
- debuggable true
- minifyEnabled false
- applicationIdSuffix ".dev"
- signingConfig signingConfigs.debug
- firebaseCrashlytics {
- mappingFileUploadEnabled false
- }
- buildConfigField 'String', environmentConfig.environment, '\"dev\"'
- buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'true'
- buildConfigField 'Boolean', environmentConfig.logEnabled, 'true'
- }
- debug_beta {
- initWith release
- debuggable false
- versionNameSuffix "-beta"
- applicationIdSuffix ".debug"
- buildConfigField 'String', environmentConfig.environment, '\"prod\"'
- buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false'
- buildConfigField 'Boolean', environmentConfig.logEnabled, 'false'
- }
-
- }
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_1_8.toString()
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- coreLibraryDesugaringEnabled false
- }
- composeOptions {
- kotlinCompilerExtensionVersion '1.1.0'
- }
- packagingOptions {
- exclude 'lib/x86_64/darwin/libscrypt.dylib'
- exclude 'lib/x86_64/freebsd/libscrypt.so'
- exclude 'lib/x86_64/linux/libscrypt.so'
- }
-}
-
-repositories {
- maven { url "https://jitpack.io" }
-}
-
-dependencies {
- implementation fileTree(include: ['*.aar'], dir: 'libs')
- implementation implementation(project(path: ':domain'))
- implementation implementation(project(path: ':network'))
- implementation implementation(project(path: ':common'))
-
- implementation 'androidx.core:core-ktx:1.9.0'
- implementation 'androidx.appcompat:appcompat:1.5.1'
- implementation 'androidx.fragment:fragment-ktx:1.5.3'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation 'com.google.android.material:material:1.6.1'
- 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:${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.4.0"
-
- implementation 'com.jakewharton.timber:timber:4.7.1'
-
- //lifecycle
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
- implementation "androidx.lifecycle:lifecycle-common-java8:2.5.1"
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
-
- implementation "org.rekotlin:rekotlin:1.0.4"
-
- // QR
- implementation 'com.google.zxing:core:3.3.3'
- implementation 'me.dm7.barcodescanner:zxing:1.9.8'
-
- // Camera
- implementation 'com.otaliastudios:cameraview:2.6.3'
-
- // Image Loading
- implementation 'io.coil-kt:coil:2.1.0'
- implementation 'io.coil-kt:coil-compose:2.1.0'
-
- // Firebase
- implementation platform('com.google.firebase:firebase-bom:26.0.0')
- implementation 'com.google.firebase:firebase-analytics-ktx'
- implementation 'com.google.firebase:firebase-crashlytics-ktx'
-
- // Analytics
- implementation 'com.appsflyer:af-android-sdk:6.5.1'
- implementation 'com.amplitude:android-sdk:2.36.1'
-
- // WalletConnect
- implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
-
- // Network and Json
- //TODO: refactoring: remove it when all network services moved to the network module
- implementation 'com.squareup.retrofit2:retrofit:2.8.1'
- implementation 'com.squareup.retrofit2:converter-moshi:2.6.0'
- implementation 'com.squareup.moshi:moshi:1.12.0'
- implementation "com.squareup.moshi:moshi-kotlin:1.12.0"
-
- implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3"))
- implementation("com.squareup.okhttp3:okhttp")
- implementation("com.squareup.okhttp3:logging-interceptor")
-
- // 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
- implementation "com.airbnb.android:lottie:3.4.0"
-
- // Shopify
- implementation('com.shopify.mobilebuysdk:buy3:12.0.0') {
- exclude group: "com.shopify.graphql.support"
- exclude module: 'joda-time'
- }
-
- // Google Pay
- implementation 'com.google.android.gms:play-services-wallet:19.1.0'
-
- //Compose
- 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.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"
-
- implementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6'
-
- // Shared preferences (encrypted)
- implementation 'at.favre.lib:armadillo:0.9.0'
-
- testImplementation 'junit:junit:4.13.2'
- testImplementation "com.google.truth:truth:1.1.3"
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
-
-}
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000000..1c44bd0851
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,212 @@
+plugins {
+ id("com.android.application")
+ kotlin("android")
+ kotlin("kapt")
+ kotlin("plugin.serialization")
+ id("com.google.gms.google-services")
+ id("com.google.firebase.crashlytics")
+ id("com.google.dagger.hilt.android")
+}
+
+android {
+ compileSdk = AppConfig.compileSdkVersion
+
+ defaultConfig {
+ applicationId = AppConfig.packageName
+ minSdk = AppConfig.minSdkVersion
+ targetSdk = AppConfig.targetSdkVersion
+
+ versionCode = if (project.hasProperty("versionCode")) {
+ (project.property("versionCode") as String).toInt()
+ } else {
+ AppConfig.versionCode
+ }
+
+ versionName = if (project.hasProperty("versionName")) {
+ project.property("versionName") as String
+ } else {
+ AppConfig.versionName
+ }
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildFeatures {
+ compose = true
+ viewBinding = true
+ }
+
+ buildTypes {
+ release {
+ isDebuggable = false
+ isMinifyEnabled = false
+ proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
+
+ BuildConfigFieldFactory(
+ fields = listOf(
+ Field.Environment("prod"),
+ Field.TestActionEnabled(false),
+ Field.LogEnabled(false),
+ ),
+ builder = ::buildConfigField,
+ ).create()
+ }
+
+ debug {
+ isDebuggable = true
+ isMinifyEnabled = false
+ applicationIdSuffix = ".dev"
+
+ configure {
+ // disable mapping file uploads (default=true if minifying)
+ mappingFileUploadEnabled = false
+ }
+
+ BuildConfigFieldFactory(
+ fields = listOf(
+ Field.Environment("dev"),
+ Field.TestActionEnabled(true),
+ Field.LogEnabled(true),
+ ),
+ builder = ::buildConfigField,
+ ).create()
+ }
+
+ create("debug_beta") {
+ initWith(getByName("release"))
+ versionNameSuffix = "-beta"
+ applicationIdSuffix = ".debug"
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_1_8.toString()
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ isCoreLibraryDesugaringEnabled = false
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = Versions.compose
+ }
+
+ packagingOptions {
+ resources.excludes += "lib/x86_64/darwin/libscrypt.dylib"
+ resources.excludes += "lib/x86_64/freebsd/libscrypt.so"
+ resources.excludes += "lib/x86_64/linux/libscrypt.so"
+ resources.excludes += "META-INF/gradle/incremental.annotation.processors"
+ }
+}
+
+repositories {
+ mavenCentral()
+ maven(url = "https://jitpack.io")
+}
+
+dependencies {
+ implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
+ implementation(project(":domain"))
+ implementation(project(":common"))
+ implementation(project(":core:analytics"))
+ implementation(project(":core:res"))
+ implementation(project(":core:ui"))
+ implementation(project(":core:datasource"))
+ implementation(project(":core:utils"))
+ implementation(project(":libs:crypto"))
+ implementation(project(":libs:auth"))
+
+ /** Features */
+ implementation(project(":features:referral:presentation"))
+ implementation(project(":features:referral:domain"))
+ implementation(project(":features:referral:data"))
+ implementation(project(":features:swap:presentation"))
+ implementation(project(":features:swap:domain"))
+ implementation(project(":features:swap:data"))
+
+ /** AndroidX libraries */
+ implementation(AndroidX.coreKtx)
+ implementation(AndroidX.appCompat)
+ implementation(AndroidX.fragmentKtx)
+ implementation(AndroidX.constraintLayout)
+ implementation(AndroidX.browser)
+ implementation(AndroidX.lifecycleRuntimeKtx)
+ implementation(AndroidX.lifecycleCommonJava8)
+ implementation(AndroidX.lifecycleViewModelKtx)
+ implementation(AndroidX.lifecycleLiveDataKtx)
+ implementation(AndroidX.activityCompose)
+
+ /** Compose libraries */
+ implementation(Compose.material)
+ implementation(Compose.animation)
+ implementation(Compose.foundation)
+ implementation(Compose.ui)
+ implementation(Compose.uiTooling)
+ implementation(Compose.coil)
+
+ /** Firebase libraries */
+ implementation(platform(Firebase.bom))
+ implementation(Firebase.firebaseAnalytics)
+ implementation(Firebase.firebaseCrashlytics)
+
+ /** Tangem libraries */
+ implementation(Tangem.blockchain) {
+ exclude(module = "joda-time")
+ }
+ implementation(Tangem.cardCore)
+ implementation(Tangem.cardAndroid) {
+ exclude(module = "joda-time")
+ }
+
+ /** DI */
+ implementation(Library.hilt)
+ kapt(Library.hiltKapt)
+
+ /** Other libraries */
+ implementation(Library.materialComponent)
+ implementation(Library.googlePlayCore)
+ implementation(Library.googlePlayCoreKtx)
+ coreLibraryDesugaring(Library.desugarJdkLibs)
+ implementation(Library.timber)
+ implementation(Library.reKotlin)
+ implementation(Library.zxingQrCore)
+ implementation(Library.zxingQrBarcodeScanner)
+ implementation(Library.otaliastudiosCameraView)
+ implementation(Library.coil)
+ implementation(Library.appsflyer)
+ implementation(Library.amplitude)
+ implementation(Library.kotsonGsonExt)
+ //TODO: refactoring: remove it when all network services moved to the datasource module
+ implementation(Library.retrofit)
+ implementation(Library.retrofitMoshiConverter)
+ implementation(Library.moshi)
+ implementation(Library.moshiKotlin)
+ implementation(Library.okHttp)
+ implementation(Library.okHttpLogging)
+ implementation(Library.zendeskChat)
+ implementation(Library.zendeskMessaging)
+ implementation(Library.spongecastleCryptoCore)
+ implementation(Library.lottie)
+ implementation(Library.shopifyBuySdk) {
+ exclude(group = "com.shopify.graphql.support")
+ exclude(module = "joda-time")
+ }
+ implementation(Library.accompanistAppCompatTheme)
+ implementation(Library.accompanistSystemUiController)
+ implementation(Library.xmlShimmer)
+ implementation(Library.viewBindingDelegate)
+ implementation(Library.armadillo)
+ implementation(Library.googlePlayServicesWallet)
+ implementation(Library.composeShimmer)
+ implementation(Library.mviCoreWatcher)
+ implementation(Library.kotlinSerialization)
+
+ /** Testing libraries */
+ testImplementation(Test.junit)
+ testImplementation(Test.truth)
+ androidTestImplementation(Test.junitAndroidExt)
+ androidTestImplementation(Test.espresso)
+}
\ No newline at end of file
diff --git a/app/libs/walletconnect-1.5.5.aar b/app/libs/walletconnect-1.5.5.aar
deleted file mode 100644
index 4e2f1b4fb7..0000000000
Binary files a/app/libs/walletconnect-1.5.5.aar and /dev/null differ
diff --git a/app/libs/walletconnect-1.5.6.aar b/app/libs/walletconnect-1.5.6.aar
new file mode 100644
index 0000000000..30c576fb89
Binary files /dev/null and b/app/libs/walletconnect-1.5.6.aar differ
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index f1b424510d..2f9dc5a47e 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
+# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
diff --git a/app/src/debug/res/values-ru/strings.xml b/app/src/debug/res/values-ru/strings.xml
index 1abc31ee12..682b552975 100644
--- a/app/src/debug/res/values-ru/strings.xml
+++ b/app/src/debug/res/values-ru/strings.xml
@@ -1,6 +1,6 @@
- DebugTangem
+ DebugTangem
\ No newline at end of file
diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml
index bf9ef4d842..764dd201cc 100644
--- a/app/src/debug/res/values/strings.xml
+++ b/app/src/debug/res/values/strings.xml
@@ -1,6 +1,6 @@
- DebugTangem
+ DebugTangem
\ No newline at end of file
diff --git a/app/src/debug_beta/res/values-ru/strings.xml b/app/src/debug_beta/res/values-ru/strings.xml
index 3e2d147b31..3ae149049f 100644
--- a/app/src/debug_beta/res/values-ru/strings.xml
+++ b/app/src/debug_beta/res/values-ru/strings.xml
@@ -1,6 +1,6 @@
- BetaTangem
+ BetaTangem
\ No newline at end of file
diff --git a/app/src/debug_beta/res/values/strings.xml b/app/src/debug_beta/res/values/strings.xml
index 3e2d147b31..3ae149049f 100644
--- a/app/src/debug_beta/res/values/strings.xml
+++ b/app/src/debug_beta/res/values/strings.xml
@@ -1,6 +1,6 @@
- BetaTangem
+ BetaTangem
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 874783a15d..5fa1e04d9d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,10 +3,6 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.tangem.wallet">
-
-
@@ -19,6 +15,9 @@
+
@@ -29,16 +28,15 @@
+ tools:replace="android:allowBackup">
-
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -102,6 +102,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -142,5 +153,4 @@
-
diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config
index a1658496e7..93150ef69d 160000
--- a/app/src/main/assets/tangem-app-config
+++ b/app/src/main/assets/tangem-app-config
@@ -1 +1 @@
-Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6
+Subproject commit 93150ef69daf244a92266d7cdfbc9294b745b4e2
diff --git a/app/src/main/java/com/tangem/tap/ActivityResultCaller.kt b/app/src/main/java/com/tangem/tap/ActivityResultCaller.kt
new file mode 100644
index 0000000000..16a05117ac
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/ActivityResultCaller.kt
@@ -0,0 +1,8 @@
+package com.tangem.tap
+
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
+
+interface ActivityResultCaller {
+ val activityResultLauncher: ActivityResultLauncher?
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt
index 806769df5f..9664f38966 100644
--- a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt
+++ b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt
@@ -2,11 +2,18 @@ package com.tangem.tap
import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
+import android.content.Intent
import android.os.Bundle
-import java.util.WeakHashMap
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import java.util.*
import kotlin.reflect.KClass
-class ForegroundActivityObserver {
+class ForegroundActivityObserver : ActivityResultCaller {
+ override var activityResultLauncher: ActivityResultLauncher? = null
+ private set
+
private val activities = WeakHashMap, Activity>()
val foregroundActivity: Activity?
@@ -15,25 +22,38 @@ class ForegroundActivityObserver {
.firstOrNull()
?.value
- val callbacks get() = Callbacks()
+ internal val callbacks: ActivityLifecycleCallbacks
+ get() = Callbacks()
+
+ internal inner class Callbacks : ActivityLifecycleCallbacks {
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
+ activityResultLauncher = (activity as? AppCompatActivity)?.registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult(),
+ ) {
+ /* no-op */
+ }
+ }
- inner class Callbacks : ActivityLifecycleCallbacks {
override fun onActivityResumed(activity: Activity) {
activities[activity::class] = activity
}
override fun onActivityDestroyed(activity: Activity) {
activities.remove(activity::class)
+ if (activities.isEmpty()) {
+ activityResultLauncher = null
+ }
}
- 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) {
}
}
diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt
new file mode 100644
index 0000000000..426f49ca25
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt
@@ -0,0 +1,117 @@
+package com.tangem.tap
+
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.redux.navigation.AppScreen
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+import timber.log.Timber
+import kotlin.time.Duration
+
+internal class LockUserWalletsTimer(
+ owner: LifecycleOwner,
+ private val duration: Duration = with(Duration) { 10.minutes },
+) : LifecycleOwner by owner,
+ DefaultLifecycleObserver {
+
+ private var delayJob: Job? = null
+ set(value) {
+ field?.cancel()
+ field = value
+ }
+
+ init {
+ lifecycle.addObserver(this)
+ }
+
+ override fun onResume(owner: LifecycleOwner) {
+ Timber.d(
+ """
+ Owner resumed
+ |- Was stopped: ${preferencesStorage.wasApplicationStopped}
+ |- Need to open welcome screen: ${preferencesStorage.shouldOpenWelcomeScreenOnResume}
+ """.trimIndent(),
+ )
+ preferencesStorage.wasApplicationStopped = false
+ start()
+ if (preferencesStorage.shouldOpenWelcomeScreenOnResume) {
+ store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
+ preferencesStorage.shouldOpenWelcomeScreenOnResume = false
+ }
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ Timber.d("Owner stopped")
+ preferencesStorage.wasApplicationStopped = true
+ }
+
+ override fun onDestroy(owner: LifecycleOwner) {
+ Timber.d("Owner destroyed")
+ stop()
+ }
+
+ fun restart() {
+ if (delayJob == null) return
+ Timber.d(
+ """
+ Timer restart
+ |- Duration millis: ${duration.inWholeMilliseconds}
+ """.trimIndent(),
+ )
+ start(log = false)
+ }
+
+ private fun start(log: Boolean = true) {
+ if (log) {
+ Timber.d(
+ """
+ Timer start
+ |- Duration millis: ${duration.inWholeMilliseconds}
+ """.trimIndent(),
+ )
+ }
+ delayJob = createDelayJob()
+ }
+
+ private fun stop(log: Boolean = true) {
+ if (log) {
+ Timber.d(
+ """
+ Timer stop
+ |- Was started: ${delayJob?.isActive ?: false}
+ """.trimIndent(),
+ )
+ }
+ delayJob = null
+ }
+
+ private fun createDelayJob(): Job = lifecycleScope.launch(Dispatchers.Default) {
+ val startTime = System.currentTimeMillis()
+ delay(duration)
+ if (isActive) {
+ val userWalletsListManager = userWalletsListManagerSafe ?: return@launch
+ if (userWalletsListManager.hasSavedUserWallets) {
+ val currentTime = System.currentTimeMillis()
+ Timber.d(
+ """
+ Finished
+ |- App is stopped: ${preferencesStorage.wasApplicationStopped}
+ |- Millis passed: ${currentTime - startTime}
+ """.trimIndent(),
+ )
+ userWalletsListManager.lock()
+ if (preferencesStorage.wasApplicationStopped) {
+ preferencesStorage.shouldOpenWelcomeScreenOnResume = true
+ } else {
+ store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
+ }
+ }
+ }
+ }
+}
\ 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 e557e59247..6b5d0bd175 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -10,35 +10,46 @@ import androidx.core.view.WindowInsetsControllerCompat
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.snackbar.Snackbar
import com.tangem.TangemSdk
-import com.tangem.domain.common.FeatureCoroutineExceptionHandler
import com.tangem.operations.backup.BackupService
import com.tangem.tangem_sdk_new.extensions.init
+import com.tangem.tangem_sdk_new.extensions.initWithBiometrics
import com.tangem.tap.common.ActivityResultCallbackHolder
import com.tangem.tap.common.DialogManager
-import com.tangem.tap.common.IntentHandler
import com.tangem.tap.common.OnActivityResultCallback
import com.tangem.tap.common.SnackbarHandler
+import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.NotificationsHandler
-import com.tangem.tap.common.redux.global.AndroidResources
-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.common.shop.GooglePayService
-import com.tangem.tap.common.shop.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE
+import com.tangem.tap.common.shop.googlepay.GooglePayService
+import com.tangem.tap.common.shop.googlepay.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE
import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient
import com.tangem.tap.domain.TangemSdkManager
+import com.tangem.tap.domain.userWalletList.UserWalletsListManager
+import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
+import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.shop.redux.ShopAction
+import com.tangem.tap.features.welcome.redux.WelcomeAction
+import com.tangem.tap.proxy.AppStateHolder
+import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import com.tangem.wallet.R
import com.tangem.wallet.databinding.ActivityMainBinding
+import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import java.lang.ref.WeakReference
+import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
lateinit var tangemSdk: TangemSdk
lateinit var tangemSdkManager: TangemSdkManager
lateinit var backupService: BackupService
+lateinit var userWalletsListManager: UserWalletsListManager
+internal var lockUserWalletsTimer: LockUserWalletsTimer? = null
+ private set
+var userWalletsListManagerSafe: UserWalletsListManager? = null
+ private set
var notificationsHandler: NotificationsHandler? = null
private val coroutineContext: CoroutineContext
@@ -49,11 +60,14 @@ private val mainCoroutineContext: CoroutineContext
get() = Job() + Dispatchers.Main + FeatureCoroutineExceptionHandler.create("mainScope")
val mainScope = CoroutineScope(mainCoroutineContext)
+@AndroidEntryPoint
class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder {
+ @Inject
+ lateinit var appStateHolder: AppStateHolder
+
private var snackbar: Snackbar? = null
private val dialogManager = DialogManager()
- private val intentHandler = IntentHandler()
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
private val onActivityResultCallbacks = mutableListOf()
@@ -64,11 +78,19 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
systemActions()
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
- tangemSdk = TangemSdk.init(this, TangemSdkManager.config)
+ tangemSdk = TangemSdk.initWithBiometrics(this, TangemSdkManager.config)
tangemSdkManager = TangemSdkManager(tangemSdk, this)
+ appStateHolder.tangemSdkManager = tangemSdkManager
+ appStateHolder.tangemSdk = tangemSdk
backupService = BackupService.init(tangemSdk, this)
+ userWalletsListManager = UserWalletsListManager.provideBiometricImplementation(
+ context = applicationContext,
+ tangemSdkManager = tangemSdkManager,
+ )
+ appStateHolder.userWalletsListManager = userWalletsListManager
+ userWalletsListManagerSafe = userWalletsListManager
+ lockUserWalletsTimer = LockUserWalletsTimer(owner = this)
- store.dispatch(GlobalAction.SetResources(getAndroidResources()))
store.dispatch(
ShopAction.CheckIfGooglePayAvailable(
GooglePayService(createPaymentsClient(this), this),
@@ -76,17 +98,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
)
}
- private fun getAndroidResources(): AndroidResources {
- return AndroidResources(
- AndroidResources.RString(
- R.string.copy_toast_msg,
- R.string.details_notification_erase_wallet_not_possible,
- ),
- )
- }
-
private fun systemActions() {
-
WindowCompat.setDecorFitsSystemWindows(window, false)
val windowInsetsController = WindowInsetsControllerCompat(window, binding.root)
@@ -105,19 +117,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
super.onResume()
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
- val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
- val isScannedBefore = store.state.globalState.scanResponse != null
- val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted
- val shopOpened = store.state.shopState.total != null
- if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore && !shopOpened)) {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
- }
- intentHandler.handleIntent(intent)
+ navigateToInitialScreenIfNeeded(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
- intentHandler.handleIntent(intent)
+ intentHandler.handleIntent(intent, userWalletsListManager.hasSavedUserWallets)
}
override fun onStart() {
@@ -140,7 +145,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
if (snackbar != null) return
snackbar = Snackbar.make(
- binding.fragmentContainer, getString(text), Snackbar.LENGTH_INDEFINITE,
+ binding.fragmentContainer,
+ getString(text),
+ Snackbar.LENGTH_INDEFINITE,
)
if (buttonTitle != null && action != null) {
snackbar?.setAction(getString(buttonTitle), action)
@@ -174,4 +181,36 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
override fun removeOnActivityResultCallback(callback: OnActivityResultCallback) {
onActivityResultCallbacks.remove(callback)
}
+
+ override fun onUserInteraction() {
+ super.onUserInteraction()
+
+ lockUserWalletsTimer?.restart()
+ }
+
+ private fun navigateToInitialScreenIfNeeded(intent: Intent?) {
+ val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0
+ val isNotScannedBefore = store.state.globalState.scanResponse == null
+ val isOnboardingServiceNotActive = store.state.globalState.onboardingState.onboardingStarted
+ val isShopNotOpened = store.state.shopState.total != null
+ when {
+ !backStackIsEmpty && isNotScannedBefore && isOnboardingServiceNotActive && isShopNotOpened -> {
+ navigateToInitialScreen(intent)
+ }
+ backStackIsEmpty -> {
+ navigateToInitialScreen(intent)
+ }
+ }
+ }
+
+ private fun navigateToInitialScreen(intent: Intent?) {
+ if (userWalletsListManager.hasSavedUserWallets) {
+ store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome))
+ store.dispatchOnMain(WelcomeAction.HandleIntentIfNeeded(intent))
+ } else {
+ store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home))
+ intentHandler.handleIntent(intent, hasSavedUserWallets = false)
+ }
+ store.dispatch(BackupAction.CheckForUnfinishedBackup)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/NavBarInsetsFragmentLifecycleCallback.kt b/app/src/main/java/com/tangem/tap/NavBarInsetsFragmentLifecycleCallback.kt
index 2239eacd70..6e620bd0bc 100644
--- a/app/src/main/java/com/tangem/tap/NavBarInsetsFragmentLifecycleCallback.kt
+++ b/app/src/main/java/com/tangem/tap/NavBarInsetsFragmentLifecycleCallback.kt
@@ -36,4 +36,4 @@ class NavBarInsetsFragmentLifecycleCallback : FragmentLifecycleCallbacks() {
windowInsets
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt
index c0d6e566b4..0c8ba92c53 100644
--- a/app/src/main/java/com/tangem/tap/TapApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TapApplication.kt
@@ -7,14 +7,23 @@ import coil.ImageLoader
import coil.ImageLoaderFactory
import com.tangem.Log
import com.tangem.LogFormat
+import com.tangem.blockchain.common.BlockchainSdkConfig
+import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
-import com.tangem.common.json.MoshiJsonConverter
+import com.tangem.core.analytics.Analytics
+import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.domain.DomainLayer
import com.tangem.domain.common.LogConfig
-import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.AndroidAssetReader
import com.tangem.tap.common.AssetReader
-import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandlerBuilder
+import com.tangem.tap.common.IntentHandler
+import com.tangem.tap.common.analytics.AnalyticsFactory
+import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
+import com.tangem.tap.common.analytics.filters.BasicSignInFilter
+import com.tangem.tap.common.analytics.filters.BasicTopUpFilter
+import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
+import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
+import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
import com.tangem.tap.common.feedback.FeedbackManager
import com.tangem.tap.common.images.createCoilImageLoader
@@ -28,38 +37,100 @@ import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tokens.UserTokensRepository
+import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator
+import com.tangem.tap.domain.totalBalance.di.provideDefaultImplementation
+import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
+import com.tangem.tap.domain.walletCurrencies.di.provideDefaultImplementation
+import com.tangem.tap.domain.walletStores.WalletStoresManager
+import com.tangem.tap.domain.walletStores.di.provideDefaultImplementation
+import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
+import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository
+import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
+import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.persistence.PreferencesStorage
+import com.tangem.tap.proxy.AppStateHolder
import com.tangem.wallet.BuildConfig
import com.zendesk.logger.Logger
+import dagger.hilt.android.HiltAndroidApp
import org.rekotlin.Store
import timber.log.Timber
import zendesk.chat.Chat
+import javax.inject.Inject
-val store = Store(
- reducer = ::appReducer,
- middleware = AppState.getMiddleware(),
- state = AppState(),
-)
+lateinit var store: Store
lateinit var foregroundActivityObserver: ForegroundActivityObserver
+lateinit var activityResultCaller: ActivityResultCaller
lateinit var preferencesStorage: PreferencesStorage
lateinit var walletConnectRepository: WalletConnectRepository
lateinit var shopService: TangemShopService
lateinit var assetReader: AssetReader
lateinit var userTokensRepository: UserTokensRepository
+private val walletStoresRepository by lazy { WalletStoresRepository.provideDefaultImplementation() }
+private val walletManagersRepository by lazy {
+ WalletManagersRepository.provideDefaultImplementation(
+ walletManagerFactory = WalletManagerFactory(
+ blockchainSdkConfig = store.state.globalState.configManager
+ ?.config
+ ?.blockchainSdkConfig
+ ?: BlockchainSdkConfig(),
+ ),
+ )
+}
+private val walletAmountsRepository by lazy {
+ WalletAmountsRepository.provideDefaultImplementation(
+ tangemTechService = store.state.domainNetworks.tangemTechService,
+ )
+}
+val walletStoresManager by lazy {
+ WalletStoresManager.provideDefaultImplementation(
+ userTokensRepository = userTokensRepository,
+ walletStoresRepository = walletStoresRepository,
+ walletManagersRepository = walletManagersRepository,
+ walletAmountsRepository = walletAmountsRepository,
+ appCurrencyProvider = { store.state.globalState.appCurrency },
+ )
+}
+val walletCurrenciesManager by lazy {
+ WalletCurrenciesManager.provideDefaultImplementation(
+ userTokensRepository = userTokensRepository,
+ walletStoresRepository = walletStoresRepository,
+ walletManagersRepository = walletManagersRepository,
+ walletAmountsRepository = walletAmountsRepository,
+ appCurrencyProvider = { store.state.globalState.appCurrency },
+ )
+}
+val totalFiatBalanceCalculator by lazy {
+ TotalFiatBalanceCalculator.provideDefaultImplementation()
+}
+val intentHandler by lazy { IntentHandler() }
+
+@HiltAndroidApp
class TapApplication : Application(), ImageLoaderFactory {
+ @Inject
+ lateinit var appStateHolder: AppStateHolder
+
override fun onCreate() {
super.onCreate()
+ store = Store(
+ reducer = { action, state ->
+ appReducer(action, state, appStateHolder)
+ },
+ middleware = AppState.getMiddleware(),
+ state = AppState(),
+ )
+
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
foregroundActivityObserver = ForegroundActivityObserver()
+ activityResultCaller = foregroundActivityObserver
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
DomainLayer.init()
@@ -68,7 +139,7 @@ class TapApplication : Application(), ImageLoaderFactory {
walletConnectRepository = WalletConnectRepository(this)
assetReader = AndroidAssetReader(this)
- val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.defaultMoshi())
+ val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi)
initConfigManager(configLoader, ::initWithConfigDependency)
initWarningMessagesManager()
@@ -78,6 +149,9 @@ class TapApplication : Application(), ImageLoaderFactory {
context = this,
tangemTechService = store.state.domainNetworks.tangemTechService,
)
+ appStateHolder.mainStore = store
+ appStateHolder.userTokensRepository = userTokensRepository
+ appStateHolder.walletStoresManager = walletStoresManager
}
override fun newImageLoader(): ImageLoader {
@@ -98,21 +172,29 @@ class TapApplication : Application(), ImageLoaderFactory {
private fun initWithConfigDependency(config: Config) {
shopService = TangemShopService(this, config.shopify!!)
initAnalytics(this, config)
- initFeedbackManager(this, preferencesStorage, config)
+ initFeedbackManager(this, preferencesStorage)
}
private fun initAnalytics(application: Application, config: Config) {
- val globalAnalyticsHandler = GlobalAnalyticsEventHandlerBuilder(
+ val factory = AnalyticsFactory()
+ factory.addHandlerBuilder(AmplitudeAnalyticsHandler.Builder())
+ factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder())
+ factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder())
+
+ factory.addFilter(BasicSignInFilter())
+ factory.addFilter(BasicTopUpFilter(preferencesStorage.toppedUpWalletStorage))
+
+ val buildData = AnalyticsHandlerBuilder.Data(
application = application,
config = config,
isDebug = BuildConfig.DEBUG,
logConfig = LogConfig.analyticsHandlers,
- jsonConverter = MoshiJsonConverter.INSTANCE,
- ).default()
- store.dispatch(GlobalAction.SetGlobalAnalyticsHandler(globalAnalyticsHandler))
+ jsonConverter = MoshiConverter.sdkMoshiConverter,
+ )
+ factory.build(Analytics, buildData)
}
- private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) {
+ private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage) {
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply {
appVersion = try {
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
diff --git a/app/src/main/java/com/tangem/tap/WaitForMigration.kt b/app/src/main/java/com/tangem/tap/WaitForMigration.kt
index 536b9711eb..59c14fb064 100644
--- a/app/src/main/java/com/tangem/tap/WaitForMigration.kt
+++ b/app/src/main/java/com/tangem/tap/WaitForMigration.kt
@@ -4,4 +4,4 @@ package com.tangem.tap
[REDACTED_AUTHOR]
*/
-val DELAY_SDK_DIALOG_CLOSE = 1400L
\ No newline at end of file
+const val DELAY_SDK_DIALOG_CLOSE = 1400L
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt
index 2ae1553d38..2f3e688532 100644
--- a/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt
+++ b/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt
@@ -9,8 +9,8 @@ import java.math.RoundingMode
[REDACTED_AUTHOR]
*/
class CurrencyConverter(
- private val rateValue: BigDecimal,
- private val decimals: Int
+ private val rateValue: BigDecimal,
+ private val decimals: Int
) {
private val roundingMode = RoundingMode.HALF_UP
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 a1739329b5..93c54eeefa 100644
--- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
@@ -14,13 +14,14 @@ import com.tangem.tap.features.details.ui.walletconnect.dialogs.ClipboardOrScanQ
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialog
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
-import com.tangem.tap.features.onboarding.products.twins.ui.dialog.CreateWalletInterruptDialog
+import com.tangem.tap.features.onboarding.OnboardingDialog
+import com.tangem.tap.features.onboarding.products.twins.ui.dialog.TwinningProcessNotCompletedDialog
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
+import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.InterruptOnboardingDialog
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.NoFundsForActivationDialog
+import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.PutVisaCardDialog
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.RegistrationErrorDialog
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
-import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.TryToInterruptRegistrationDialog
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AddMoreBackupCardsDialog
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.BackupInProgressDialog
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
@@ -55,6 +56,7 @@ class DialogManager : StoreSubscriber {
store.unsubscribe(this)
}
+ @Suppress("LongMethod", "ComplexMethod")
override fun newState(state: GlobalState) {
if (state.dialog == null) {
dialog?.dismiss()
@@ -72,17 +74,17 @@ class DialogManager : StoreSubscriber {
is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
- is TwinCardsAction.Wallet.ShowInterruptDialog ->
- CreateWalletInterruptDialog.create(state.dialog, context)
+ is OnboardingDialog.TwinningProcessNotCompleted -> TwinningProcessNotCompletedDialog.create(context)
+ is OnboardingDialog.InterruptOnboarding -> InterruptOnboardingDialog.create(context, state.dialog)
is WalletConnectDialog.UnsupportedCard ->
SimpleAlertDialog.create(
- titleRes = R.string.wallet_connect,
- messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet,
+ titleRes = R.string.wallet_connect_title,
+ messageRes = R.string.wallet_connect_scanner_error_not_valid_card,
context = context,
)
is WalletConnectDialog.AddNetwork ->
SimpleAlertDialog.create(
- titleRes = R.string.wallet_connect,
+ titleRes = R.string.wallet_connect_title,
message = context.getString(
R.string.wallet_connect_network_not_found_format,
state.dialog.network,
@@ -91,14 +93,14 @@ class DialogManager : StoreSubscriber {
)
is WalletConnectDialog.OpeningSessionRejected -> {
SimpleAlertDialog.create(
- titleRes = R.string.wallet_connect,
+ titleRes = R.string.wallet_connect_title,
messageRes = R.string.wallet_connect_same_wcuri,
context = context,
)
}
is WalletConnectDialog.SessionTimeout -> {
SimpleAlertDialog.create(
- titleRes = R.string.wallet_connect,
+ titleRes = R.string.wallet_connect_title,
messageRes = R.string.wallet_connect_error_timeout,
context = context,
)
@@ -109,10 +111,8 @@ class DialogManager : StoreSubscriber {
ChooseNetworkDialog.create(state.dialog.session, state.dialog.networks, context)
is WalletConnectDialog.ClipboardOrScanQr ->
ClipboardOrScanQrDialog.create(state.dialog.clipboardUri, context)
- is WalletConnectDialog.RequestTransaction ->
- TransactionDialog.create(state.dialog.dialogData, context)
- is WalletConnectDialog.PersonalSign ->
- PersonalSignDialog.create(state.dialog.data, context)
+ is WalletConnectDialog.RequestTransaction -> TransactionDialog.create(state.dialog.dialogData, context)
+ is WalletConnectDialog.PersonalSign -> PersonalSignDialog.create(state.dialog.data, context)
is WalletConnectDialog.BnbTransactionDialog ->
BnbTransactionDialog.create(
data = state.dialog.data,
@@ -123,7 +123,7 @@ class DialogManager : StoreSubscriber {
)
is WalletConnectDialog.UnsupportedNetwork ->
SimpleAlertDialog.create(
- titleRes = R.string.wallet_connect,
+ titleRes = R.string.wallet_connect_title,
messageRes = R.string.wallet_connect_scanner_error_unsupported_network,
context = context,
)
@@ -132,16 +132,18 @@ class DialogManager : StoreSubscriber {
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context)
is SaltPayDialog.Activation.NoGas -> NoFundsForActivationDialog.create(context)
- is SaltPayDialog.Activation.TryToInterrupt -> TryToInterruptRegistrationDialog.create(context, state.dialog)
+ is SaltPayDialog.Activation.PutVisaCard -> PutVisaCardDialog.create(context)
is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog)
is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context)
- is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context)
+ is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context, state.dialog)
is WalletDialog.SelectAmountToSendDialog -> 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,
+ state.dialog.messageRes,
+ state.dialog.currencySymbol,
+ state.dialog.currencyTitle,
),
context = context,
)
@@ -153,7 +155,7 @@ class DialogManager : StoreSubscriber {
primaryButtonAction = state.dialog.onOk,
)
is WalletDialog.RussianCardholdersWarningDialog ->
- RussianCardholdersWarningBottomSheetDialog(context, state.dialog.topUpUrl)
+ RussianCardholdersWarningBottomSheetDialog(context, state.dialog.data)
else -> null
}
dialog?.show()
diff --git a/app/src/main/java/com/tangem/tap/common/GlobalLayoutStateHandler.kt b/app/src/main/java/com/tangem/tap/common/GlobalLayoutStateHandler.kt
index af4645f141..a716d772fe 100644
--- a/app/src/main/java/com/tangem/tap/common/GlobalLayoutStateHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/GlobalLayoutStateHandler.kt
@@ -7,9 +7,9 @@ import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
-class GlobalLayoutStateHandler(
- private val view: T,
- attachImmediately: Boolean = true
+class GlobalLayoutStateHandler(
+ private val view: T,
+ attachImmediately: Boolean = true
) : ViewTreeObserver.OnGlobalLayoutListener {
var onStateChanged: ((T) -> Unit)? = null
diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
index 1e41053369..f1cd3b1027 100644
--- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
@@ -3,50 +3,76 @@ package com.tangem.tap.common
import android.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
-import com.tangem.tap.common.redux.navigation.AppScreen
-import com.tangem.tap.common.redux.navigation.NavigationAction
+import android.os.Build
+import com.tangem.tap.common.extensions.removePrefixOrNull
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.welcome.redux.WelcomeAction
+import com.tangem.tap.scope
import com.tangem.tap.store
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import timber.log.Timber
class IntentHandler {
- private val TRANSACTION_ID_PARAM = "transactionId"
- private val CURRENCY_CODE_PARAM = "baseCurrencyCode"
- private val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
- private val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
+ private val nfcActions = arrayOf(
+ NfcAdapter.ACTION_NDEF_DISCOVERED,
+ NfcAdapter.ACTION_TECH_DISCOVERED,
+ NfcAdapter.ACTION_TAG_DISCOVERED,
+ )
- fun handleIntent(intent: Intent?) {
- handleBackgroundScan(intent)
+ fun handleIntent(intent: Intent?, hasSavedUserWallets: Boolean) {
+ handleBackgroundScan(intent, hasSavedUserWallets)
handleWalletConnectLink(intent)
handleSellCurrencyCallback(intent)
}
- private fun handleBackgroundScan(intent: Intent?) {
- if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action ||
- NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action
- )
- ) {
- val tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
- if (tag != null) {
- intent.action = null
- store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
- store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
- store.dispatch(HomeAction.ReadCard)
+ fun handleWalletConnectLink(intent: Intent?) {
+ val wcUri = when (intent?.scheme) {
+ WalletConnectManager.WC_SCHEME -> {
+ intent.data?.toString()
+ }
+ TANGEM_SCHEME -> {
+ intent.data?.toString()?.removePrefixOrNull(TANGEM_WC_PREFIX)
+ }
+ else -> {
+ null
}
}
- }
-
- private fun handleWalletConnectLink(intent: Intent?) {
- if (intent?.scheme == WalletConnectManager.WC_SCHEME) {
- store.dispatch(WalletConnectAction.HandleDeepLink(intent.data?.toString()))
+ if (wcUri != null) {
+ store.dispatch(WalletConnectAction.HandleDeepLink(wcUri))
}
}
- private fun handleSellCurrencyCallback(intent: Intent?) {
+ fun handleBackgroundScan(intent: Intent?, hasSavedUserWallets: Boolean): Boolean {
+ if (intent == null || intent.action !in nfcActions) return false
+
+ val tag: Tag? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java)
+ } else {
+ @Suppress("DEPRECATION")
+ intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
+ }
+ if (tag == null) return false
+
+ intent.action = null
+ if (hasSavedUserWallets) {
+ // TODO: Remove delay after [REDACTED_JIRA]
+ scope.launch {
+ delay(timeMillis = 200)
+ store.dispatch(WelcomeAction.ProceedWithCard)
+ }
+ } else {
+ store.dispatch(HomeAction.ReadCard())
+ }
+
+ return true
+ }
+
+ fun handleSellCurrencyCallback(intent: Intent?) {
try {
val transactionID =
intent?.data?.getQueryParameter(TRANSACTION_ID_PARAM) ?: return
@@ -60,16 +86,25 @@ class IntentHandler {
Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
- store.dispatch(WalletAction.TradeCryptoAction.SendCrypto(
- currencyId = currency,
- amount = amount,
- destinationAddress = destinationAddress,
- transactionId = transactionID
- ))
+ store.dispatch(
+ WalletAction.TradeCryptoAction.SendCrypto(
+ currencyId = currency,
+ amount = amount,
+ destinationAddress = destinationAddress,
+ transactionId = transactionID,
+ ),
+ )
} catch (exception: Exception) {
Timber.d("Not MoonPay URL")
}
}
-
+ companion object {
+ private const val TRANSACTION_ID_PARAM = "transactionId"
+ private const val CURRENCY_CODE_PARAM = "baseCurrencyCode"
+ private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
+ private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
+ private const val TANGEM_SCHEME = "tangem"
+ private const val TANGEM_WC_PREFIX = "tangem://wc?uri="
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt b/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt
index 69581a7a34..47f579ca1b 100644
--- a/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt
+++ b/app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt
@@ -44,7 +44,7 @@ class KeyboardObserver(activity: Activity) {
onKeyboardListener?.invoke(isShow)
}
- private fun isSoftKeyChanged() = ((lastWindowHeight - getWindowHeight()).absoluteValue) == getSoftKeyButtonHeight()
+ private fun isSoftKeyChanged() = (lastWindowHeight - getWindowHeight()).absoluteValue == getSoftKeyButtonHeight()
private fun getSoftKeyButtonHeight(): Int {
val applicationDisplayHeight = DisplayMetrics().apply {
@@ -57,5 +57,4 @@ class KeyboardObserver(activity: Activity) {
return realDisplayHeight - applicationDisplayHeight
}
-
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/TestActions.kt b/app/src/main/java/com/tangem/tap/common/TestActions.kt
index 5eb1021886..b787e7a1b3 100644
--- a/app/src/main/java/com/tangem/tap/common/TestActions.kt
+++ b/app/src/main/java/com/tangem/tap/common/TestActions.kt
@@ -40,9 +40,10 @@ typealias TestAction = Pair Unit>
class TestActionsBottomSheetDialog(
private val appDialog: AppDialog.TestActionsDialog,
- context: Context
+ context: Context,
) : BottomSheetDialog(context) {
+ @Suppress("MagicNumber")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventAnOld.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventAnOld.kt
deleted file mode 100644
index 41cc0d9b96..0000000000
--- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventAnOld.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.tangem.tap.common.analytics
-
-enum class AnalyticsEventAnOld(val event: String) {
- CARD_IS_SCANNED("card_is_scanned"),
- TRANSACTION_IS_SENT("transaction_is_sent"),
- READY_TO_SCAN("ready_to_scan"),
- DEMO_MODE_ACTIVATED("demo_mode_activated"),
-
- APP_RATING_DISPLAYED("rate_app_warning_displayed"),
- APP_RATING_DISMISS("dismiss_rate_app_warning"),
- APP_RATING_NEGATIVE("negative_rate_app_feedback"),
- APP_RATING_POSITIVE("positive_rate_app_feedback"),
-
- WC_SUCCESS_RESPONSE("wallet_connect_success_response"),
- WC_INVALID_REQUEST("wallet_connect_invalid_request"),
- WC_NEW_SESSION("wallet_connect_new_session"),
- WC_SESSION_DISCONNECTED("wallet_connect_session_disconnected"),
-
- GET_CARD("get_card"),
-}
-
-enum class AnalyticsParamAnOld(val param: String) {
- BLOCKCHAIN("blockchain"),
- CARD_ID("cardId"),
- BATCH_ID("batch_id"),
- FIRMWARE("firmware"),
- ACTION("action"),
- ERROR_DESCRIPTION("error_description"),
- ERROR_CODE("error_code"),
- NEW_SECURITY_OPTION("new_security_option"),
- ERROR_KEY("Tangem SDK error key"),
-
- WALLET_CONNECT_ACTION("wallet_connect_action"),
- WALLET_CONNECT_REQUEST("wallet_connect_request"),
- WALLET_CONNECT_DAPP_URL("wallet_connect_dapp_url"),
-
- SOURCE("source"),
-}
-
-enum class GetCardSourceParamsAnOld(val param: String) {
- WELCOME("welcome"),
- ONBOARDING_BUY_MORE("wallet_onboaring_buy_more_cards"),
- ONBOARDING("wallet_onboarding")
-}
-
-object AnalyticsAnOld {
- enum class ActionToLog(val key: String) {
- Scan("tap_scan_task"),
- SendTransaction("send_transaction"),
- WalletConnectSign("wallet_connect_personal_sign"),
- WalletConnectTransaction("wallet_connect_tx_sign"),
- ReadPinSettings("read_pin_settings"),
- ChangeSecOptions("change_sec_options"),
- CreateWallet("create_wallet"),
- PurgeWallet("purge_wallet"),
- WriteIssuerData("write_issuer_data"),
- }
-
- enum class WcSessionEvent { Disconnect, Connect }
-
- enum class WcAction { PersonalSign, SignTransaction, SendTransaction }
-
- sealed class WcAnalyticsEvent {
- data class Error(val error: Throwable, val action: WcAction?) :
- WcAnalyticsEvent()
-
- data class Session(val event: WcSessionEvent, val url: String?) : WcAnalyticsEvent()
- data class Action(val action: WcAction) : WcAnalyticsEvent()
- data class InvalidRequest(val json: String?) : WcAnalyticsEvent()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt
index 0865d2aa81..39291d12f6 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventsLogger.kt
@@ -1,8 +1,8 @@
package com.tangem.tap.common.analytics
import com.tangem.common.json.MoshiJsonConverter
-import com.tangem.tap.common.analytics.api.ErrorEventLogger
-import com.tangem.tap.common.analytics.api.EventLogger
+import com.tangem.core.analytics.api.ErrorEventLogger
+import com.tangem.core.analytics.api.EventLogger
import timber.log.Timber
class AnalyticsEventsLogger(
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsFactory.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsFactory.kt
new file mode 100644
index 0000000000..ed2e3868b5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsFactory.kt
@@ -0,0 +1,38 @@
+package com.tangem.tap.common.analytics
+
+import com.tangem.core.analytics.Analytics
+import com.tangem.core.analytics.api.AnalyticsEventFilter
+import com.tangem.core.analytics.api.ParamsInterceptor
+import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
+
+/**
+[REDACTED_AUTHOR]
+ */
+class AnalyticsFactory {
+
+ private val builders = mutableListOf()
+ private val filters = mutableListOf()
+ private val interceptors = mutableListOf()
+
+ fun addHandlerBuilder(builder: AnalyticsHandlerBuilder) {
+ builders.add(builder)
+ }
+
+ fun addFilter(filter: AnalyticsEventFilter) {
+ filters.add(filter)
+ }
+
+ fun addParamsInterceptor(interceptor: ParamsInterceptor) {
+ interceptors.add(interceptor)
+ }
+
+ fun build(analytics: Analytics, data: AnalyticsHandlerBuilder.Data) {
+ builders.mapNotNull { it.build(data) }.forEach { analytics.addHandler(it.id(), it) }
+ filters.forEach { analytics.addFilter(it) }
+ interceptors.forEach { analytics.addParamsInterceptor(it) }
+
+ builders.clear()
+ filters.clear()
+ interceptors.clear()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsInterfaces.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsInterfaces.kt
deleted file mode 100644
index 6fd6fc7dc8..0000000000
--- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsInterfaces.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.tangem.tap.common.analytics
-
-
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandlerBuilder.kt b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandlerBuilder.kt
deleted file mode 100644
index 8e2b439e08..0000000000
--- a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandlerBuilder.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.tangem.tap.common.analytics
-
-import android.app.Application
-import com.tangem.common.json.MoshiJsonConverter
-import com.tangem.domain.common.AnalyticsHandlersLogConfig
-import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
-import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
-import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeClient
-import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeLogClient
-import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
-import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerClient
-import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerLogClient
-import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
-import com.tangem.tap.common.analytics.handlers.firebase.FirebaseClient
-import com.tangem.tap.common.analytics.handlers.firebase.FirebaseLogClient
-import com.tangem.tap.domain.configurable.config.Config
-
-/**
-[REDACTED_AUTHOR]
- */
-class GlobalAnalyticsEventHandlerBuilder(
- val application: Application,
- val config: Config,
- val isDebug: Boolean,
- val logConfig: AnalyticsHandlersLogConfig,
- val jsonConverter: MoshiJsonConverter,
-) {
-
- private val analyticHandlers = mutableListOf()
-
- fun build(): GlobalAnalyticsEventHandler {
- return GlobalAnalyticsHandler(analyticHandlers.toList())
- }
-
- fun default(): GlobalAnalyticsEventHandler {
- addFirebaseAnalytics()
- addAppsFlyer()
- addAmplitude()
-
- return GlobalAnalyticsHandler(analyticHandlers.toList())
- }
-
- fun addFirebaseAnalytics(): GlobalAnalyticsEventHandlerBuilder {
- if (isDebug) {
- if (logConfig.firebase) FirebaseLogClient(createLogger("FirebaseAnalyticsHandler")) else null
- } else {
- FirebaseClient()
- }?.let {
- analyticHandlers.add(FirebaseAnalyticsHandler(it))
- }
- return this
- }
-
- fun addAppsFlyer(): GlobalAnalyticsEventHandlerBuilder {
- if (isDebug) {
- if (logConfig.appsFlyer) AppsFlyerLogClient(createLogger("AppsFlyerAnalyticsHandler")) else null
- } else {
- AppsFlyerClient(application, config.appsFlyerDevKey)
- }?.let {
- analyticHandlers.add(AppsFlyerAnalyticsHandler(it))
- }
- return this
- }
-
- fun addAmplitude(): GlobalAnalyticsEventHandlerBuilder {
- if (isDebug) {
- if (logConfig.firebase) AmplitudeLogClient(createLogger("AmplitudeAnalyticsHandler")) else null
- } else {
- AmplitudeClient(application, config.amplitudeApiKey)
- }?.let {
- analyticHandlers.add(AmplitudeAnalyticsHandler(it))
- }
- return this
- }
-
- private fun createLogger(name: String): AnalyticsEventsLogger {
- return AnalyticsEventsLogger(name, jsonConverter)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt
deleted file mode 100644
index d1bef7d01b..0000000000
--- a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-package com.tangem.tap.common.analytics
-
-import com.shopify.buy3.Storefront
-import com.tangem.blockchain.common.BlockchainError
-import com.tangem.common.card.Card
-import com.tangem.common.core.TangemSdkError
-import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
-import com.tangem.tap.common.analytics.api.BlockchainSdkErrorEventHandler
-import com.tangem.tap.common.analytics.api.CardSdkErrorEventHandler
-import com.tangem.tap.common.analytics.api.ErrorEventHandler
-import com.tangem.tap.common.analytics.api.ErrorEventLogger
-import com.tangem.tap.common.analytics.api.SdkErrorEventHandler
-import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
-import com.tangem.tap.common.analytics.events.AnalyticsEvent
-import com.tangem.tap.common.extensions.filterNotNull
-
-interface GlobalAnalyticsEventHandler : AnalyticsEventHandler,
- ErrorEventHandler,
- SdkErrorEventHandler,
- ShopifyOrderEventHandler {
-
- fun attachToAllEvents(key: String, value: String)
-
- companion object {
- fun stub(): GlobalAnalyticsEventHandler {
- return object : GlobalAnalyticsEventHandler {
- override fun attachToAllEvents(key: String, value: String) {}
- override fun handleEvent(event: String, params: Map) {}
- override fun handleErrorEvent(error: Throwable, params: Map) {}
- override fun handleCardSdkErrorEvent(
- error: TangemSdkError,
- action: AnalyticsAnOld.ActionToLog,
- params: Map,
- card: Card?,
- ) {
- }
-
- override fun handleBlockchainSdkErrorEvent(
- error: BlockchainError,
- action: AnalyticsAnOld.ActionToLog,
- params: Map,
- card: Card?,
- ) {
- }
-
- override fun handleShopifyOrderEvent(order: Storefront.Order) {}
- }
- }
- }
-}
-
-class GlobalAnalyticsHandler(
- private val analyticsHandlers: List,
-) : GlobalAnalyticsEventHandler {
-
- private val attachToAllEventsParams: MutableMap = mutableMapOf()
-
- override fun attachToAllEvents(key: String, value: String) {
- attachToAllEventsParams[key] = value
- }
-
- override fun handleEvent(event: String, params: Map) {
- analyticsHandlers.forEach { it.handleEvent(event, params) }
- }
-
- override fun handleAnalyticsEvent(
- event: AnalyticsEvent,
- card: Card?,
- blockchain: String?,
- ) {
- analyticsHandlers.forEach { it.handleAnalyticsEvent(event, card, blockchain) }
- }
-
- override fun handleAnalyticsEvent(
- event: AnalyticsEventAnOld,
- params: Map,
- card: Card?,
- blockchain: String?,
- ) {
- analyticsHandlers.forEach { it.handleAnalyticsEvent(event, params, card, blockchain) }
- }
-
- override fun handleErrorEvent(error: Throwable, params: Map) {
- analyticsHandlers.filterIsInstance().forEach {
- it.logErrorEvent(error, params)
- }
- }
-
- override fun handleCardSdkErrorEvent(
- error: TangemSdkError,
- action: AnalyticsAnOld.ActionToLog,
- params: Map,
- card: Card?,
- ) {
- analyticsHandlers.filterIsInstance().forEach {
- it.handleCardSdkErrorEvent(error, action, params, card)
- }
- }
-
- override fun handleBlockchainSdkErrorEvent(
- error: BlockchainError,
- action: AnalyticsAnOld.ActionToLog,
- params: Map,
- card: Card?,
- ) {
- analyticsHandlers.filterIsInstance().forEach {
- it.handleBlockchainSdkErrorEvent(error, action, params, card)
- }
- }
-
- override fun handleShopifyOrderEvent(order: Storefront.Order) {
- analyticsHandlers.filterIsInstance().forEach {
- it.handleShopifyOrderEvent(order)
- }
- }
-
- override fun prepareParams(card: Card?, blockchain: String?, params: Map): Map {
- return super.prepareParams(card, blockchain, params).toMutableMap().apply {
- putAll(attachToAllEventsParams)
- }
- }
-}
-
-fun GlobalAnalyticsEventHandler.logWcEvent(event: AnalyticsAnOld.WcAnalyticsEvent) {
- when (event) {
- is AnalyticsAnOld.WcAnalyticsEvent.Action -> {
- handleAnalyticsEvent(
- event = AnalyticsEventAnOld.WC_SUCCESS_RESPONSE,
- params = mapOf(
- AnalyticsParamAnOld.WALLET_CONNECT_ACTION.param to event.action.name,
- ),
- )
- }
- is AnalyticsAnOld.WcAnalyticsEvent.Error -> {
- val params = mapOf(
- AnalyticsParamAnOld.WALLET_CONNECT_ACTION.param to event.action?.name,
- AnalyticsParamAnOld.ERROR_DESCRIPTION.param to event.error.message,
- ).filterNotNull()
- handleErrorEvent(event.error, params)
- }
- is AnalyticsAnOld.WcAnalyticsEvent.InvalidRequest ->
- handleAnalyticsEvent(
- event = AnalyticsEventAnOld.WC_INVALID_REQUEST,
- params = mapOf(
- AnalyticsParamAnOld.WALLET_CONNECT_REQUEST.param to event.json,
- ).filterNotNull(),
- )
- is AnalyticsAnOld.WcAnalyticsEvent.Session -> {
- val analyticsEvent = when (event.event) {
- AnalyticsAnOld.WcSessionEvent.Disconnect -> AnalyticsEventAnOld.WC_SESSION_DISCONNECTED
- AnalyticsAnOld.WcSessionEvent.Connect -> AnalyticsEventAnOld.WC_NEW_SESSION
- }
- handleAnalyticsEvent(
- event = analyticsEvent,
- params = mapOf(
- AnalyticsParamAnOld.WALLET_CONNECT_DAPP_URL.param to event.url,
- ).filterNotNull(),
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkErrorMapper.kt
similarity index 95%
rename from app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt
rename to app/src/main/java/com/tangem/tap/common/analytics/TangemSdkErrorMapper.kt
index 17e31d88c4..2f9552c1ae 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkErrorMapper.kt
@@ -2,10 +2,11 @@ package com.tangem.tap.common.analytics
import com.tangem.common.core.TangemSdkError
-object TangemSdk {
+object TangemSdkErrorMapper {
// This mapping is performed to group errors in FirebaseCrashlytics.
// At the moment, the errors in Crashlytics can only be grouped by their place of creation (class and line).
+ @Suppress("LongMethod", "ComplexMethod")
fun map(error: TangemSdkError): TangemSdkError {
return when (error) {
is TangemSdkError.TagLost -> TangemSdkError.TagLost()
@@ -13,12 +14,15 @@ object TangemSdk {
is TangemSdkError.SerializeCommandError -> TangemSdkError.SerializeCommandError()
is TangemSdkError.DeserializeApduFailed -> TangemSdkError.DeserializeApduFailed()
is TangemSdkError.EncodingFailedTypeMismatch -> TangemSdkError.EncodingFailedTypeMismatch(
- error.customMessage)
+ error.customMessage
+ )
is TangemSdkError.EncodingFailed -> TangemSdkError.EncodingFailed(error.customMessage)
is TangemSdkError.DecodingFailedMissingTag -> TangemSdkError.DecodingFailedMissingTag(
- error.customMessage)
+ error.customMessage
+ )
is TangemSdkError.DecodingFailedTypeMismatch -> TangemSdkError.DecodingFailedTypeMismatch(
- error.customMessage)
+ error.customMessage
+ )
is TangemSdkError.DecodingFailed -> TangemSdkError.DecodingFailed(error.customMessage)
is TangemSdkError.InvalidResponse -> TangemSdkError.InvalidResponse()
is TangemSdkError.UnknownStatus -> TangemSdkError.UnknownStatus(error.statusWord)
@@ -74,7 +78,7 @@ object TangemSdk {
is TangemSdkError.UnsupportedWalletConfig -> TangemSdkError.UnsupportedWalletConfig()
is TangemSdkError.CryptoUtilsError -> TangemSdkError.CryptoUtilsError(error.customMessage)
is TangemSdkError.NetworkError -> TangemSdkError.NetworkError(error.customMessage)
- is TangemSdkError.ExceptionError -> TangemSdkError.ExceptionError(error.throwable)
+ is TangemSdkError.ExceptionError -> TangemSdkError.ExceptionError(error.cause)
is TangemSdkError.TooMuchBackupCards -> TangemSdkError.TooMuchBackupCards()
is TangemSdkError.BackupCardRequired -> TangemSdkError.BackupCardRequired()
is TangemSdkError.CertificateSignatureRequired -> TangemSdkError.CertificateSignatureRequired()
@@ -90,7 +94,8 @@ object TangemSdk {
is TangemSdkError.BackupFailedEmptyWallets -> TangemSdkError.BackupFailedEmptyWallets()
is TangemSdkError.BackupFailedNotEmptyWallets -> TangemSdkError.BackupFailedNotEmptyWallets()
is TangemSdkError.NoActiveBackup -> TangemSdkError.NoActiveBackup()
- is TangemSdkError.ResetBackupFailedHasBackupedWallets -> TangemSdkError.ResetBackupFailedHasBackupedWallets()
+ is TangemSdkError.ResetBackupFailedHasBackupedWallets ->
+ TangemSdkError.ResetBackupFailedHasBackupedWallets()
is TangemSdkError.BackupServiceInvalidState -> TangemSdkError.BackupServiceInvalidState()
is TangemSdkError.NoBackupCardForIndex -> TangemSdkError.NoBackupCardForIndex()
is TangemSdkError.EmptyBackupCards -> TangemSdkError.EmptyBackupCards()
@@ -107,7 +112,8 @@ object TangemSdk {
is TangemSdkError.BackupFailedFirmware -> TangemSdkError.BackupFailedFirmware()
is TangemSdkError.UserForgotTheCode -> TangemSdkError.UserForgotTheCode()
is TangemSdkError.BackupFailedIncompatibleBatch -> TangemSdkError.BackupFailedIncompatibleBatch()
+ is TangemSdkError.BiometricsUnavailable -> error
+ is TangemSdkError.BiometricsAuthenticationFailed -> error
}
}
-
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/AnalyticsHandlerBuilder.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/AnalyticsHandlerBuilder.kt
new file mode 100644
index 0000000000..6d23925143
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/api/AnalyticsHandlerBuilder.kt
@@ -0,0 +1,19 @@
+package com.tangem.tap.common.analytics.api
+
+import android.app.Application
+import com.tangem.common.json.MoshiJsonConverter
+import com.tangem.core.analytics.api.AnalyticsHandler
+import com.tangem.domain.common.AnalyticsHandlersLogConfig
+import com.tangem.tap.domain.configurable.config.Config
+
+interface AnalyticsHandlerBuilder {
+ fun build(data: Data): AnalyticsHandler?
+
+ data class Data(
+ val application: Application,
+ val config: Config,
+ val isDebug: Boolean,
+ val logConfig: AnalyticsHandlersLogConfig,
+ val jsonConverter: MoshiJsonConverter,
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt
deleted file mode 100644
index 08e1609e3c..0000000000
--- a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.tangem.tap.common.analytics.api
-
-import com.shopify.buy3.Storefront
-import com.tangem.blockchain.common.BlockchainError
-import com.tangem.common.card.Card
-import com.tangem.common.core.TangemSdkError
-import com.tangem.tap.common.analytics.AnalyticsAnOld
-import com.tangem.tap.common.analytics.AnalyticsEventAnOld
-import com.tangem.tap.common.analytics.AnalyticsParamAnOld
-import com.tangem.tap.common.analytics.events.AnalyticsEvent
-import com.tangem.tap.common.analytics.events.AnalyticsParam
-import com.tangem.tap.common.extensions.filterNotNull
-
-/**
-[REDACTED_AUTHOR]
- */
-interface AnalyticsEventHandler {
- fun handleEvent(
- event: String,
- params: Map = emptyMap(),
- )
-
- fun handleAnalyticsEvent(
- event: AnalyticsEvent,
- card: Card? = null,
- blockchain: String? = null,
- ) {
- handleEvent(
- event = prepareEventString(event.category, event.event),
- params = prepareParams(card, blockchain, event.params),
- )
- }
-
- @Deprecated("Migrate to AnalyticsEvent")
- fun handleAnalyticsEvent(
- event: AnalyticsEventAnOld,
- params: Map = emptyMap(),
- card: Card? = null,
- blockchain: String? = null,
- ) {
- handleEvent(event.event, prepareParams(card, blockchain, params))
- }
-
- fun prepareParams(
- card: Card? = null,
- blockchain: String? = null,
- params: Map = emptyMap(),
- ): Map = mapOf(
- AnalyticsParam.Firmware to card?.firmwareVersion?.stringValue,
- AnalyticsParam.BatchId to card?.batchId,
- AnalyticsParam.Blockchain to blockchain,
- ).filterNotNull() + params
-
- fun prepareEventString(category: String, event: String): String {
- return "[$category] $event"
- }
-}
-
-interface ErrorEventHandler {
- fun handleErrorEvent(
- error: Throwable,
- params: Map = emptyMap(),
- )
-}
-
-interface SdkErrorEventHandler : CardSdkErrorEventHandler, BlockchainSdkErrorEventHandler
-
-interface CardSdkErrorEventHandler {
- fun handleCardSdkErrorEvent(
- error: TangemSdkError,
- action: AnalyticsAnOld.ActionToLog,
- params: Map = emptyMap(),
- card: Card? = null,
- )
-}
-
-interface BlockchainSdkErrorEventHandler {
- fun handleBlockchainSdkErrorEvent(
- error: BlockchainError,
- action: AnalyticsAnOld.ActionToLog,
- params: Map = mapOf(),
- card: Card? = null,
- )
-}
-
-interface ShopifyOrderEventHandler {
- fun handleShopifyOrderEvent(order: Storefront.Order)
-}
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt
new file mode 100644
index 0000000000..b6fab47d07
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt
@@ -0,0 +1,70 @@
+package com.tangem.tap.common.analytics.converters
+
+import com.tangem.blockchain.common.BlockchainSdkError
+import com.tangem.common.Converter
+import com.tangem.common.core.TangemSdkError
+import com.tangem.tap.common.analytics.events.AnalyticsParam
+import com.tangem.tap.features.demo.DemoTransactionSender
+
+/**
+[REDACTED_AUTHOR]
+ */
+class AnalyticsErrorConverter : Converter> {
+ fun canBeHandled(error: Throwable): Boolean {
+ return when (error) {
+ is TangemSdkError.UserCancelled -> false
+ is BlockchainSdkError -> !error.customMessage.contains(DemoTransactionSender.ID)
+ else -> true
+ }
+ }
+
+ override fun convert(value: Throwable): Map {
+ return when (value) {
+ is BlockchainSdkError -> BlockchainSdkErrorConverter(CardSdkErrorConverter()).convert(value)
+ is TangemSdkError -> CardSdkErrorConverter().convert(value)
+ else -> ThrowableErrorConverter().convert(value)
+ }
+ }
+}
+
+private class ThrowableErrorConverter : Converter> {
+ override fun convert(value: Throwable): Map {
+ val unknown = "unknown"
+ return mapOf(
+ AnalyticsParam.ErrorKey to AnalyticsParam.Error.App.value,
+ AnalyticsParam.ErrorDescription to (value.message ?: value.cause?.message ?: unknown),
+ )
+ }
+}
+
+class CardSdkErrorConverter : Converter> {
+
+ override fun convert(value: TangemSdkError): Map {
+ if (value is TangemSdkError.UserCancelled) return emptyMap()
+
+ return mapOf(
+ AnalyticsParam.ErrorKey to AnalyticsParam.Error.CardSdk.value,
+ AnalyticsParam.ErrorCode to value.code.toString(),
+ AnalyticsParam.ErrorDescription to value.toString(),
+ )
+ }
+}
+
+private class BlockchainSdkErrorConverter(
+ private val cardSdkErrorConverter: CardSdkErrorConverter,
+) : Converter> {
+
+ override fun convert(value: BlockchainSdkError): Map {
+ if (value.customMessage.contains(DemoTransactionSender.ID)) return emptyMap()
+
+ if (value is BlockchainSdkError.WrappedTangemError) {
+ return (value.tangemError as? TangemSdkError)?.let { cardSdkErrorConverter.convert(it) } ?: emptyMap()
+ }
+
+ return mapOf(
+ AnalyticsParam.ErrorKey to AnalyticsParam.Error.BlockchainSdk.value,
+ AnalyticsParam.ErrorCode to value.code.toString(),
+ AnalyticsParam.ErrorDescription to "${value.javaClass.simpleName}: ${value.customMessage}",
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt
new file mode 100644
index 0000000000..1f227fc391
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt
@@ -0,0 +1,160 @@
+package com.tangem.tap.common.analytics.converters
+
+import com.tangem.common.Converter
+import com.tangem.common.extensions.isZero
+import com.tangem.core.analytics.Analytics
+import com.tangem.domain.common.ScanResponse
+import com.tangem.tap.common.analytics.events.AnalyticsParam
+import com.tangem.tap.common.analytics.events.Basic
+import com.tangem.tap.common.analytics.filters.BasicTopUpFilter
+import com.tangem.tap.domain.model.WalletDataModel
+import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
+import com.tangem.tap.features.wallet.redux.ProgressState
+import com.tangem.tap.features.wallet.redux.WalletState
+import com.tangem.tap.features.wallet.redux.reducers.calculateTotalCryptoAmount
+import timber.log.Timber
+import java.math.BigDecimal
+
+/**
+[REDACTED_AUTHOR]
+ */
+class BasicEventsPreChecker {
+
+ fun tryToSend(converterData: BasicEventsSourceData) {
+ if (!isReadyToSend(converterData)) return
+
+ BasicSignInEventConverter().convert(converterData)?.let { Analytics.send(it) }
+ BasicTopUpEventConverter().convert(converterData)?.let { Analytics.send(it) }
+ }
+
+ @Suppress("ComplexMethod")
+ private fun isReadyToSend(data: BasicEventsSourceData): Boolean {
+ val (scanResponse, walletState, biometricsWalletDataModels) = data
+ if (walletState.derivationsCheckIsScheduled) {
+ Timber.d("FAILED: derivationsCheckIsScheduled")
+ return false
+ }
+ if (scanResponse.cardTypesResolver.isMultiwalletAllowed() && walletState.missingDerivations.isNotEmpty()) {
+ Timber.d("FAILED: isMultiwalletAllowed || missingDerivations.isNotEmpty")
+ return false
+ }
+
+ if (biometricsWalletDataModels == null) {
+ Timber.d("SWITCH: OLD")
+ val walletsDataFromStores = data.walletState.walletsDataFromStores
+ if (walletsDataFromStores.isEmpty()) {
+ Timber.d("FAILED: walletsDataFromStores.isEmpty")
+ return false
+ }
+
+ val totalBalanceState = data.walletState.totalBalance?.state
+ if (totalBalanceState == null || totalBalanceState == ProgressState.Loading ||
+ totalBalanceState == ProgressState.Refreshing
+ ) {
+ Timber.d("FAILED: totalBalanceState: ${totalBalanceState?.name}")
+ return false
+ }
+
+ val balancesCount = walletsDataFromStores
+ .map { if (it.currencyData.amount == null) 0 else 1 }
+ .reduce { acc, i -> acc + i }
+
+ if (balancesCount != walletsDataFromStores.size) {
+ Timber.d("FAILED: balancesCount != walletsDataFromStores.size")
+ return false
+ }
+ } else {
+ Timber.d("SWITCH: BIOMETRICS")
+ if (biometricsWalletDataModels.isEmpty()) {
+ Timber.d("FAILED: biometricsWalletDataModels.isEmpty")
+ return false
+ }
+
+ val isCorrectStatus = biometricsWalletDataModels.any {
+ it.status is WalletDataModel.Loading ||
+ it.status is WalletDataModel.NoAccount ||
+ it.status is WalletDataModel.Unreachable ||
+ it.status is WalletDataModel.MissedDerivation ||
+ it.status.isErrorStatus
+ }
+ if (isCorrectStatus) {
+ Timber.d("FAILED: by status")
+ return false
+ }
+ }
+
+ Timber.d("SUCCESS")
+ return true
+ }
+}
+
+/**
+ * With biometrics enabled, we should check its storage instead of "WalletState.walletsDataFromStores"
+ * because the latter is updated after some time.
+ * @property biometricsWalletDataModels - wallet data models from the 'WalletStoresManager'. If null, then
+ * the "WalletState.walletsDataFromStores" will be used to determine appropriate state
+ */
+data class BasicEventsSourceData(
+ val scanResponse: ScanResponse,
+ val walletState: WalletState,
+ val biometricsWalletDataModels: List?,
+) {
+ val batchId: String by lazy { scanResponse.card.batchId }
+
+ val userWalletIdStringValue: String? by lazy {
+ UserWalletIdBuilder.scanResponse(scanResponse)
+ .build()
+ ?.stringValue
+ }
+
+ val paramCardCurrency: AnalyticsParam.CardCurrency? by lazy {
+ ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)
+ }
+
+ val paramCardBalanceState: AnalyticsParam.CardBalanceState by lazy { calculateAmount().toCardBalanceState() }
+
+ private fun calculateAmount(): BigDecimal {
+ return biometricsWalletDataModels?.calculateTotalCryptoAmount()
+ ?: walletState.walletsDataFromStores.calculateTotalCryptoAmount()
+ }
+
+ private fun BigDecimal.toCardBalanceState(): AnalyticsParam.CardBalanceState = when {
+ isZero() -> AnalyticsParam.CardBalanceState.Empty
+ else -> AnalyticsParam.CardBalanceState.Full
+ }
+
+ private fun List.calculateTotalCryptoAmount(): BigDecimal {
+ return this
+ .map { it.status.amount }
+ .reduce(BigDecimal::plus)
+ }
+}
+
+class BasicSignInEventConverter : Converter {
+
+ override fun convert(value: BasicEventsSourceData): Basic.SignedIn? {
+ if (value.paramCardCurrency == null || value.userWalletIdStringValue == null) return null
+
+ return Basic.SignedIn(
+ state = value.paramCardBalanceState,
+ currency = value.paramCardCurrency!!,
+ batch = value.batchId,
+ ).apply {
+ filterData = value.userWalletIdStringValue
+ }
+ }
+}
+
+class BasicTopUpEventConverter : Converter {
+
+ override fun convert(value: BasicEventsSourceData): Basic.ToppedUp? {
+ if (value.paramCardCurrency == null || value.userWalletIdStringValue == null) return null
+
+ val data = BasicTopUpFilter.Data(
+ walletId = value.userWalletIdStringValue!!,
+ cardBalanceState = value.paramCardBalanceState,
+ )
+
+ return Basic.ToppedUp(value.paramCardCurrency!!).apply { filterData = data }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt
new file mode 100644
index 0000000000..61d71c5a14
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt
@@ -0,0 +1,28 @@
+package com.tangem.tap.common.analytics.converters
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.common.Converter
+import com.tangem.domain.common.CardTypesResolver
+import com.tangem.domain.common.SaltPayWorkaround
+import com.tangem.tap.common.analytics.events.AnalyticsParam
+
+/**
+[REDACTED_AUTHOR]
+ */
+class ParamCardCurrencyConverter : Converter {
+
+ override fun convert(value: CardTypesResolver): AnalyticsParam.CardCurrency? {
+ if (value.isMultiwalletAllowed()) return AnalyticsParam.CardCurrency.MultiCurrency
+
+ val type = when {
+ value.isTangemNote() -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
+ value.isTangemTwins() -> AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin)
+ value.isSaltPay() -> AnalyticsParam.CurrencyType.Token(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay))
+ value.getBlockchain() != Blockchain.Unknown -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
+ value.getPrimaryToken() != null -> AnalyticsParam.CurrencyType.Token(value.getPrimaryToken()!!)
+ else -> null
+ } ?: return null
+
+ return AnalyticsParam.CardCurrency.SingleCurrency(type)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/SdkErrorConverters.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/SdkErrorConverters.kt
deleted file mode 100644
index 5229df8174..0000000000
--- a/app/src/main/java/com/tangem/tap/common/analytics/converters/SdkErrorConverters.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.tangem.tap.common.analytics.converters
-
-import com.tangem.blockchain.common.BlockchainError
-import com.tangem.blockchain.common.BlockchainSdkError
-import com.tangem.common.Converter
-import com.tangem.common.core.TangemSdkError
-import com.tangem.tap.common.analytics.AnalyticsAnOld
-import com.tangem.tap.common.analytics.AnalyticsParamAnOld
-import com.tangem.tap.common.analytics.TangemSdk
-import com.tangem.tap.features.demo.DemoTransactionSender
-
-/**
-[REDACTED_AUTHOR]
- */
-internal data class ErrorEvent(
- val throwable: Throwable,
- val params: Map,
-)
-
-internal class CardSdkErrorConverter : Converter {
-
- override fun convert(value: Model): ErrorEvent? {
- if (value.error is TangemSdkError.UserCancelled) return null
-
- val mutableParams = value.params.convertToParamValue().toMutableMap().apply {
- putAll(value.cardParams)
- }
- mutableParams[AnalyticsParamAnOld.ACTION.param] = value.action.key
- mutableParams[AnalyticsParamAnOld.ERROR_CODE.param] = value.error.code.toString()
- mutableParams[AnalyticsParamAnOld.ERROR_DESCRIPTION.param] = value.error.javaClass.simpleName
- mutableParams[AnalyticsParamAnOld.ERROR_KEY.param] = "TangemSdkError"
-
- return ErrorEvent(TangemSdk.map(value.error), mutableParams)
- }
-
- internal data class Model(
- val error: TangemSdkError,
- val action: AnalyticsAnOld.ActionToLog,
- val params: Map,
- val cardParams: Map,
- )
-}
-
-internal class BlockchainSdkErrorConverter(
- private val cardSdkErrorConverter: CardSdkErrorConverter,
-) : Converter {
-
- override fun convert(value: Model): ErrorEvent? {
- val error = (value.error as? BlockchainSdkError) ?: return null
-
- if (error is BlockchainSdkError.WrappedTangemError) {
- val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return null
-
- val cardSdkModel = CardSdkErrorConverter.Model(
- error = tangemSdkError,
- action = value.action,
- params = value.params,
- cardParams = value.cardParams,
- )
- return cardSdkErrorConverter.convert(cardSdkModel)
- }
-
- if (error.customMessage.contains(DemoTransactionSender.ID)) return null
-
- val mutableParams = value.params.convertToParamValue().toMutableMap()
- mutableParams[AnalyticsParamAnOld.ACTION.param] = value.action.key
- mutableParams[AnalyticsParamAnOld.ERROR_CODE.param] = error.code.toString()
- mutableParams[AnalyticsParamAnOld.ERROR_DESCRIPTION.param] = "${error.javaClass.simpleName}: ${error.customMessage}"
- mutableParams[AnalyticsParamAnOld.ERROR_KEY.param] = "BlockchainSdkError"
-
- return ErrorEvent(value.error, mutableParams)
- }
-
- data class Model(
- val error: BlockchainError,
- val action: AnalyticsAnOld.ActionToLog,
- val params: Map,
- val cardParams: Map,
- )
-}
-
-internal fun Map.convertToParamValue(): Map {
- return mutableMapOf().also { newMap ->
- this.forEach {
- newMap[it.key.param] = it.value
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt
new file mode 100644
index 0000000000..5d779e661f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt
@@ -0,0 +1,28 @@
+package com.tangem.tap.common.analytics.converters
+
+import com.shopify.buy3.Storefront
+import com.tangem.common.Converter
+import com.tangem.tap.common.analytics.events.Shop
+import com.tangem.tap.common.shop.data.ProductType
+
+/**
+[REDACTED_AUTHOR]
+ */
+class ShopOrderToEventConverter : Converter, Shop.Purchased> {
+
+ override fun convert(value: Pair): Shop.Purchased {
+ val checkout = value.first
+ val productType = value.second
+
+ val sku = checkout.lineItems?.edges?.firstOrNull()?.node?.variant?.sku ?: productType.sku
+ val count = when (productType) {
+ ProductType.WALLET_2_CARDS -> "2"
+ ProductType.WALLET_3_CARDS -> "3"
+ }
+ val amount = "${checkout.totalPriceV2.amount} ${checkout.totalPriceV2.currencyCode.name}"
+ val code = (checkout.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)
+ ?.code
+
+ return Shop.Purchased(sku, count, amount, code)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt
deleted file mode 100644
index 0451fdd68c..0000000000
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.tangem.tap.common.analytics.events
-
-/**
-[REDACTED_AUTHOR]
- */
-sealed class AnalyticsEvent(
- val category: String,
- val event: String,
- val params: Map = mapOf(),
-)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt
index 8c69239429..2e5a925560 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt
@@ -1,22 +1,30 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.tap.features.details.redux.SecurityOption
+
sealed class AnalyticsParam {
sealed class CurrencyType(val value: String) {
class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol)
class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency)
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
+ class FiatCurrency(
+ fiatCurrency: com.tangem.tap.common.entities.FiatCurrency,
+ ) : CurrencyType(fiatCurrency.symbol)
+
+ class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
}
- // Multicurrency or CurrencyType
+ // MultiCurrency or CurrencyType
sealed class CardCurrency(val value: String) {
object MultiCurrency : CardCurrency("Multicurrency")
class SingleCurrency(type: CurrencyType) : CardCurrency(type.value)
}
- sealed class CardState(val value: String) {
- object Empty : CardState("Empty")
- object Full : CardState("Full")
+ sealed class CardBalanceState(val value: String) {
+ object Empty : CardBalanceState("Empty")
+ object Full : CardBalanceState("Full")
+ companion object
}
sealed class RateApp(val value: String) {
@@ -39,20 +47,26 @@ sealed class AnalyticsParam {
object AccessCode : SecurityMode("Access Code")
object Passcode : SecurityMode("Passcode")
object LongTap : SecurityMode("Long Tap")
+
+ companion object {
+ fun from(option: SecurityOption): SecurityMode = when (option) {
+ SecurityOption.AccessCode -> AccessCode
+ SecurityOption.PassCode -> Passcode
+ SecurityOption.LongTap -> LongTap
+ }
+ }
}
- sealed class SocialNetwork(val value: String) {
- object Facebook : SocialNetwork("Facebook")
- object Instagram : SocialNetwork("Instagram")
- object Youtube : SocialNetwork("Youtube")
- object Twitter : SocialNetwork("Twitter")
- object LinkedIn : SocialNetwork("LinkedIn")
- object GitHub : SocialNetwork("GitHub")
+ sealed class Error(val value: String) {
+ object App : Error("App Error")
+ object CardSdk : Error("Card Sdk Error")
+ object BlockchainSdk : Error("Blockchain Sdk Error")
}
companion object Key {
- const val Blockchain = "blockchain"
- const val BatchId = "batch_id"
- const val Firmware = "firmware"
+ const val BatchId = "Batch"
+ const val ErrorDescription = "Error Description"
+ const val ErrorCode = "Error Code"
+ const val ErrorKey = "Error Key"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt
index bb37145672..0d9a529469 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt
@@ -1,15 +1,18 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+
/**
[REDACTED_AUTHOR]
*/
sealed class Basic(
event: String,
params: Map = mapOf(),
-) : AnalyticsEvent("Basic", event, params) {
+ error: Throwable? = null,
+) : AnalyticsEvent("Basic", event, params, error) {
- sealed class SignedIn(
- state: AnalyticsParam.CardState,
+ class SignedIn(
+ state: AnalyticsParam.CardBalanceState,
currency: AnalyticsParam.CardCurrency,
batch: String,
) : Basic(
@@ -17,7 +20,7 @@ sealed class Basic(
params = mapOf(
"State" to state.value,
"Currency" to currency.value,
- "Batch" to batch,
+ AnalyticsParam.BatchId to batch,
),
)
@@ -25,4 +28,9 @@ sealed class Basic(
event = "Topped up",
params = mapOf("Currency" to currency.value),
)
-}
+
+ class ScanError(error: Throwable) : Basic(
+ event = "Scan",
+ error = error,
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt
index a26109ae2a..96fa307105 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt
@@ -1,5 +1,7 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+
/**
[REDACTED_AUTHOR]
*/
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt
index 1ec8d81533..a0d34306c9 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt
@@ -1,5 +1,7 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+
/**
[REDACTED_AUTHOR]
*/
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt
index 4b3eb7f431..0d799d379f 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt
@@ -1,5 +1,7 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+
/**
[REDACTED_AUTHOR]
*/
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt
index c6825718b7..6f718755f0 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt
@@ -1,5 +1,7 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+
/**
[REDACTED_AUTHOR]
*/
@@ -14,12 +16,17 @@ sealed class MainScreen(
class CardWasScanned : MainScreen("Card Was Scanned")
class ButtonMyWallets : MainScreen("Button - My Wallets")
+ class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen(
+ event = "Enable Biometric",
+ params = mapOf("State" to state.value),
+ )
+
class MainCurrencyChanged(currencyType: AnalyticsParam.CurrencyType) : MainScreen(
event = "Main Currency Changed",
params = mapOf("Currency Type" to currencyType.value),
)
- sealed class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen(
+ class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen(
event = "Notice - Rate The App Button Tapped",
params = mapOf("Result" to result.value),
)
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt
index e5e3fdb96a..55ed5f97b9 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt
@@ -1,5 +1,10 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+import com.tangem.domain.common.extensions.toNetworkId
+import com.tangem.domain.features.addCustomToken.CustomCurrency
+import com.tangem.tap.common.extensions.filterNotNull
+
/**
[REDACTED_AUTHOR]
*/
@@ -10,7 +15,18 @@ sealed class ManageTokens(
class ScreenOpened : ManageTokens("Manage Tokens Screen Opened")
class TokenSearched : ManageTokens("Token Searched")
- class TokenSwitcherChanged : ManageTokens("Token Switcher Changed")
+
+ class TokenSwitcherChanged(
+ type: AnalyticsParam.CurrencyType,
+ state: AnalyticsParam.OnOffState,
+ ) : ManageTokens(
+ "Token Switcher Changed",
+ params = mapOf(
+ "Token" to type.value,
+ "State" to state.value,
+ ),
+ )
+
class ButtonSaveChanges : ManageTokens("Button - Save Changes")
class ButtonCustomToken : ManageTokens("Button - Custom Token")
@@ -21,13 +37,26 @@ sealed class ManageTokens(
class ScreenOpened : ManageTokens("Custom Token Screen Opened")
- class TokenWasAdded(symbol: String, network: String, address: String) : ManageTokens(
+ class TokenWasAdded(customCurrency: CustomCurrency) : ManageTokens(
event = "Custom Token Was Added",
- params = mapOf(
- "Symbol" to symbol,
- "Network" to network,
- "Address" to address,
- ),
- )
+ params = convertToParam(customCurrency),
+ ) {
+ companion object {
+ private fun convertToParam(customCurrency: CustomCurrency): Map = with(customCurrency) {
+ return when (this) {
+ is CustomCurrency.CustomBlockchain -> mapOf(
+ "Token" to network.currency,
+ "Derivation Path" to derivationPath?.rawPath,
+ ).filterNotNull()
+ is CustomCurrency.CustomToken -> mapOf(
+ "Token" to token.symbol,
+ "Derivation Path" to derivationPath?.rawPath,
+ "Network Id" to network.toNetworkId(),
+ "Contract Address" to token.contractAddress,
+ ).filterNotNull()
+ }
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt
new file mode 100644
index 0000000000..4db0526ad2
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt
@@ -0,0 +1,20 @@
+package com.tangem.tap.common.analytics.events
+
+import com.tangem.core.analytics.AnalyticsEvent
+
+sealed class MyWallets(
+ event: String,
+ params: Map = mapOf(),
+) : AnalyticsEvent("My Wallets", event, params) {
+
+ class MyWalletsScreenOpened : MyWallets(event = "My Wallets Screen Opened")
+ class CardWasScanned : MyWallets(event = "Card Was Scanned")
+
+ sealed class Button {
+ class ScanNewCard : MyWallets(event = "Button - Scan New Card")
+ class UnlockWithBiometrics : MyWallets(event = "Button - Unlock all with Face ID")
+ class EditWalletTapped : MyWallets(event = "Button - Edit Wallet Tapped")
+ class DeleteWalletTapped : MyWallets(event = "Button - Delete Wallet Tapped")
+ class WalletUnlockTapped : MyWallets(event = "Button - Wallet Unlock Tapped")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt
index b65408b7d5..4ec37c4806 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt
@@ -1,5 +1,7 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+
/**
[REDACTED_AUTHOR]
*/
@@ -34,9 +36,9 @@ sealed class Onboarding(
class AccessCodeEntered : Backup("Access Code Entered")
class AccessCodeReEntered : Backup("Access Code Re-entered")
- class Finished(cardsCount: String) : Backup(
+ class Finished(cardsCount: Int) : Backup(
event = "Backup Finished",
- params = mapOf("Cards count" to cardsCount),
+ params = mapOf("Cards count" to "$cardsCount"),
)
}
@@ -65,12 +67,19 @@ sealed class Onboarding(
class SetupFinished : Twins("Twin Setup Finished")
}
- class PinCodeSet: Onboarding("Onboarding", "PIN code set")
- class ButtonConnect: Onboarding("Onboarding", "Button - Connect")
- class KYCStarted: Onboarding("Onboarding", "KYC started")
- class KYCInProgress: Onboarding("Onboarding", "KYC in progress")
- class KYCRejected: Onboarding("Onboarding", "KYC rejected")
- class ClaimScreenOpened: Onboarding("Onboarding", "Claim screen opened")
- class ButtonClaim: Onboarding("Onboarding", "Button - Claim")
- class ClaimWasSuccessfully: Onboarding("Onboarding", "Claim was successfully")
+ class PinCodeSet : Onboarding("Onboarding", "PIN code set")
+ class ButtonConnect : Onboarding("Onboarding", "Button - Connect")
+ class KYCStarted : Onboarding("Onboarding", "KYC started")
+ class KYCInProgress : Onboarding("Onboarding", "KYC in progress")
+ class KYCRejected : Onboarding("Onboarding", "KYC rejected")
+ class ClaimScreenOpened : Onboarding("Onboarding", "Claim screen opened")
+ class ButtonClaim : Onboarding("Onboarding", "Button - Claim")
+ class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim was successfully")
+ class ButtonChat : Onboarding("Onboarding", "Button - Chat")
+
+ class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding(
+ category = "Onboarding / Biometric",
+ event = "Enable Biometric",
+ params = mapOf("State" to state.value),
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt
index 6747c2172d..71383c9f9a 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt
@@ -1,5 +1,7 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+
/**
[REDACTED_AUTHOR]
*/
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt
index 56893d2f31..03cbb88ac6 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt
@@ -1,5 +1,8 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+import com.tangem.tap.features.details.ui.details.SocialNetwork
+
/**
[REDACTED_AUTHOR]
*/
@@ -7,7 +10,8 @@ sealed class Settings(
category: String = "Settings",
event: String,
params: Map = mapOf(),
-) : AnalyticsEvent(category, event, params) {
+ error: Throwable? = null,
+) : AnalyticsEvent(category, event, params, error) {
class ScreenOpened : Settings(event = "Settings Screen Opened")
class ButtonChat : Settings(event = "Button - Chat")
@@ -17,30 +21,37 @@ sealed class Settings(
class ButtonCardSettings : Settings(event = "Button - Card Settings")
class ButtonAppSettings : Settings(event = "Button - App Settings")
class ButtonCreateBackup : Settings(event = "Button - Create Backup")
+ class ButtonWalletConnect : Settings(event = "Button - Wallet Connect")
- sealed class ButtonSocialNetwork(network: AnalyticsParam.SocialNetwork) : Settings(
+ class ButtonSocialNetwork(network: SocialNetwork) : Settings(
event = "Button - Social Network",
- params = mapOf("Network" to network.value),
+ params = mapOf("Network" to network.id),
)
sealed class CardSettings(
event: String,
params: Map = mapOf(),
- ) : Settings("Settings / Card Settings", event, params) {
+ error: Throwable? = null,
+ ) : Settings("Settings / Card Settings", event, params, error) {
class ButtonFactoryReset : CardSettings("Button - Factory Reset")
- class FactoryResetFinished : CardSettings("Factory Reset Finished")
+ class FactoryResetFinished(error: Throwable? = null) : CardSettings(
+ event = "Factory Reset Finished",
+ error = error,
+ )
+
class UserCodeChanged : CardSettings("User Code Changed")
class ButtonChangeSecurityMode : CardSettings("Button - Change Security Mode")
- sealed class ButtonChangeUserCode(type: AnalyticsParam.UserCode) : CardSettings(
+ class ButtonChangeUserCode(type: AnalyticsParam.UserCode) : CardSettings(
event = "Button - Change User Code",
params = mapOf("Type" to type.value),
)
- sealed class SecurityModeChanged(mode: AnalyticsParam.SecurityMode) : CardSettings(
+ class SecurityModeChanged(mode: AnalyticsParam.SecurityMode, error: Throwable? = null) : CardSettings(
event = "Security Mode Changed",
params = mapOf("Mode" to mode.value),
+ error = error,
)
}
@@ -49,16 +60,16 @@ sealed class Settings(
params: Map = mapOf(),
) : Settings("Settings / App Settings", event, params) {
- sealed class FaceIDSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings(
- event = "Face ID Switcher Changed",
+ class SaveWalletSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings(
+ event = "Save Wallet Switcher Changed",
params = mapOf("State" to state.value),
)
- sealed class SaveAccessCodeSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings(
+ class SaveAccessCodeSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings(
event = "Save Access Code Switcher Changed",
params = mapOf("State" to state.value),
)
- class ButtonEnableBiometricAuthentication : AppSettings("Button - Enable Biometric Authentication")
+ object ButtonEnableBiometricAuthentication : AppSettings("Button - Enable Biometric Authentication")
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt
index c09bf89ab3..0ab4dd9361 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt
@@ -1,5 +1,8 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+import com.tangem.tap.common.extensions.filterNotNull
+
/**
[REDACTED_AUTHOR]
*/
@@ -8,15 +11,20 @@ sealed class Shop(
params: Map = mapOf(),
) : AnalyticsEvent("Shop", event, params) {
- class ScreenOpened : IntroductionProcess("Shop Screen Opened")
+ class ScreenOpened : Shop("Shop Screen Opened")
- class Purchased(productSku: String) : Shop(
+ class Purchased(sku: String, count: String, amount: String, couponCode: String?) : Shop(
event = "Purchased",
- params = mapOf("SKU" to productSku),
+ params = mapOf(
+ "SKU" to sku,
+ "Count" to count,
+ "Amount" to amount,
+ "Coupon Code" to couponCode,
+ ).filterNotNull(),
)
- class Redirected(partnerName: String) : Shop(
+ class Redirected(partnerName: String?) : Shop(
event = "Redirected",
- params = mapOf("Partner" to partnerName),
+ params = partnerName?.let { mapOf("Partner" to partnerName) } ?: mapOf(),
)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt
new file mode 100644
index 0000000000..cad34430d9
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt
@@ -0,0 +1,19 @@
+package com.tangem.tap.common.analytics.events
+
+import com.tangem.core.analytics.AnalyticsEvent
+
+/**
+[REDACTED_AUTHOR]
+ */
+sealed class SignIn(
+ event: String,
+ params: Map = mapOf(),
+ error: Throwable? = null,
+) : AnalyticsEvent("Sign In", event, params, error) {
+
+ class ScreenOpened : SignIn(event = "Sing In Screen Opened")
+ class CardWasScanned : SignIn(event = "Card Was Scanned")
+
+ class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
+ class ButtonCardSignIn : SignIn(event = "Button - Card Sign In")
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt
index afe5da529a..0ad3ca514e 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt
@@ -1,5 +1,8 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+import com.tangem.tap.common.analytics.events.AnalyticsParam.CurrencyType
+
/**
[REDACTED_AUTHOR]
*/
@@ -7,39 +10,67 @@ sealed class Token(
category: String,
event: String,
params: Map = mapOf(),
-) : AnalyticsEvent(category, event, params) {
+ error: Throwable? = null,
+) : AnalyticsEvent(category, event, params, error) {
class Refreshed : Token("Token", "Refreshed")
- class ButtonRemoveToken : Token("Token", "Button - Remove Token")
class ButtonExplore : Token("Token", "Button - Explore")
- class ButtonBuy(token: String) : Token("Token", "Button - Buy", mapOf("Token" to token))
- class ButtonSell(token: String) : Token("Token", "Button - Sell", mapOf("Token" to token))
- class ButtonExchange(token: String) : Token("Token", "Button - Exchange", mapOf("Token" to token))
- class ButtonSend(token: String) : Token("Token", "Button - Send", mapOf("Token" to token))
- sealed class Recieve(
+ class ButtonRemoveToken(type: CurrencyType) : Token(
+ "Token",
+ "Button - Remove Token",
+ params = mapOf("Token" to type.value),
+ )
+
+ class ButtonBuy(type: CurrencyType) : Token(
+ category = "Token",
+ event = "Button - Buy",
+ params = mapOf("Token" to type.value),
+ )
+
+ class ButtonSell(type: CurrencyType) : Token(
+ category = "Token",
+ event = "Button - Sell",
+ params = mapOf("Token" to type.value),
+ )
+
+ class ButtonExchange(type: CurrencyType) : Token(
+ category = "Token",
+ event = "Button - Exchange",
+ params = mapOf("Token" to type.value),
+ )
+
+ class ButtonSend(type: CurrencyType) : Token(
+ category = "Token",
+ event = "Button - Send",
+ params = mapOf("Token" to type.value),
+ )
+
+ sealed class Receive(
event: String,
params: Map = mapOf(),
- ) : Token("Token / Recieve", event, params) {
+ ) : Token("Token / Receive", event, params) {
- class ScreenOpened : Recieve("Recieve Screen Opened")
- class ButtonCopyAddress : Recieve("Button - Copy Address")
- class ButtonShareAddress : Recieve("Button - Share Address")
+ class ScreenOpened : Receive("Receive Screen Opened")
+ class ButtonCopyAddress : Receive("Button - Copy Address")
+ class ButtonShareAddress : Receive("Button - Share Address")
}
sealed class Send(
event: String,
params: Map = mapOf(),
- ) : Token("Token / Send", event, params) {
+ error: Throwable? = null,
+ ) : Token("Token / Send", event, params, error) {
class ScreenOpened : Send("Send Screen Opened")
class ButtonPaste : Send("Button - Paste")
class ButtonQRCode : Send("Button - QR Code")
class ButtonSwapCurrency : Send("Button - Swap Currency")
- class TransactionSent(token: AnalyticsParam.CurrencyType) : Send(
+ class TransactionSent(type: CurrencyType, error: Throwable? = null) : Send(
event = "Transaction Sent",
- params = mapOf("Token" to token.value),
+ params = mapOf("Token" to type.value),
+ error = error,
)
}
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt
index 624145c63d..9ce10213ac 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt
@@ -1,14 +1,21 @@
package com.tangem.tap.common.analytics.events
+import com.tangem.core.analytics.AnalyticsEvent
+
/**
[REDACTED_AUTHOR]
*/
sealed class WalletConnect(
event: String,
params: Map = mapOf(),
-) : AnalyticsEvent("Wallet Connect", event, params) {
+ error: Throwable? = null,
+) : AnalyticsEvent("Wallet Connect", event, params, error) {
+ class ScreenOpened : WalletConnect(event = "WC Screen Opened")
class NewSessionEstablished : WalletConnect("New Session Established")
class SessionDisconnected : WalletConnect("Session Disconnected")
class RequestSigned : WalletConnect("Request Signed")
+
+ class SignError(error: Throwable) : WalletConnect("Sign", error = error)
+ class TransactionError(error: Throwable) : WalletConnect("Transaction", error = error)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt
new file mode 100644
index 0000000000..09c26d8d7d
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt
@@ -0,0 +1,27 @@
+package com.tangem.tap.common.analytics.filters
+
+import com.tangem.core.analytics.api.AnalyticsEventFilter
+import com.tangem.core.analytics.api.AnalyticsHandler
+import com.tangem.core.analytics.AnalyticsEvent
+import com.tangem.tap.common.analytics.events.Basic
+
+/**
+[REDACTED_AUTHOR]
+ */
+class BasicSignInFilter : AnalyticsEventFilter {
+
+ private val alreadySignedIn = mutableSetOf()
+
+ override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is Basic.SignedIn
+
+ override fun canBeSent(event: AnalyticsEvent): Boolean {
+ val userWalletId = event.filterData as? String ?: return false
+
+ val canBeSent = !alreadySignedIn.contains(userWalletId)
+ alreadySignedIn.add(userWalletId)
+
+ return canBeSent
+ }
+
+ override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean = true
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt
new file mode 100644
index 0000000000..de89bf9e7e
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt
@@ -0,0 +1,50 @@
+package com.tangem.tap.common.analytics.filters
+
+import com.tangem.common.extensions.guard
+import com.tangem.core.analytics.api.AnalyticsEventFilter
+import com.tangem.core.analytics.api.AnalyticsHandler
+import com.tangem.core.analytics.AnalyticsEvent
+import com.tangem.tap.common.analytics.events.AnalyticsParam
+import com.tangem.tap.common.analytics.events.Basic
+import com.tangem.tap.persistence.ToppedUpWalletStorage
+
+/**
+[REDACTED_AUTHOR]
+ */
+class BasicTopUpFilter(
+ private val topupWalletStorage: ToppedUpWalletStorage,
+) : AnalyticsEventFilter {
+
+ override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is Basic.ToppedUp
+
+ override fun canBeSent(event: AnalyticsEvent): Boolean {
+ val data = event.filterData as? Data ?: return false
+
+ val walletInfo = topupWalletStorage.restore(data.walletId).guard {
+ val newWalletInfo = Data(
+ walletId = data.walletId,
+ cardBalanceState = data.cardBalanceState,
+ )
+ topupWalletStorage.save(newWalletInfo)
+ return false
+ }
+
+ if (walletInfo.isToppedUp) return false
+
+ return if (!walletInfo.isToppedUp && data.isToppedUp) {
+ topupWalletStorage.save(walletInfo.copy(cardBalanceState = AnalyticsParam.CardBalanceState.Full))
+ true
+ } else {
+ false
+ }
+ }
+
+ override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean = true
+
+ data class Data(
+ val walletId: String,
+ val cardBalanceState: AnalyticsParam.CardBalanceState,
+ ) {
+ val isToppedUp: Boolean = cardBalanceState == AnalyticsParam.CardBalanceState.Full
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt
index 27132a1f61..a1daeb7ccf 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt
@@ -1,12 +1,27 @@
package com.tangem.tap.common.analytics.handlers.amplitude
-import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
+import com.tangem.core.analytics.api.AnalyticsHandler
+import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
class AmplitudeAnalyticsHandler(
private val client: AmplitudeAnalyticsClient,
-) : AnalyticsEventHandler {
+) : AnalyticsHandler {
- override fun handleEvent(event: String, params: Map) {
+ override fun id(): String = ID
+
+ override fun send(event: String, params: Map) {
client.logEvent(event, params)
}
+
+ companion object {
+ const val ID = "Amplitude"
+ }
+
+ class Builder : AnalyticsHandlerBuilder {
+ override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
+ !data.isDebug -> AmplitudeClient(data.application, data.config.amplitudeApiKey)
+ data.isDebug && data.logConfig.amplitude -> AmplitudeLogClient(data.jsonConverter)
+ else -> null
+ }?.let { AmplitudeAnalyticsHandler(it) }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt
index d90ed084b5..ddb27d18ff 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeClient.kt
@@ -4,7 +4,7 @@ import android.app.Application
import com.amplitude.api.Amplitude
import com.amplitude.api.AmplitudeClient
import com.tangem.common.Converter
-import com.tangem.tap.common.analytics.api.EventLogger
+import com.tangem.core.analytics.api.EventLogger
import org.json.JSONObject
/**
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt
index b800259b5f..f60d2edfcf 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt
@@ -1,14 +1,17 @@
package com.tangem.tap.common.analytics.handlers.amplitude
+import com.tangem.common.json.MoshiJsonConverter
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
/**
[REDACTED_AUTHOR]
*/
internal class AmplitudeLogClient(
- private val logger: AnalyticsEventsLogger,
+ jsonConverter: MoshiJsonConverter,
) : AmplitudeAnalyticsClient {
+ private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(AmplitudeAnalyticsHandler.ID, jsonConverter)
+
override fun logEvent(event: String, params: Map) {
logger.logEvent(event, params)
}
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt
index 0acfb1dc2f..d1b3189ab1 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt
@@ -1,47 +1,38 @@
package com.tangem.tap.common.analytics.handlers.appsFlyer
-import com.appsflyer.AFInAppEventParameterName
import com.appsflyer.AFInAppEventType
-import com.shopify.buy3.Storefront
-import com.tangem.common.Converter
-import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
-import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
+import com.tangem.core.analytics.AnalyticsEvent
+import com.tangem.core.analytics.api.AnalyticsHandler
+import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
+import com.tangem.tap.common.analytics.events.Shop
class AppsFlyerAnalyticsHandler(
private val client: AppsFlyerAnalyticsClient,
-) : AnalyticsEventHandler, ShopifyOrderEventHandler {
+) : AnalyticsHandler {
- override fun handleEvent(event: String, params: Map) {
+ override fun id(): String = ID
+
+ override fun send(event: String, params: Map) {
client.logEvent(event, params)
}
- override fun handleShopifyOrderEvent(order: Storefront.Order) {
- handleEvent(ORDER_EVENT, OrderToParamsConverter().convert(order))
+ override fun send(event: AnalyticsEvent) {
+ if (event is Shop.Purchased) {
+ send(AFInAppEventType.PURCHASE, event.params)
+ } else {
+ super.send(event)
+ }
}
companion object {
- const val ORDER_EVENT = AFInAppEventType.PURCHASE
+ const val ID = "AppsFlyer"
}
-}
-private class OrderToParamsConverter : Converter> {
-
- override fun convert(value: Storefront.Order): Map {
- val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
-
- val discountCode =
- (value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
-
- val discountParams = if (discountCode != null) {
- mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode)
- } else {
- mapOf()
- }
-
- return mapOf(
- AFInAppEventParameterName.CONTENT_ID to sku,
- AFInAppEventParameterName.REVENUE to value.totalPriceV2.amount,
- AFInAppEventParameterName.CURRENCY to value.currencyCode.name,
- ) + discountParams
+ class Builder : AnalyticsHandlerBuilder {
+ override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
+ !data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerDevKey)
+ data.isDebug && data.logConfig.appsFlyer -> AppsFlyerLogClient(data.jsonConverter)
+ else -> null
+ }?.let { AppsFlyerAnalyticsHandler(it) }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt
index 6a7aa7cfed..40cee4e698 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerClient.kt
@@ -2,7 +2,7 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer
import android.content.Context
import com.appsflyer.AppsFlyerLib
-import com.tangem.tap.common.analytics.api.EventLogger
+import com.tangem.core.analytics.api.EventLogger
/**
[REDACTED_AUTHOR]
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerLogClient.kt
index 0d3737a161..8e87061b2c 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerLogClient.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerLogClient.kt
@@ -1,14 +1,17 @@
package com.tangem.tap.common.analytics.handlers.appsFlyer
+import com.tangem.common.json.MoshiJsonConverter
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
/**
[REDACTED_AUTHOR]
*/
internal class AppsFlyerLogClient(
- private val logger: AnalyticsEventsLogger,
+ jsonConverter: MoshiJsonConverter,
) : AppsFlyerAnalyticsClient {
+ private val logger = AnalyticsEventsLogger(AppsFlyerAnalyticsHandler.ID, jsonConverter)
+
override fun logEvent(event: String, params: Map) {
logger.logEvent(event, params)
}
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt
index 3ab6326670..7430fcc17f 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt
@@ -1,81 +1,58 @@
package com.tangem.tap.common.analytics.handlers.firebase
import com.google.firebase.analytics.FirebaseAnalytics
-import com.shopify.buy3.Storefront
-import com.tangem.blockchain.common.BlockchainError
-import com.tangem.common.Converter
-import com.tangem.common.card.Card
-import com.tangem.common.core.TangemSdkError
-import com.tangem.tap.common.analytics.AnalyticsAnOld
-import com.tangem.tap.common.analytics.AnalyticsParamAnOld
-import com.tangem.tap.common.analytics.api.AnalyticsEventHandler
-import com.tangem.tap.common.analytics.api.ErrorEventHandler
-import com.tangem.tap.common.analytics.api.SdkErrorEventHandler
-import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler
-import com.tangem.tap.common.analytics.converters.BlockchainSdkErrorConverter
-import com.tangem.tap.common.analytics.converters.CardSdkErrorConverter
+import com.tangem.core.analytics.AnalyticsEvent
+import com.tangem.core.analytics.api.AnalyticsHandler
+import com.tangem.core.analytics.api.ErrorEventHandler
+import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
+import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter
+import com.tangem.tap.common.analytics.events.Shop
class FirebaseAnalyticsHandler(
private val client: FirebaseAnalyticsClient,
-) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler, ShopifyOrderEventHandler {
+) : AnalyticsHandler, ErrorEventHandler {
- override fun handleEvent(event: String, params: Map) {
+ override fun id(): String = ID
+
+ override fun send(event: String, params: Map) {
client.logEvent(event, params)
}
- override fun handleErrorEvent(error: Throwable, params: Map) {
+ override fun send(event: AnalyticsEvent) {
+ val error = event.error
+ when {
+ error != null -> {
+ val errorConverter = AnalyticsErrorConverter()
+ if (!errorConverter.canBeHandled(error)) return
+
+ val errorParams = errorConverter.convert(error).toMutableMap()
+ errorParams["Category"] = event.category
+ errorParams["Event"] = event.event
+ errorParams.putAll(event.params)
+ send(error, errorParams)
+ }
+ event is Shop.Purchased -> {
+ send(FirebaseAnalytics.Event.PURCHASE, event.params)
+ }
+ else -> {
+ super.send(event)
+ }
+ }
+ }
+
+ override fun send(error: Throwable, params: Map) {
client.logErrorEvent(error, params)
}
- override fun handleCardSdkErrorEvent(
- error: TangemSdkError,
- action: AnalyticsAnOld.ActionToLog,
- params: Map,
- card: Card?,
- ) {
- val model = CardSdkErrorConverter.Model(error, action, params, prepareParams(card))
- val converter = CardSdkErrorConverter()
- converter.convert(model)?.let {
- handleErrorEvent(it.throwable, it.params)
- }
+ companion object {
+ const val ID = "Firebase"
}
- override fun handleBlockchainSdkErrorEvent(
- error: BlockchainError,
- action: AnalyticsAnOld.ActionToLog,
- params: Map,
- card: Card?,
- ) {
-
- val model = BlockchainSdkErrorConverter.Model(error, action, params, prepareParams(card))
- val converter = BlockchainSdkErrorConverter(CardSdkErrorConverter())
- converter.convert(model)?.let {
- handleErrorEvent(it.throwable, it.params)
- }
- }
-
- override fun handleShopifyOrderEvent(order: Storefront.Order) {
- handleEvent(FirebaseClient.ORDER_EVENT, OrderToParamsConverter().convert(order))
- }
-}
-
-private class OrderToParamsConverter : Converter> {
- override fun convert(value: Storefront.Order): Map {
- val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown"
-
- val discountCode =
- (value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
-
- val discountParams = if (discountCode != null) {
- mapOf(FirebaseAnalytics.Param.DISCOUNT to discountCode)
- } else {
- mapOf()
- }
-
- return mapOf(
- FirebaseAnalytics.Param.ITEM_ID to sku,
- FirebaseAnalytics.Param.VALUE to value.totalPriceV2.amount,
- FirebaseAnalytics.Param.CURRENCY to value.currencyCode.name,
- ) + discountParams
+ class Builder : AnalyticsHandlerBuilder {
+ override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when {
+ !data.isDebug -> FirebaseClient()
+ data.isDebug && data.logConfig.firebase -> FirebaseLogClient(data.jsonConverter)
+ else -> null
+ }?.let { FirebaseAnalyticsHandler(it) }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt
index 07ed4c4551..33751b5e0a 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt
@@ -6,8 +6,8 @@ import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
-import com.tangem.tap.common.analytics.api.ErrorEventLogger
-import com.tangem.tap.common.analytics.api.EventLogger
+import com.tangem.core.analytics.api.ErrorEventLogger
+import com.tangem.core.analytics.api.EventLogger
/**
[REDACTED_AUTHOR]
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt
index ccacc09f9e..e460f9dbf9 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt
@@ -1,14 +1,17 @@
package com.tangem.tap.common.analytics.handlers.firebase
+import com.tangem.common.json.MoshiJsonConverter
import com.tangem.tap.common.analytics.AnalyticsEventsLogger
/**
[REDACTED_AUTHOR]
*/
internal class FirebaseLogClient(
- private val logger: AnalyticsEventsLogger,
+ jsonConverter: MoshiJsonConverter,
) : FirebaseAnalyticsClient {
+ private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(FirebaseAnalyticsHandler.ID, jsonConverter)
+
override fun logEvent(event: String, params: Map) {
logger.logEvent(event, params)
}
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt
new file mode 100644
index 0000000000..b7e7ec6fbf
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt
@@ -0,0 +1,21 @@
+package com.tangem.tap.common.analytics.paramsInterceptor
+
+import com.tangem.core.analytics.api.ParamsInterceptor
+import com.tangem.core.analytics.AnalyticsEvent
+import com.tangem.tap.common.analytics.events.AnalyticsParam
+
+/**
+[REDACTED_AUTHOR]
+ */
+class BatchIdParamsInterceptor(
+ val batchId: String,
+) : ParamsInterceptor {
+
+ override fun id(): String = this::class.java.simpleName
+
+ override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true
+
+ override fun intercept(params: MutableMap) {
+ params[AnalyticsParam.BatchId] = batchId
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt b/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt
index 324fd3877e..63d98b6269 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/AutoSizeText.kt
@@ -11,12 +11,13 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
+@Suppress("MagicNumber")
@Composable
fun TextAutoSize(
- modifier: Modifier = Modifier,
text: String,
- textStyle: TextStyle = LocalTextStyle.current,
fontSizeRange: FontSizeRange,
+ modifier: Modifier = Modifier,
+ textStyle: TextStyle = LocalTextStyle.current,
) {
val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) }
val readyToDraw = remember { mutableStateOf(false) }
@@ -46,7 +47,7 @@ fun TextAutoSize(
} else {
readyToDraw.value = true
}
- }
+ },
)
}
diff --git a/app/src/main/java/com/tangem/tap/common/compose/BlockchainSpinner.kt b/app/src/main/java/com/tangem/tap/common/compose/BlockchainSpinner.kt
index 3df228a032..215fb7b312 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/BlockchainSpinner.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/BlockchainSpinner.kt
@@ -17,7 +17,7 @@ fun BlockchainSpinner(
textFieldConverter: (Blockchain) -> String,
dropdownItemView: @Composable ((Blockchain) -> Unit)? = null,
closePopupTrigger: ClosePopupTrigger = ClosePopupTrigger(),
- onItemSelected: (Blockchain) -> Unit,
+ onItemSelect: (Blockchain) -> Unit,
) {
OutlinedSpinner(
modifier = Modifier.fillMaxWidth(),
@@ -27,7 +27,7 @@ fun BlockchainSpinner(
textFieldConverter = textFieldConverter,
dropdownItemView = dropdownItemView,
isEnabled = isEnabled,
- onItemSelected = onItemSelected,
+ onItemSelected = onItemSelect,
closePopupTrigger = closePopupTrigger,
)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/Button.kt b/app/src/main/java/com/tangem/tap/common/compose/Button.kt
index 690f35f388..c536af17bd 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/Button.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/Button.kt
@@ -1,126 +1,8 @@
package com.tangem.tap.common.compose
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.*
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
-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.res.painterResource
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.DpSize
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import com.tangem.tap.common.compose.extensions.stringResourceDefault
-import com.tangem.wallet.R
-
-/**
-[REDACTED_AUTHOR]
- */
-@Composable
-fun RectangleButton(
- modifier: Modifier = Modifier,
- text: String = "",
- textId: Int? = null,
- isEnabled: Boolean = true,
- contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
- leadingView: @Composable RowScope.() -> Unit = {},
- middleView: @Composable RowScope.() -> Unit = { TextInButton(text = text, textId = textId) },
- trailingView: @Composable RowScope.() -> Unit = {},
- onClick: () -> Unit,
-) {
- Button(
- modifier = modifier,
- contentPadding = contentPadding,
- enabled = isEnabled,
- onClick = onClick,
- ) {
- leadingView()
- middleView()
- trailingView()
- }
-}
-
-@Composable
-private fun TextInButton(
- modifier: Modifier = Modifier,
- text: String = "",
- textId: Int? = null,
-) {
- Text(
- modifier = modifier,
- text = stringResourceDefault(textId, text),
- maxLines = 1,
- style = TextStyle(
- fontSize = 16.sp,
- lineHeight = 20.sp,
- fontWeight = FontWeight.Medium,
- )
- )
-}
-
-@Composable
-fun PasteButton(
- modifier: Modifier = Modifier,
- enabled: Boolean = true,
- dpSize: DpSize = DpSize(40.dp, 40.dp),
- onClick: () -> Unit,
- tint: Color? = null,
- content: @Composable (() -> Unit)? = null
-) {
- IconButton(
- modifier = modifier.size(dpSize),
- enabled = enabled,
- onClick = onClick,
- ) {
- when (content) {
- null -> {
- val tintColor = tint
- ?: colorResource(id = if (enabled) R.color.accent else R.color.accent_disabled)
- Icon(
- painterResource(id = R.drawable.ic_paste),
- contentDescription = "Paste",
- tint = tintColor,
- )
- }
- else -> content()
- }
- }
-}
-
-@Composable
-fun ClearButton(
- modifier: Modifier = Modifier,
- enabled: Boolean = true,
- dpSize: DpSize = DpSize(40.dp, 40.dp),
- onClick: () -> Unit,
- tint: Color? = null,
- content: @Composable (() -> Unit)? = null
-) {
- IconButton(
- modifier = modifier.size(dpSize),
- enabled = enabled,
- onClick = onClick,
- ) {
- when (content) {
- null -> {
- val tintColor = tint
- ?: colorResource(id = if (enabled) R.color.accent else R.color.accent_disabled)
- Icon(
- painterResource(id = R.drawable.ic_clear),
- contentDescription = "Clear",
- tint = tintColor,
- )
- }
- else -> content()
- }
- }
-}
/**
* Used for disable ripple if button is enable = false
@@ -132,35 +14,4 @@ fun ToggledRippleTheme(
) {
val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme()
CompositionLocalProvider(theme) { content() }
-}
-
-@Preview
-@Composable
-fun ButtonTest() {
- Scaffold(
- ) {
- Column(modifier = Modifier.padding(16.dp)) {
- PreviewItem("Button") {
- RectangleButton(text = "Some button") {}
- }
- PreviewItem("PasteButton") {
- PasteButton(onClick = {})
- }
- }
- }
-}
-
-@Composable
-fun PreviewItem(
- name: String,
- content: @Composable RowScope.() -> Unit,
-) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Text(
- modifier = Modifier.weight(1f),
- text = name,
- )
- content()
- }
- SpacerH8()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/ComposableValueDebouncer.kt b/app/src/main/java/com/tangem/tap/common/compose/ComposableValueDebouncer.kt
index b3251f3cac..44395f9279 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/ComposableValueDebouncer.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/ComposableValueDebouncer.kt
@@ -11,19 +11,19 @@ import com.tangem.domain.common.util.ValueDebouncer
@Composable
fun valueDebouncerAsState(
initialValue: T,
+ onValueChange: (T) -> Unit,
debounce: Long = 600,
- onEmitValueReceived: (T) -> Unit = {},
- onValueChanged: (T) -> Unit,
+ onEmitValueReceive: (T) -> Unit = {},
): ValueDebouncer {
return remember {
- ValueDebouncer(
+ ValueDebouncer(
initialValue = initialValue,
debounceDuration = debounce,
onEmitValueReceived = { emitValue ->
- emitValue?.let { onEmitValueReceived(it) }
+ emitValue?.let { onEmitValueReceive(it) }
},
onValueChanged = { changedValue ->
- changedValue?.let { onValueChanged(it) }
+ changedValue?.let { onValueChange(it) }
},
)
}
@@ -32,16 +32,16 @@ fun valueDebouncerAsState(
@Composable
fun valueDebouncerNullableAsState(
initialValue: T?,
+ onValueChange: (T?) -> Unit,
debounce: Long = 400,
- onEmitValueReceived: (T?) -> Unit = {},
- onValueChanged: (T?) -> Unit,
+ onEmitValueReceive: (T?) -> Unit = {},
): ValueDebouncer {
return remember {
ValueDebouncer(
initialValue = initialValue,
debounceDuration = debounce,
- onEmitValueReceived = onEmitValueReceived,
- onValueChanged = onValueChanged,
+ onEmitValueReceived = onEmitValueReceive,
+ onValueChanged = onValueChange,
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt b/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt
index fc496f1d9b..8d0de7df69 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt
@@ -30,6 +30,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
+import com.tangem.core.ui.components.SpacerH16
import com.tangem.domain.DomainDialog
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
@@ -79,6 +80,7 @@ private fun ShowTheDialog(dialogState: MutableState) {
onDismissRequest
)
is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest)
+ else -> {}
}
}
@@ -105,7 +107,7 @@ fun SimpleDialog(
modifier = Modifier.padding(22.dp)
) {
DialogTitle(title = title)
- LazyColumn() {
+ LazyColumn {
items(items) { item ->
Row(
modifier = Modifier
@@ -154,4 +156,4 @@ fun ErrorDialog(
}
}
)
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/ErrorViews.kt b/app/src/main/java/com/tangem/tap/common/compose/ErrorViews.kt
index 49b6625474..c8d0701b2e 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/ErrorViews.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/ErrorViews.kt
@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -19,23 +18,20 @@ import androidx.compose.ui.unit.dp
fun ErrorView(
text: String,
modifier: Modifier = Modifier,
- style: TextStyle = LocalTextStyle.current
+ style: TextStyle = LocalTextStyle.current,
) {
Text(
text,
color = MaterialTheme.colors.error,
modifier = modifier,
- style = style
+ style = style,
)
}
@Preview
@Composable
-fun ErrorViewTest() {
- Scaffold(
- ) {
- Box(Modifier.padding(16.dp)) {
- ErrorView(text = "Some error description")
- }
+private fun ErrorViewTest() {
+ Box(Modifier.padding(16.dp)) {
+ ErrorView(text = "Some error description")
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/Theme.kt b/app/src/main/java/com/tangem/tap/common/compose/NoRippleTheme.kt
similarity index 100%
rename from app/src/main/java/com/tangem/tap/common/compose/Theme.kt
rename to app/src/main/java/com/tangem/tap/common/compose/NoRippleTheme.kt
diff --git a/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt b/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt
index 3792119da9..981e5e00e6 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/OutlinedSpinner.kt
@@ -17,14 +17,15 @@ import com.tangem.tap.common.extensions.ValueCallback
/**
[REDACTED_AUTHOR]
*/
+@Suppress("MagicNumber")
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun OutlinedSpinner(
- modifier: Modifier = Modifier,
label: String,
itemList: List,
selectedItem: Field.Data,
onItemSelected: ValueCallback,
+ modifier: Modifier = Modifier,
textFieldConverter: (T) -> String = { it.toString() },
dropdownItemView: @Composable ((T) -> Unit)? = null,
isEnabled: Boolean = true,
@@ -66,6 +67,7 @@ fun OutlinedSpinner(
onValueChange = {},
label = { Text(label) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = rIsExpanded.value) },
+ colors = TangemTextFieldsDefault.defaultTextFieldColors,
)
if (isEnabled) {
@@ -94,13 +96,11 @@ class ClosePopupTrigger {
@Preview
@Composable
-fun TestSpinnerPreview() {
- Scaffold() {
- OutlinedSpinner(
- label = "Blockchain name",
- itemList = listOf(Blockchain.values()),
- selectedItem = Field.Data(Blockchain.Avalanche, false),
- onItemSelected = {},
- )
- }
-}
+private fun TestSpinnerPreview() {
+ OutlinedSpinner(
+ label = "Blockchain name",
+ itemList = listOf(Blockchain.values()),
+ selectedItem = Field.Data(Blockchain.Avalanche, false),
+ onItemSelected = {},
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt b/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt
index deacbaf05f..113dbfb809 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/OutlinedTextFieldWidget.kt
@@ -6,6 +6,7 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
@@ -13,8 +14,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.OutlinedTextField
-import androidx.compose.material.Scaffold
import androidx.compose.material.Text
+import androidx.compose.material.TextFieldColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -28,6 +29,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.common.module.ModuleError
+import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.common.form.Field
import com.tangem.tap.common.CompositionLogger
import com.tangem.tap.common.compose.extensions.stringResourceDefault
@@ -38,7 +40,6 @@ import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
*/
@Composable
fun OutlinedTextFieldWidget(
- modifier: Modifier = Modifier,
fieldData: Field.Data,
labelId: Int? = null,
label: String = "",
@@ -53,15 +54,12 @@ fun OutlinedTextFieldWidget(
debounceTextChanges: Long = 400,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
- onTextChanged: (String) -> Unit,
+ onTextChange: (String) -> Unit,
) {
if (!isVisible) return
- Column(
- modifier = modifier.animateContentSize(),
- ) {
+ Column(modifier = Modifier.animateContentSize()) {
OutlinedProgressTextField(
- modifier = modifier,
fieldData = fieldData,
label = stringResourceDefault(labelId, label),
placeholder = stringResourceDefault(placeholderId, placeholder),
@@ -72,15 +70,15 @@ fun OutlinedTextFieldWidget(
debounce = debounceTextChanges,
visualTransformation = visualTransformation,
keyboardOptions = keyboardOptions,
- onTextChanged = onTextChanged,
+ onTextChange = onTextChange,
)
- errorConverter?.let { AnimatedErrorView(error, it) }
+ errorConverter?.let { AnimatedErrorView(errorConverter = it, error = error) }
}
}
+@Suppress("LongMethod", "NestedBlockDepth", "MagicNumber", "MaxLineLength")
@Composable
private fun OutlinedProgressTextField(
- modifier: Modifier = Modifier,
fieldData: Field.Data,
label: String = "",
placeholder: String = "",
@@ -90,8 +88,10 @@ private fun OutlinedProgressTextField(
debounce: Long = 400,
visualTransformation: VisualTransformation = VisualTransformation.None,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
+ colors: TextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
trailingIcon: @Composable (() -> Unit)? = null,
- onTextChanged: (String) -> Unit,
+ onTextChange: (String) -> Unit,
) {
val logger = remember {
CompositionLogger(label, "OutlinedProgressTextField", listOf("Символ токена"))
@@ -102,19 +102,19 @@ private fun OutlinedProgressTextField(
val textDebouncer = valueDebouncerAsState(
initialValue = fieldData.value,
debounce = debounce,
- onEmitValueReceived = {
+ onEmitValueReceive = {
logger.log("DEBOUNCER: onEmitValueReceived: [$it]")
logger.log("DEBOUNCER: start RECOMPOSE by new value for textValueState.value = [$it]")
textValueState.value = it
},
- onValueChanged = {
+ onValueChange = {
logger.log("DEBOUNCER: onValueChanged: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> dispatch.toStore([$it])")
- onTextChanged(it)
+ onTextChange(it)
},
)
logger.log("RECOMPOSE ---------------------------------------------------------------START [${logger.count}]")
- logger.log("RECOMPOSE --data: fieldData.value: [${fieldData}]")
+ logger.log("RECOMPOSE --data: fieldData.value: [$fieldData]")
logger.log("RECOMPOSE --data: textValueState.value: [${textValueState.value}]")
logger.log("RECOMPOSE --data: textDebouncer.emittedValue = [${textDebouncer.emittedValue}]")
logger.log("RECOMPOSE --data: textDebouncer.debounced = [${textDebouncer.debounced}]")
@@ -127,21 +127,32 @@ private fun OutlinedProgressTextField(
logger.log("$isNotUserInput: внешние данные ОДИНАКОВЫ с данными в поле")
} else {
logger.log("$isNotUserInput: внешние данные РАЗЛИЧАЮТСЯ с данными в поле")
- if ((textDebouncer.emittedValue != textDebouncer.debounced) || textDebouncer.emitsCountBeforeDebounce > 0) {
+ if (textDebouncer.emittedValue != textDebouncer.debounced || textDebouncer.emitsCountBeforeDebounce > 0) {
logger.log("$isNotUserInput: пользователь ВВОДИТ данные -> внешние данные игнорируем, ждем RECOMPOSE")
} else {
logger.log("$isNotUserInput: пользователь НЕ вводит данные -> пытаемся обработать внешние данные")
- if (textValueState.value != textDebouncer.emittedValue || textValueState.value != textDebouncer.debounced) {
+ if (textValueState.value != textDebouncer.emittedValue ||
+ textValueState.value != textDebouncer.debounced
+ ) {
logger.log("$isNotUserInput: даннные в поле не соответствуют данным из textDebouncer")
if (textDebouncer.emittedValue.isEmpty() && textDebouncer.debounced.isEmpty()) {
- logger.log("$isNotUserInput: даннные в textDebouncer ПУСТЫ -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]")
+ logger.log(
+ "$isNotUserInput: даннные в textDebouncer ПУСТЫ -> start RECOMPOSE новые данные для " +
+ "textValueState.value = [${fieldData.value}]",
+ )
textValueState.value = fieldData.value
} else {
- logger.log("$isNotUserInput: даннные в textDebouncer НЕ ПУСТЫ -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]")
+ logger.log(
+ "$isNotUserInput: даннные в textDebouncer НЕ ПУСТЫ -> start RECOMPOSE новые данные для " +
+ "textValueState.value = [${fieldData.value}]",
+ )
textValueState.value = fieldData.value
}
} else {
- logger.log("$isNotUserInput: в пустое поле вставляются данные -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]")
+ logger.log(
+ "$isNotUserInput: в пустое поле вставляются данные -> start RECOMPOSE новые данные для " +
+ "textValueState.value = [${fieldData.value}]",
+ )
textValueState.value = fieldData.value
}
}
@@ -159,10 +170,22 @@ private fun OutlinedProgressTextField(
textDebouncer.emmit(it)
},
keyboardOptions = keyboardOptions,
- label = { Text(label) },
+ label = {
+ Text(
+ text = label,
+ style = TangemTheme.typography.caption,
+ color = colors.labelColor(
+ enabled = isEnabled,
+ error = error != null,
+ interactionSource = interactionSource,
+ ).value,
+ )
+ },
placeholder = {
Text(
text = placeholder,
+ style = TangemTheme.typography.body1,
+ color = colors.placeholderColor(enabled = isEnabled).value,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
@@ -172,21 +195,27 @@ private fun OutlinedProgressTextField(
enabled = isEnabled,
isError = error != null,
visualTransformation = visualTransformation,
+ colors = colors,
+ interactionSource = interactionSource,
)
AnimatedVisibility(
- modifier = modifier
+ modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.padding(start = 6.dp, top = 0.dp, end = 6.dp, bottom = 6.dp),
visible = isLoading,
- ) { LinearProgressIndicator() }
+ ) {
+ LinearProgressIndicator(
+ color = TangemTheme.colors.icon.primary1,
+ )
+ }
}
}
@Composable
private fun AnimatedErrorView(
- error: ModuleError? = null,
errorConverter: ModuleMessageConverter,
+ error: ModuleError? = null,
) {
AnimatedVisibility(
visible = error != null,
@@ -204,7 +233,7 @@ private fun AnimatedErrorView(
@Preview
@Composable
-fun OutlinedTextFieldWithErrorTest() {
+private fun OutlinedTextFieldWithErrorTest() {
val context = LocalContext.current
val converter = remember { ModuleMessageConverter(context) }
@@ -217,41 +246,35 @@ fun OutlinedTextFieldWithErrorTest() {
val modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
- Scaffold {
- Column {
- OutlinedTextFieldWidget(
- modifier = modifier,
- fieldData = Field.Data("", false),
- label = "First label",
- placeholder = "1 placeholder",
- error = null,
- errorConverter = converter,
- ) {}
- OutlinedTextFieldWidget(
- modifier = modifier,
- fieldData = Field.Data("First", false),
- label = "First label",
- placeholder = "1 placeholder",
- error = null,
- errorConverter = converter,
- ) {}
- OutlinedTextFieldWidget(
- modifier = modifier,
- fieldData = Field.Data("First", false),
- label = "First label",
- placeholder = "1 placeholder",
- isLoading = true,
- error = null,
- errorConverter = converter,
- ) {}
- OutlinedTextFieldWidget(
- modifier = modifier,
- fieldData = Field.Data("First", false),
- label = "First label",
- placeholder = "1 placeholder",
- error = SimpleError(),
- errorConverter = converter,
- ) {}
- }
+ Column {
+ OutlinedTextFieldWidget(
+ fieldData = Field.Data("", false),
+ label = "First label",
+ placeholder = "1 placeholder",
+ error = null,
+ errorConverter = converter,
+ ) {}
+ OutlinedTextFieldWidget(
+ fieldData = Field.Data("First", false),
+ label = "First label",
+ placeholder = "1 placeholder",
+ error = null,
+ errorConverter = converter,
+ ) {}
+ OutlinedTextFieldWidget(
+ fieldData = Field.Data("First", false),
+ label = "First label",
+ placeholder = "1 placeholder",
+ isLoading = true,
+ error = null,
+ errorConverter = converter,
+ ) {}
+ OutlinedTextFieldWidget(
+ fieldData = Field.Data("First", false),
+ label = "First label",
+ placeholder = "1 placeholder",
+ error = SimpleError(),
+ errorConverter = converter,
+ ) {}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt b/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt
index ad7bbe794b..f742bb5731 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/PinCodeWidget.kt
@@ -50,7 +50,7 @@ import androidx.core.text.isDigitsOnly
@Composable
fun PinCodeWidget(
config: PinViewConfig = tangemPinConfig,
- onPinChanged: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> },
+ onPinChange: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> },
) {
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
@@ -65,7 +65,7 @@ fun PinCodeWidget(
if (value.text.length <= config.pinsCount) {
rTextFieldValue.value = value
- onPinChanged(value.text, isLastSymbolEntered())
+ onPinChange(value.text, isLastSymbolEntered())
}
}
@@ -125,10 +125,6 @@ private fun PinElement(
}
}
-@Composable
-private fun HiddenInputField() {
-}
-
private fun createPinSymbolsList(size: Int, text: String): List = List(size) {
try {
text[it].toString()
@@ -165,7 +161,7 @@ private val tangemPinConfig = PinViewConfig(
@Preview
@Composable
-fun PinCodeWidgetPreview() {
+private fun PinCodeWidgetPreview() {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
PinCodeWidget(tangemPinConfig)
}
diff --git a/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt b/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt
new file mode 100644
index 0000000000..694fd8c376
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/compose/TangemTextFieldsDefault.kt
@@ -0,0 +1,156 @@
+package com.tangem.tap.common.compose
+
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.collectIsFocusedAsState
+import androidx.compose.material.TextFieldColors
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.graphics.Color
+import com.tangem.core.ui.res.TangemTheme
+
+// Copy from com.tangem.core.ui.components
+// TODO: Delete this after fields has been moved to core-ui module
+internal object TangemTextFieldsDefault {
+ val defaultTextFieldColors: TangemTextFieldColors
+ @Composable @Stable get() = TangemTextFieldColors(
+ textColor = TangemTheme.colors.text.primary1,
+ disabledTextColor = TangemTheme.colors.text.disabled,
+ backgroundColor = Color.Transparent,
+ cursorColor = TangemTheme.colors.icon.primary1,
+ errorCursorColor = TangemTheme.colors.icon.warning,
+ focusedIndicatorColor = TangemTheme.colors.icon.primary1,
+ unfocusedIndicatorColor = TangemTheme.colors.stroke.primary,
+ disabledIndicatorColor = TangemTheme.colors.stroke.primary,
+ errorIndicatorColor = TangemTheme.colors.icon.warning,
+ leadingIconColor = TangemTheme.colors.icon.informative,
+ disabledLeadingIconColor = Color.Transparent,
+ errorLeadingIconColor = TangemTheme.colors.icon.warning,
+ trailingIconColor = TangemTheme.colors.icon.informative,
+ disabledTrailingIconColor = Color.Transparent,
+ errorTrailingIconColor = TangemTheme.colors.icon.warning,
+ focusedLabelColor = TangemTheme.colors.text.primary1,
+ unfocusedLabelColor = TangemTheme.colors.text.secondary,
+ disabledLabelColor = TangemTheme.colors.text.disabled,
+ errorLabelColor = TangemTheme.colors.icon.warning,
+ placeholderColor = TangemTheme.colors.text.secondary,
+ disabledPlaceholderColor = TangemTheme.colors.text.disabled,
+ captionColor = TangemTheme.colors.text.tertiary,
+ disabledCaptionColor = TangemTheme.colors.text.disabled,
+ errorCaptionColor = TangemTheme.colors.icon.warning,
+ )
+}
+
+@Immutable
+internal data class TangemTextFieldColors(
+ private val textColor: Color,
+ private val disabledTextColor: Color,
+ private val cursorColor: Color,
+ private val errorCursorColor: Color,
+ private val focusedIndicatorColor: Color,
+ private val unfocusedIndicatorColor: Color,
+ private val errorIndicatorColor: Color,
+ private val disabledIndicatorColor: Color,
+ private val leadingIconColor: Color,
+ private val disabledLeadingIconColor: Color,
+ private val errorLeadingIconColor: Color,
+ private val trailingIconColor: Color,
+ private val disabledTrailingIconColor: Color,
+ private val errorTrailingIconColor: Color,
+ private val backgroundColor: Color,
+ private val focusedLabelColor: Color,
+ private val unfocusedLabelColor: Color,
+ private val disabledLabelColor: Color,
+ private val errorLabelColor: Color,
+ private val placeholderColor: Color,
+ private val disabledPlaceholderColor: Color,
+ private val captionColor: Color,
+ private val disabledCaptionColor: Color,
+ private val errorCaptionColor: Color,
+) : TextFieldColors {
+
+ @Composable
+ override fun leadingIconColor(enabled: Boolean, isError: Boolean): State {
+ return rememberUpdatedState(
+ when {
+ !enabled -> disabledLeadingIconColor
+ isError -> errorLeadingIconColor
+ else -> leadingIconColor
+ },
+ )
+ }
+
+ @Composable
+ override fun trailingIconColor(enabled: Boolean, isError: Boolean): State {
+ return rememberUpdatedState(
+ when {
+ !enabled -> disabledTrailingIconColor
+ isError -> errorTrailingIconColor
+ else -> trailingIconColor
+ },
+ )
+ }
+
+ @Composable
+ override fun indicatorColor(
+ enabled: Boolean,
+ isError: Boolean,
+ interactionSource: InteractionSource,
+ ): State {
+ val focused by interactionSource.collectIsFocusedAsState()
+
+ val targetValue = when {
+ !enabled -> disabledIndicatorColor
+ isError -> errorIndicatorColor
+ focused -> focusedIndicatorColor
+ else -> unfocusedIndicatorColor
+ }
+ return if (enabled) {
+ animateColorAsState(targetValue, tween(durationMillis = 120))
+ } else {
+ rememberUpdatedState(targetValue)
+ }
+ }
+
+ @Composable
+ override fun backgroundColor(enabled: Boolean): State {
+ return rememberUpdatedState(backgroundColor)
+ }
+
+ @Composable
+ override fun placeholderColor(enabled: Boolean): State {
+ return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor)
+ }
+
+ @Composable
+ override fun labelColor(
+ enabled: Boolean,
+ error: Boolean,
+ interactionSource: InteractionSource,
+ ): State {
+ val focused by interactionSource.collectIsFocusedAsState()
+
+ val targetValue = when {
+ !enabled -> disabledLabelColor
+ error -> errorLabelColor
+ focused -> focusedLabelColor
+ else -> unfocusedLabelColor
+ }
+ return rememberUpdatedState(targetValue)
+ }
+
+ @Composable
+ override fun textColor(enabled: Boolean): State {
+ return rememberUpdatedState(if (enabled) textColor else disabledTextColor)
+ }
+
+ @Composable
+ override fun cursorColor(isError: Boolean): State {
+ return rememberUpdatedState(if (isError) errorCursorColor else cursorColor)
+ }
+}
\ 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
index 88b088fa6a..2013295f25 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/TangemTypography.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/TangemTypography.kt
@@ -9,13 +9,13 @@ import com.tangem.wallet.R
object TangemTypography {
val body1 = TextStyle(
- fontFamily = FontFamily(Font(R.font.roboto)),
+ fontFamily = FontFamily(Font(R.font.roboto_regular)),
fontSize = 16.0.sp,
letterSpacing = 0.5.sp,
lineHeight = 24.0.sp,
)
val body2 = TextStyle(
- fontFamily = FontFamily(Font(R.font.roboto)),
+ fontFamily = FontFamily(Font(R.font.roboto_regular)),
fontSize = 14.0.sp,
letterSpacing = 0.25.sp,
lineHeight = 20.0.sp,
@@ -27,13 +27,13 @@ object TangemTypography {
lineHeight = 20.0.sp,
)
val caption = TextStyle(
- fontFamily = FontFamily(Font(R.font.roboto)),
+ fontFamily = FontFamily(Font(R.font.roboto_regular)),
fontSize = 12.0.sp,
letterSpacing = 0.4000000059604645.sp,
lineHeight = 16.0.sp,
)
val headline1 = TextStyle(
- fontFamily = FontFamily(Font(R.font.roboto)),
+ fontFamily = FontFamily(Font(R.font.roboto_regular)),
fontSize = 34.0.sp,
letterSpacing = 0.0.sp,
lineHeight = 44.0.sp,
diff --git a/app/src/main/java/com/tangem/tap/common/compose/Undefined.kt b/app/src/main/java/com/tangem/tap/common/compose/Undefined.kt
index 6cd0b70d72..10e2ec9956 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/Undefined.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/Undefined.kt
@@ -16,7 +16,7 @@ fun TitleSubtitle(
title: String,
subtitle: String
) {
- Column() {
+ Column {
Text(text = title)
Text(
text = subtitle,
diff --git a/app/src/main/java/com/tangem/tap/common/compose/Warning.kt b/app/src/main/java/com/tangem/tap/common/compose/Warning.kt
index 07c82d1384..ea4d150e51 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/Warning.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/Warning.kt
@@ -13,6 +13,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.common.module.ModuleMessage
+import com.tangem.core.ui.components.SpacerH8
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
import com.tangem.wallet.R
@@ -21,9 +22,9 @@ import com.tangem.wallet.R
*/
@Composable
fun AddCustomTokenWarning(
- modifier: Modifier = Modifier,
warning: ModuleMessage,
- converter: ModuleMessageConverter
+ converter: ModuleMessageConverter,
+ modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier,
@@ -48,6 +49,5 @@ fun AddCustomTokenWarning(
lineHeight = 18.sp
)
}
-
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/Color.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/Color.kt
deleted file mode 100644
index d25bc9e26b..0000000000
--- a/app/src/main/java/com/tangem/tap/common/compose/extensions/Color.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.tangem.tap.common.compose.extensions
-
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.toArgb
-import androidx.core.graphics.alpha
-import androidx.core.graphics.blue
-import androidx.core.graphics.green
-import androidx.core.graphics.red
-
-/**
-[REDACTED_AUTHOR]
- */
-fun Color.toAndroidGraphicsColor(): Int {
- val argb = this.toArgb()
- return android.graphics.Color.argb(argb.alpha, argb.red, argb.green, argb.blue)
-}
-
-fun Color.parse(hexColor: String): Color {
- return Color(hexColor.removePrefix("#").toInt(16))
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/Context.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/Context.kt
index 25ae94623c..719c80d0d4 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/extensions/Context.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/Context.kt
@@ -8,11 +8,13 @@ import com.tangem.tap.common.extensions.getFromClipboard
/**
[REDACTED_AUTHOR]
*/
+@Suppress("ComposableFunctionName")
@Composable
fun copyToClipboard(value: Any, label: String = "") {
LocalContext.current.copyToClipboard(value, label)
}
+@Suppress("ComposableFunctionName")
@Composable
fun getFromClipboard(default: CharSequence? = null): CharSequence? {
return LocalContext.current.getFromClipboard(default)
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
index 1692b4c51f..99911d9e6f 100644
--- 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
@@ -13,7 +13,8 @@ import androidx.core.graphics.drawable.toBitmap
*/
@Composable
fun asImageBitmap(@DrawableRes drawableId: Int): ImageBitmap {
- val drawable = AppCompatResources.getDrawable(LocalContext.current, drawableId)
- ?: throw NullPointerException()
+ val drawable = requireNotNull(AppCompatResources.getDrawable(LocalContext.current, drawableId)) {
+ "drawable is null"
+ }
return drawable.toBitmap().asImageBitmap()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/compose/extensions/LazyListState.kt b/app/src/main/java/com/tangem/tap/common/compose/extensions/LazyListState.kt
index f05044368f..dab9b8f654 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/extensions/LazyListState.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/extensions/LazyListState.kt
@@ -1,12 +1,16 @@
package com.tangem.tap.common.compose.extensions
import androidx.compose.foundation.lazy.LazyListState
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalView
import com.tangem.tap.common.extensions.hideKeyboard
@Composable
-fun LazyListState.OnBottomReached(loadMoreThreshold: Int, loadMore: () -> Unit) {
+fun LazyListState.OnBottomReached(loadMoreThreshold: Int, onLoadMore: () -> Unit) {
require(loadMoreThreshold >= 0)
val shouldLoadMore by remember {
derivedStateOf {
@@ -17,7 +21,7 @@ fun LazyListState.OnBottomReached(loadMoreThreshold: Int, loadMore: () -> Unit)
}
LaunchedEffect(shouldLoadMore) {
- if (shouldLoadMore) loadMore()
+ if (shouldLoadMore) onLoadMore()
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/entities/Button.kt b/app/src/main/java/com/tangem/tap/common/entities/Button.kt
index a2d4127c8f..d9ff2b27f8 100644
--- a/app/src/main/java/com/tangem/tap/common/entities/Button.kt
+++ b/app/src/main/java/com/tangem/tap/common/entities/Button.kt
@@ -6,7 +6,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState
open class Button(val enabled: Boolean)
open class IndeterminateProgressButton(
- val state: ButtonState
+ val state: ButtonState
) : Button(state != ButtonState.DISABLED) {
val progressState: ProgressState
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt b/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt
index 8ed146440b..5f531a9bc8 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Activity.kt
@@ -26,11 +26,11 @@ fun Activity.sendEmail(
) {
fun createEmailShareIntent(recipient: String, subject: String, text: String, file: File? = null): Intent {
val builder = ShareCompat.IntentBuilder.from(this)
- .setType("message/rfc822")
- .setEmailTo(arrayOf(recipient))
- .setSubject(subject)
- .setText(text)
- file?.let { builder.setStream(FileProvider.getUriForFile(this, "${packageName}.provider", it)) }
+ .setType("message/rfc822")
+ .setEmailTo(arrayOf(recipient))
+ .setSubject(subject)
+ .setText(text)
+ file?.let { builder.setStream(FileProvider.getUriForFile(this, "$packageName.provider", it)) }
return builder.intent
}
@@ -41,17 +41,17 @@ fun Activity.sendEmail(
val emailFilterIntentResults = packageManager.queryIntentActivities(emailFilterIntent, 0)
val targetedIntents = originalIntentResults
- .filter { originalResult ->
- emailFilterIntentResults.any {
- originalResult.activityInfo.packageName == it.activityInfo.packageName
- }
+ .filter { originalResult ->
+ emailFilterIntentResults.any {
+ originalResult.activityInfo.packageName == it.activityInfo.packageName
}
- .map {
- createEmailShareIntent(email, subject, message, file).apply {
- setPackage(it.activityInfo.packageName)
- }
+ }
+ .map {
+ createEmailShareIntent(email, subject, message, file).apply {
+ setPackage(it.activityInfo.packageName)
}
- .toMutableList()
+ }
+ .toMutableList()
try {
val chooserIntent = Intent.createChooser(targetedIntents.removeAt(0), "Send mail...")
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt
index b2905ec473..9130b6c91d 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/AssetManager.kt
@@ -3,4 +3,4 @@ package com.tangem.tap.common.extensions
import android.content.res.AssetManager
fun AssetManager.readJsonFileToString(fileName: String): String =
- this.open("$fileName.json").bufferedReader().readText()
\ No newline at end of file
+ this.open("$fileName.json").bufferedReader().readText()
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt b/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt
index abe1863c6d..140f0946b4 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt
@@ -6,8 +6,7 @@ import com.tangem.operations.backup.BackupService
/**
[REDACTED_AUTHOR]
*/
+@Suppress("MagicNumber")
fun BackupService.primaryCardIsSaltPayVisa(): Boolean {
- return primaryCardId?.slice(0..3)?.let {
- SaltPayWorkaround.isVisaBatchId(it)
- } ?: false
+ return primaryCardId?.slice(0..3)?.let(SaltPayWorkaround::isVisaBatchId) ?: false
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt b/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt
index e8b3891ce9..80e756c21c 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Bitmap.kt
@@ -4,12 +4,14 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import java.io.ByteArrayOutputStream
+@Suppress("MagicNumber")
fun Bitmap.toByteArray(): ByteArray {
val stream = ByteArrayOutputStream()
this.compress(Bitmap.CompressFormat.JPEG, 20, stream)
return stream.toByteArray()
}
+@Suppress("MagicNumber")
fun ByteArray.toBitmap(): Bitmap {
return BitmapFactory.decodeByteArray(this, 0, this.size)
}
\ 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 e53e1effbe..133869ee22 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
@@ -5,49 +5,17 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.remove
import com.tangem.wallet.R
-@DrawableRes
-fun Blockchain.getRoundIconRes(): Int {
- return when (this) {
- Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_round
- Blockchain.Ducatus -> R.drawable.ic_ducatus
- Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_round
- Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_round
- Blockchain.Litecoin -> R.drawable.ic_litecoin_round
- Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_round
- Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.ic_eth_round
- Blockchain.RSK -> R.drawable.ic_rsk_round
- Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_round
- Blockchain.Tezos -> R.drawable.ic_tezos_round
- Blockchain.XRP -> R.drawable.ic_xrp_round
- Blockchain.Stellar -> R.drawable.ic_stellar_round
- Blockchain.Avalanche, Blockchain.AvalancheTestnet -> R.drawable.ic_avalanche_round
- Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_round
- Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_round
- Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_round
- 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
- Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_round
- Blockchain.EthereumFair -> R.drawable.ic_ethereumfair_round
- Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_round
- Blockchain.Kusama -> R.drawable.ic_kusama_round
- Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism
- Blockchain.Dash -> R.drawable.ic_dash
- else -> R.drawable.ic_tangem_logo
- }
-}
-
+@Suppress("ComplexMethod")
@DrawableRes
fun Blockchain.getGreyedOutIconRes(): Int {
return when (this) {
Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_no_color
// Blockchain.Ducatus -> R.drawable.ic_ducatus
- Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_no_color
+ Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> R.drawable.ic_bitcoin_no_color
Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_no_color
Blockchain.Litecoin -> R.drawable.ic_litecoin_no_color
- Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_no_color
- Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet, -> R.drawable.ic_eth_no_color
+ Blockchain.Ethereum, Blockchain.EthereumTestnet -> R.drawable.ic_eth_no_color
+ Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.ic_eth_no_color
Blockchain.RSK -> R.drawable.ic_rsk_no_color
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_no_color
Blockchain.Tezos -> R.drawable.ic_tezos_no_color
@@ -57,7 +25,8 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_no_color
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_no_color
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_no_color
- Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_no_color
+ 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
@@ -82,4 +51,4 @@ fun Blockchain.getNetworkName(): String {
}
val Blockchain.fullNameWithoutTestnet
-get() = this.fullName.remove(" Testnet")
\ No newline at end of file
+ get() = this.fullName.remove(" Testnet")
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Context.kt b/app/src/main/java/com/tangem/tap/common/extensions/Context.kt
index cba31fe77c..ee68830265 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Context.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Context.kt
@@ -1,7 +1,11 @@
package com.tangem.tap.common.extensions
+import android.content.ContentResolver
import android.content.Context
import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.net.Uri
+import androidx.annotation.AnyRes
import androidx.core.content.ContextCompat
fun Context.readFile(fileName: String): String =
@@ -19,4 +23,20 @@ fun Context.readAssetAsString(fileName: String): String {
fun Context.isPermissionGranted(permission: String): Boolean {
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
+}
+
+/**
+ * Get uri to any resource type via given Resource Instance
+ * @param resId - resource id
+ * @throws Resources.NotFoundException if the given ID does not exist.
+ * @return - Uri to resource by the given ID
+ */
+@Throws(Resources.NotFoundException::class)
+fun Context.resourceUri(@AnyRes resId: Int): Uri {
+ return Uri.parse(
+ ContentResolver.SCHEME_ANDROID_RESOURCE +
+ "://" + resources.getResourcePackageName(resId) +
+ '/' + resources.getResourceTypeName(resId) +
+ '/' + resources.getResourceEntryName(resId),
+ )
}
\ No newline at end of file
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 a9504e668a..6cb707b071 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
@@ -1,10 +1,12 @@
package com.tangem.tap.common.extensions
-import androidx.activity.OnBackPressedCallback
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
-import com.tangem.common.extensions.VoidCallback
+import com.tangem.feature.referral.ReferralFragment
+import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment
@@ -20,24 +22,27 @@ import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment
import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
+import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.shop.ui.ShopFragment
import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment
import com.tangem.tap.features.tokens.ui.AddTokensFragment
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
import com.tangem.tap.features.wallet.ui.WalletFragment
+import com.tangem.tap.features.walletSelector.ui.WalletSelectorBottomSheetFragment
+import com.tangem.tap.features.welcome.ui.WelcomeFragment
import com.tangem.wallet.R
import timber.log.Timber
-private class Navigation
-
fun FragmentActivity.openFragment(
screen: AppScreen,
addToBackstack: Boolean,
- fgShareTransition: FragmentShareTransition? = null
+ bundle: Bundle? = null,
+ fgShareTransition: FragmentShareTransition? = null,
) {
val transaction = this.supportFragmentManager.beginTransaction()
val fragment = fragmentFactory(screen)
+ fragment.arguments = bundle
fgShareTransition?.apply {
fragment.sharedElementEnterTransition = enterTransitionSet
fragment.sharedElementReturnTransition = exitTransitionSet
@@ -48,9 +53,18 @@ fun FragmentActivity.openFragment(
}
}
}
- transaction.replace(R.id.fragment_container, fragment, screen.name)
- if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null)
- transaction.commitAllowingStateLoss()
+ if (screen.isDialogFragment) {
+ (fragment as DialogFragment).show(transaction, screen.name)
+ if (addToBackstack) {
+ transaction.addToBackStack(screen.name)
+ }
+ } else {
+ transaction.replace(R.id.fragment_container, fragment, screen.name)
+ if (addToBackstack) {
+ transaction.addToBackStack(screen.name)
+ }
+ transaction.commitAllowingStateLoss()
+ }
}
fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) {
@@ -68,21 +82,15 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? {
} else {
0
}
- val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount)
+ val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount) {
this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name
- else null
+ } else {
+ null
+ }
return tag?.let { AppScreen.valueOf(tag) }
}
-fun FragmentActivity.addOnBackPressedDispatcher(
- isEnabled: Boolean = true,
- onBackPressed: VoidCallback
-): OnBackPressedCallback = (object : OnBackPressedCallback(isEnabled) {
- override fun handleOnBackPressed() {
- onBackPressed()
- }
-}).also { this.onBackPressedDispatcher.addCallback(it) }
-
+@Suppress("ComplexMethod")
private fun fragmentFactory(screen: AppScreen): Fragment {
return when (screen) {
AppScreen.Home -> HomeFragment()
@@ -104,5 +112,10 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.WalletDetails -> WalletDetailsFragment()
AppScreen.WalletConnectSessions -> WalletConnectFragment()
AppScreen.QrScan -> QrScanFragment()
+ AppScreen.ReferralProgram -> ReferralFragment()
+ AppScreen.Swap -> SwapFragment()
+ AppScreen.Welcome -> WelcomeFragment()
+ AppScreen.SaveWallet -> SaveWalletBottomSheetFragment()
+ AppScreen.WalletSelector -> WalletSelectorBottomSheetFragment()
}
}
\ 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 795bc8e268..ff4f898d8d 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
@@ -11,8 +11,11 @@ import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.*
+// TODO: move extensions to utils
fun BigDecimal.toFormattedString(
- decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US
+ decimals: Int,
+ roundingMode: RoundingMode = RoundingMode.DOWN,
+ locale: Locale = Locale.US,
): String {
val symbols = DecimalFormatSymbols(locale)
val df = DecimalFormat()
@@ -24,9 +27,12 @@ fun BigDecimal.toFormattedString(
return df.format(this)
}
+@Suppress("MagicNumber")
fun BigDecimal.toFormattedCurrencyString(
- decimals: Int, currency: String, roundingMode: RoundingMode = RoundingMode.DOWN,
- limitNumberOfDecimals: Boolean = true
+ decimals: Int,
+ currency: String,
+ roundingMode: RoundingMode = RoundingMode.DOWN,
+ limitNumberOfDecimals: Boolean = true,
): String {
val decimalsForRounding = if (limitNumberOfDecimals) {
if (decimals > 8) 8 else decimals
@@ -34,13 +40,14 @@ fun BigDecimal.toFormattedCurrencyString(
decimals
}
val formattedAmount = this.toFormattedString(
- decimals = decimalsForRounding, roundingMode = roundingMode
+ decimals = decimalsForRounding,
+ roundingMode = roundingMode,
)
return "$formattedAmount $currency"
}
fun BigDecimal.toFiatRateString(
- fiatCurrencyName: String
+ fiatCurrencyName: String,
): String {
val value = this
.setScale(2, RoundingMode.HALF_UP)
@@ -51,7 +58,7 @@ fun BigDecimal.toFiatRateString(
fun BigDecimal.toFiatString(
rateValue: BigDecimal,
fiatCurrencyName: String,
- formatWithSpaces: Boolean = false
+ formatWithSpaces: Boolean = false,
): String {
val fiatValue = rateValue.multiply(this)
return fiatValue.toFormattedFiatValue(fiatCurrencyName, formatWithSpaces)
@@ -64,11 +71,11 @@ fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
fun BigDecimal.toFormattedFiatValue(
fiatCurrencyName: String,
- formatWithSpaces: Boolean = false
+ formatWithSpaces: Boolean = false,
): String {
val fiatValue = this.setScale(2, RoundingMode.HALF_UP)
.let { if (formatWithSpaces) it.formatWithSpaces() else it }
- return " ${fiatValue} $fiatCurrencyName"
+ return " $fiatValue $fiatCurrencyName"
}
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
@@ -81,9 +88,7 @@ fun BigDecimal.scaleToFiat(applyPrecision: Boolean = false): BigDecimal {
if (this.isZero()) return this
val scaledFiat = this.setScale(2, RoundingMode.DOWN)
- return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1)
- else scaledFiat
-
+ return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1) else scaledFiat
}
fun BigDecimal.setPrecision(precision: Int, roundingMode: RoundingMode = RoundingMode.DOWN): BigDecimal {
@@ -108,7 +113,7 @@ fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean {
fun BigDecimal.formatAmountAsSpannedString(
currencySymbol: String,
- reminderPartSizeProportion: Float = 0.7f
+ reminderPartSizeProportion: Float = 0.7f,
): SpannedString {
val amount = this
.setScale(2, RoundingMode.HALF_UP)
@@ -124,11 +129,12 @@ fun BigDecimal.formatAmountAsSpannedString(
append(
"$reminder $currencySymbol",
RelativeSizeSpan(reminderPartSizeProportion),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
)
}
}
+@Suppress("MagicNumber")
fun BigDecimal.formatWithSpaces(): String {
val str = this.toString()
var integerStr = str.substringBefore('.')
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt
index d88560669e..69c5f1c085 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt
@@ -6,6 +6,7 @@ import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.model.UserWallet
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.Dispatchers
@@ -26,6 +27,11 @@ fun Store<*>.dispatchNotification(resId: Int) {
dispatchOnMain(GlobalAction.ShowNotification(resId))
}
+@Suppress("UnusedReceiverParameter")
+suspend fun Store<*>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) {
+ store.state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh)
+}
+
fun Store<*>.dispatchToastNotification(resId: Int) {
dispatchOnMain(GlobalAction.ShowToastNotification(resId))
}
@@ -71,7 +77,6 @@ suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse) {
fun Store<*>.dispatchOpenUrl(url: String) {
store.dispatch(NavigationAction.OpenUrl(url))
}
-
fun Store<*>.dispatchShare(url: String) {
store.dispatch(NavigationAction.Share(url))
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt
index 7d54a7bf92..3c4125d422 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt
@@ -29,7 +29,7 @@ fun String.colorSegment(
context: Context,
color: Int,
startIndex: Int = 0,
- endIndex: Int = this.length
+ endIndex: Int = this.length,
): Spannable {
return this.toSpannable()
.also { spannable ->
@@ -37,11 +37,12 @@ fun String.colorSegment(
ForegroundColorSpan(ContextCompat.getColor(context, color)),
startIndex,
endIndex,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
)
}
}
+@Suppress("MagicNumber")
fun String.toQrCode(): Bitmap {
val hintMap = Hashtable()
hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage
@@ -62,4 +63,9 @@ fun String.toQrCode(): Bitmap {
return bmp
}
-fun String.urlEncode(): String = Uri.encode(this)
\ No newline at end of file
+fun String.urlEncode(): String = Uri.encode(this)
+
+fun String.removePrefixOrNull(prefix: String): String? = when {
+ startsWith(prefix) -> substring(prefix.length)
+ else -> null
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt
index 48ee67afc7..aef0701282 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/TextView.kt
@@ -18,13 +18,12 @@ fun EditText.update(text: String?) {
val textLength = text?.length ?: 0
- //prevent cursor jumping while editing a text
+ // prevent cursor jumping while editing a text
val cursorPosition = if (selectionEnd > textLength) textLength else selectionEnd
this.setText(text)
if (!isFocused || textLength == 0) return
- if (cursorPosition == 0) setSelection(textLength)
- else setSelection(cursorPosition)
+ if (cursorPosition == 0) setSelection(textLength) else setSelection(cursorPosition)
}
fun EditText.setOnImeActionListener(action: Int, handler: (EditText) -> Unit) {
@@ -56,13 +55,4 @@ fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) {
error = null
isErrorEnabled = false
}
-}
-
-fun TextView.isEllipsized(): Boolean {
- val layout = this.layout
- if (layout != null) {
- val lines: Int = layout.lineCount
- return (lines > 0) && (layout.getEllipsisCount(lines - 1) > 0)
- }
- return false
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt
index 59c918d736..7acbdfd9fb 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Token.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Token.kt
@@ -6,19 +6,23 @@ import androidx.core.graphics.luminance
import androidx.core.graphics.toColorInt
import com.tangem.blockchain.common.Token
+@Suppress("MagicNumber")
@ColorInt
fun Token.getColor(isTestnet: Boolean = false): Int {
val defaultColor = "#C7C7CC".toColorInt() // equivalent to R.color.lightGray4
- return if (isTestnet)
- defaultColor
- else try {
- ("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt()
- } catch (exception: Exception) {
+ return if (isTestnet) {
defaultColor
+ } else {
+ try {
+ ("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt()
+ } catch (exception: Exception) {
+ defaultColor
+ }
}
}
+@Suppress("MagicNumber")
@ColorInt
fun Token.getTextColor(isTestnet: Boolean = false): Int = when {
isTestnet -> Color.WHITE
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 4be902ebf7..28bfaad264 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
@@ -1,16 +1,14 @@
+@file:Suppress("TooManyFunctions")
+
package com.tangem.tap.common.extensions
import android.app.Activity
-import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
-import android.content.res.Resources
import android.graphics.drawable.Drawable
-import android.os.Build
-import android.os.Bundle
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.View
@@ -19,16 +17,12 @@ import android.view.inputmethod.InputMethodManager
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
+import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import com.google.android.material.card.MaterialCardView
-import com.tangem.common.extensions.VoidCallback
-
-fun Fragment.getDrawable(@DrawableRes drawableResId: Int): Drawable? {
- return ContextCompat.getDrawable(requireContext(), drawableResId)
-}
fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? {
return ContextCompat.getDrawable(this, drawableResId)
@@ -57,21 +51,12 @@ fun View.getString(@StringRes id: Int, vararg formatArgs: String): String {
return context.getString(id, *formatArgs)
}
-fun View.getQuantityString(@StringRes id: Int, quantity: Int): String {
+fun View.getQuantityString(@PluralsRes id: Int, quantity: Int): String {
return context.resources.getQuantityString(id, quantity, quantity)
}
-fun View.getResourceName(): String {
- return try {
- resources.getResourceEntryName(id)
- } catch (ex: Resources.NotFoundException) {
- "Not found"
- }
-}
-
fun View.show(show: Boolean, invokeBeforeStateChanged: (() -> Unit)? = null) {
- return if (show) this.show(invokeBeforeStateChanged)
- else this.hide(invokeBeforeStateChanged)
+ return if (show) this.show(invokeBeforeStateChanged) else this.hide(invokeBeforeStateChanged)
}
fun View.show(invokeBeforeStateChanged: (() -> Unit)? = null) {
@@ -101,7 +86,9 @@ 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,
+ TypedValue.COMPLEX_UNIT_DIP,
+ dp.toFloat(),
+ this.resources.displayMetrics,
).toInt()
fun Context.pixelsToDp(pixels: Int): Int {
@@ -110,12 +97,6 @@ fun Context.pixelsToDp(pixels: Int): Int {
.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()
@@ -135,19 +116,6 @@ fun MaterialCardView.setMargins(
this.layoutParams = params
}
-fun Activity.setSystemBarTextColor(setTextDark: Boolean) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- 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
- }
- }
-}
-
fun View.hideKeyboard() {
val inputMethodManager =
context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as? InputMethodManager
@@ -184,21 +152,6 @@ fun Fragment.shareText(text: String) {
requireContext().shareText(text)
}
-fun Context.safeStartActivity(
- intent: Intent,
- options: Bundle? = null,
- fallback: ((ActivityNotFoundException) -> Unit)? = null,
- finally: VoidCallback? = null,
-) {
- try {
- this.startActivity(intent, options)
- } catch (ex: ActivityNotFoundException) {
- fallback?.invoke(ex)
- } finally {
- finally?.invoke()
- }
-}
-
fun View.getString(resId: Int, vararg formatArgs: Any?): String {
return context.getString(resId, *formatArgs)
}
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Any.kt b/app/src/main/java/com/tangem/tap/common/extensions/ValueCallback.kt
similarity index 100%
rename from app/src/main/java/com/tangem/tap/common/extensions/Any.kt
rename to app/src/main/java/com/tangem/tap/common/extensions/ValueCallback.kt
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 15d02907bf..97997ba97f 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
@@ -21,6 +21,7 @@ import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
+@Suppress("MagicNumber")
suspend fun WalletManager.safeUpdate(): Result = try {
val scanResponse = store.state.globalState.scanResponse
@@ -55,11 +56,10 @@ suspend fun WalletManager.safeUpdate(): Result = try {
}
}
-fun WalletManager?.getToUpUrl(): String? {
+fun WalletManager.getTopUpUrl(): String? {
val globalState = store.state.globalState
- val wallet = this?.wallet ?: return null
-
val defaultAddress = wallet.address
+
return globalState.exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Buy,
blockchain = wallet.blockchain,
@@ -73,8 +73,7 @@ fun WalletManager?.getAddressData(): AddressData? {
val wallet = this?.wallet ?: return null
val addressDataList = wallet.createAddressesData()
- return if (addressDataList.isEmpty()) null
- else addressDataList[0]
+ return if (addressDataList.isEmpty()) null else addressDataList[0]
}
fun WalletManager.Companion.stub(): T {
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
index ce1313c772..1eb07546b4 100644
--- a/app/src/main/java/com/tangem/tap/common/feature/Feature.kt
+++ b/app/src/main/java/com/tangem/tap/common/feature/Feature.kt
@@ -4,5 +4,5 @@ package com.tangem.tap.common.feature
[REDACTED_AUTHOR]
*/
interface Feature {
- fun featureIsSwitchedOn():Boolean
+ 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
index 97c8a9e41f..c6f5bafd0a 100644
--- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt
+++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt
@@ -8,9 +8,10 @@ import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.Address
-import com.tangem.common.card.CardWallet
+import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.extensions.stripZeroPlainString
+import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
class AdditionalFeedbackInfo {
class EmailWalletInfo(
@@ -29,6 +30,7 @@ class AdditionalFeedbackInfo {
var cardFirmwareVersion: String = ""
var cardIssuer: String = ""
var cardBlockchain: String = ""
+ var userWalletId: String = ""
// wallets
internal val walletsInfo = mutableListOf()
@@ -46,12 +48,16 @@ class AdditionalFeedbackInfo {
var fee: String = ""
var token: String = ""
+ private val Address.name: String
+ get() = type.javaClass.simpleName
+
fun setCardInfo(data: ScanResponse) {
cardId = data.card.cardId
cardBlockchain = data.walletData?.blockchain ?: ""
cardFirmwareVersion = data.card.firmwareVersion.stringValue
cardIssuer = data.card.issuer.name
signedHashesCount = formatSignedHashes(data.card.wallets)
+ userWalletId = UserWalletIdBuilder.scanResponse(data).build()?.stringValue ?: ""
}
fun setWalletsInfo(walletManagers: List) {
@@ -89,7 +95,7 @@ class AdditionalFeedbackInfo {
)
}
- private fun formatSignedHashes(wallets: List): String {
+ private fun formatSignedHashes(wallets: List): String {
return wallets.joinToString("\n") { "Signed hashes: ${it.curve.curve} - ${it.totalSignedHashes}" }
}
@@ -105,6 +111,7 @@ class AdditionalFeedbackInfo {
}
}
+ @Suppress("MagicNumber")
private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String {
return if (addresses.size == 1) {
getExploreUrl(address)
@@ -114,7 +121,4 @@ class AdditionalFeedbackInfo {
.joinToString("\n")
}
}
-
- private val Address.name: String
- get() = type.javaClass.simpleName
}
\ 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
index 6578b97356..452eb173aa 100644
--- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt
@@ -15,6 +15,7 @@ interface FeedbackData {
fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String
+ @Suppress("MagicNumber")
fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String {
return StringBuilder().apply {
append(context.getString(mainMessageResId))
@@ -42,6 +43,7 @@ class ScanFailsEmail : FeedbackData {
override val subjectResId: Int = R.string.feedback_subject_scan_failed
override val mainMessageResId: Int = R.string.feedback_preface_scan_failed
+ @Suppress("MagicNumber")
override fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String = StringBuilder().apply {
append(context.getString(mainMessageResId))
breakLine(4)
@@ -96,8 +98,8 @@ class FeedbackEmail : FeedbackData {
}
class SupportInfo : FeedbackData {
- override val subjectResId: Int = R.string.details_ask_a_question
- override val mainMessageResId: Int = R.string.details_ask_a_question
+ override val subjectResId: Int = R.string.details_chat
+ override val mainMessageResId: Int = R.string.details_chat
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String {
return FeedbackDataBuilder(infoHolder)
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
index 46cf302fee..4baf39c3e2 100644
--- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt
@@ -22,6 +22,7 @@ class FeedbackDataBuilder(
builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain)
builder.appendKeyValue("", infoHolder.signedHashesCount)
+ builder.appendKeyValue("User Wallet ID", infoHolder.userWalletId)
return this
}
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
index 12fe1e42e9..b44db4004b 100644
--- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.common.feedback
import android.content.Context
import android.os.Build
+import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.TapWorkarounds
import com.tangem.tap.common.extensions.sendEmail
import com.tangem.tap.common.log.TangemLogCollector
@@ -108,6 +109,7 @@ class FeedbackManager(
}
private fun showMessagingActivity(context: Context) {
+ Analytics.send(com.tangem.tap.common.analytics.events.Chat.ScreenOpened())
MessagingActivity.builder()
.withMultilineResponseOptionsEnabled(false)
.withBotLabelStringRes(R.string.chat_bot_name)
diff --git a/app/src/main/java/com/tangem/tap/common/images/Coil.kt b/app/src/main/java/com/tangem/tap/common/images/Coil.kt
index 5224c40df0..4e5a88d6e0 100644
--- a/app/src/main/java/com/tangem/tap/common/images/Coil.kt
+++ b/app/src/main/java/com/tangem/tap/common/images/Coil.kt
@@ -30,13 +30,11 @@ fun createCoilImageLoader(
}
)
.build()
-
}
}
.build()
}
-
private class CoilTimberLogger : Logger {
override var level: Int = Log.DEBUG
diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt
index 1999c35855..98a06327bb 100644
--- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt
+++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt
@@ -17,13 +17,13 @@ class TestLeapfrogFragment : Fragment(R.layout.test_leapfrog_fragment) {
private lateinit var twinsCardWidget: TwinsCardWidget
private val binding: TestLeapfrogFragmentBinding by viewBinding(TestLeapfrogFragmentBinding::bind)
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val inflater = TransitionInflater.from(requireContext())
exitTransition = inflater.inflateTransition(R.transition.fade)
}
+ @Suppress("MagicNumber")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
diff --git a/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt b/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt
index da69eed9ae..4a86caa20d 100644
--- a/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt
+++ b/app/src/main/java/com/tangem/tap/common/qrCodeScan/ScanQrCodeActivity.kt
@@ -16,11 +16,6 @@ import me.dm7.barcodescanner.zxing.ZXingScannerView
[REDACTED_AUTHOR]
*/
class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
- companion object {
- val SCAN_QR_REQUEST_CODE = 1001
- val SCAN_RESULT = "scanResult"
- }
-
private lateinit var mScannerView: ZXingScannerView
override fun onCreate(state: Bundle?) {
@@ -29,7 +24,6 @@ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
setContentView(mScannerView)
if (!permissionIsGranted()) requestPermission()
-
}
override fun onResume() {
@@ -66,4 +60,9 @@ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
private fun requestPermission() {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE)
}
+
+ companion object {
+ const val SCAN_QR_REQUEST_CODE = 1001
+ const val SCAN_RESULT = "scanResult"
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt
new file mode 100644
index 0000000000..e9aaabfd70
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt
@@ -0,0 +1,27 @@
+package com.tangem.tap.common.redux
+
+import com.tangem.domain.common.ScanResponse
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.preferencesStorage
+import com.tangem.tap.tangemSdkManager
+import org.rekotlin.Middleware
+
+class AccessCodeRequestPolicyMiddleware {
+ val middleware: Middleware = { _, _ ->
+ { next ->
+ { action ->
+ if (action is GlobalAction.SaveScanResponse) {
+ updateAccessCodeRequestPolicy(action.scanResponse)
+ }
+ next(action)
+ }
+ }
+ }
+
+ private fun updateAccessCodeRequestPolicy(scanResponse: ScanResponse) {
+ tangemSdkManager.setAccessCodeRequestPolicy(
+ useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
+ scanResponse.card.isAccessCodeSet,
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
index cbfa2565fb..f03dfc70ca 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
@@ -10,24 +10,28 @@ import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteRedu
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer
+import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
import com.tangem.tap.features.shop.redux.ShopReducer
import com.tangem.tap.features.tokens.redux.TokensReducer
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
+import com.tangem.tap.proxy.AppStateHolder
+import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
+import com.tangem.tap.features.welcome.redux.WelcomeReducer
import org.rekotlin.Action
-fun appReducer(action: Action, state: AppState?): AppState {
+fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder): AppState {
requireNotNull(state)
if (action is AppAction.RestoreState) return action.state
return AppState(
navigationState = NavigationReducer.reduce(action, state),
- globalState = globalReducer(action, state),
+ globalState = globalReducer(action, state, appStateHolder),
homeState = HomeReducer.reduce(action, state),
onboardingNoteState = OnboardingNoteReducer.reduce(action, state),
onboardingWalletState = OnboardingWalletReducer.reduce(action, state),
onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state),
- walletState = WalletReducer.reduce(action, state),
+ walletState = WalletReducer.reduce(action, state, appStateHolder),
twinCardsState = TwinCardsReducer.reduce(action, state),
sendState = SendScreenReducer.reduce(action, state.sendState),
detailsState = DetailsReducer.reduce(action, state),
@@ -35,6 +39,9 @@ fun appReducer(action: Action, state: AppState?): AppState {
tokensState = TokensReducer.reduce(action, state),
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
shopState = ShopReducer.reduce(action, state.shopState),
+ welcomeState = WelcomeReducer.reduce(action, state),
+ saveWalletState = SaveWalletReducer.reduce(action, state),
+ walletSelectorState = WalletSelectorReducer.reduce(action, state),
)
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
index af79cb81e1..820f5b3d3f 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
@@ -25,6 +25,8 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayMiddleware
+import com.tangem.tap.features.saveWallet.redux.SaveWalletMiddleware
+import com.tangem.tap.features.saveWallet.redux.SaveWalletState
import com.tangem.tap.features.send.redux.middlewares.SendMiddleware
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.shop.redux.ShopMiddleware
@@ -33,6 +35,11 @@ import com.tangem.tap.features.tokens.redux.TokensMiddleware
import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware
+import com.tangem.tap.features.walletSelector.redux.WalletSelectorMiddleware
+import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
+import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
+import com.tangem.tap.features.welcome.redux.WelcomeState
+import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import org.rekotlin.Middleware
import org.rekotlin.StateType
@@ -51,6 +58,9 @@ data class AppState(
val tokensState: TokensState = TokensState(),
val walletConnectState: WalletConnectState = WalletConnectState(),
val shopState: ShopState = ShopState(),
+ val welcomeState: WelcomeState = WelcomeState(),
+ val saveWalletState: SaveWalletState = SaveWalletState(),
+ val walletSelectorState: WalletSelectorState = WalletSelectorState(),
) : StateType {
val domainState: DomainState
@@ -59,10 +69,18 @@ data class AppState(
val domainNetworks: NetworkServices
get() = domainState.globalState.networkServices
+ val featureRepositoryProvider: FeatureRepositoryProvider
+ get() = FeatureRepositoryProvider(
+ tangemTechApi = domainNetworks.tangemTechService.api,
+ dispatchers = AppCoroutineDispatcherProvider(),
+ )
+
companion object {
fun getMiddleware(): List> {
return listOf(
- logMiddleware, navigationMiddleware, notificationsMiddleware,
+ logMiddleware,
+ navigationMiddleware,
+ notificationsMiddleware,
GlobalMiddleware.handler,
HomeMiddleware.handler,
OnboardingNoteMiddleware.handler,
@@ -78,8 +96,12 @@ data class AppState(
WalletConnectMiddleware().walletConnectMiddleware,
BackupMiddleware().backupMiddleware,
ShopMiddleware().shopMiddleware,
+ WelcomeMiddleware().middleware,
+ SaveWalletMiddleware().middleware,
+ WalletSelectorMiddleware().middleware,
+ LockUserWalletsTimerMiddleware().middleware,
+ AccessCodeRequestPolicyMiddleware().middleware,
)
}
}
-}
-
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryProvider.kt b/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryProvider.kt
new file mode 100644
index 0000000000..5794edffb9
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/redux/FeatureRepositoryProvider.kt
@@ -0,0 +1,15 @@
+package com.tangem.tap.common.redux
+
+import com.tangem.datasource.api.tangemTech.TangemTechApi
+import com.tangem.tap.features.home.data.HomeRepositoryImpl
+import com.tangem.tap.features.home.domain.HomeRepository
+import com.tangem.tap.features.wallet.data.WalletRepositoryImpl
+import com.tangem.tap.features.wallet.domain.WalletRepository
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+
+class FeatureRepositoryProvider(tangemTechApi: TangemTechApi, dispatchers: CoroutineDispatcherProvider) {
+
+ val walletRepository: WalletRepository = WalletRepositoryImpl(tangemTechApi, dispatchers)
+
+ val homeRepository: HomeRepository = HomeRepositoryImpl(tangemTechApi, dispatchers)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/LockUserWalletsTimerMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/LockUserWalletsTimerMiddleware.kt
new file mode 100644
index 0000000000..67bb53a5b5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/redux/LockUserWalletsTimerMiddleware.kt
@@ -0,0 +1,15 @@
+package com.tangem.tap.common.redux
+
+import com.tangem.tap.lockUserWalletsTimer
+import org.rekotlin.Middleware
+
+class LockUserWalletsTimerMiddleware {
+ val middleware: Middleware = { _, _ ->
+ { nextDispatch ->
+ { action ->
+ lockUserWalletsTimer?.restart()
+ nextDispatch(action)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt
index a32545d675..4a7cb7b8f8 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt
@@ -88,6 +88,7 @@ val notificationsMiddleware: Middleware = { dispatch, state ->
}
}
+@Suppress("ComplexMethod")
private fun handleNotificationAction(action: Action) {
if (action is Debug && !BuildConfig.DEBUG) return
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 b226be3401..1417d013d1 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
@@ -5,7 +5,6 @@ import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError
import com.tangem.domain.common.ScanResponse
-import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.feedback.FeedbackData
import com.tangem.tap.common.feedback.FeedbackManager
@@ -30,9 +29,6 @@ sealed class GlobalAction : Action {
data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction
data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction
- // android resources
- data class SetResources(val resources: AndroidResources) : GlobalAction()
-
// dialogs
data class ShowDialog(val stateDialog: StateDialog) : GlobalAction()
object HideDialog : GlobalAction()
@@ -51,7 +47,7 @@ sealed class GlobalAction : Action {
* For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start.
* See more Onboarding.Start, CheckForUnfinishedBackup
*/
- object StartForUnfinishedBackup : Onboarding()
+ data class StartForUnfinishedBackup(val addedBackupCardsCount: Int) : Onboarding()
object Stop : Onboarding()
}
@@ -68,7 +64,7 @@ sealed class GlobalAction : Action {
object Increment : GlobalAction()
}
- data class SaveScanNoteResponse(val scanResponse: ScanResponse) : GlobalAction()
+ data class SaveScanResponse(val scanResponse: ScanResponse) : GlobalAction()
data class SetIfCardVerifiedOnline(val verified: Boolean) : GlobalAction()
@@ -89,7 +85,6 @@ sealed class GlobalAction : Action {
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction()
data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction()
- data class SetGlobalAnalyticsHandler(val analyticsHandler: GlobalAnalyticsEventHandler) : GlobalAction()
data class SendEmail(val feedbackData: FeedbackData) : GlobalAction()
data class OpenChat(val feedbackData: FeedbackData, val zendeskConfig: ZendeskConfig? = null) : 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/GlobalMiddleware.kt
similarity index 90%
rename from app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
rename to app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt
index 6f5af6bb92..b880c71872 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/GlobalMiddleware.kt
@@ -4,7 +4,6 @@ import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.ifNotNull
-import com.tangem.common.services.Result
import com.tangem.domain.common.LogConfig
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
@@ -31,10 +30,8 @@ import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
import java.util.*
-class GlobalMiddleware {
- companion object {
- val handler = globalMiddlewareHandler
- }
+object GlobalMiddleware {
+ val handler = globalMiddlewareHandler
}
private val globalMiddlewareHandler: Middleware = { dispatch, appState ->
@@ -46,6 +43,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState
}
}
+@Suppress("LongMethod", "ComplexMethod")
private fun handleAction(action: Action, appState: () -> AppState?, dispatch: DispatchFunction) {
when (action) {
is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
@@ -74,7 +72,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
store.state.globalState.warningManager?.let {
if (it.hideWarning(action.warning)) {
if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
- //TODO: No appropriate warningMessage identification. Make it better later
+ // TODO: No appropriate warningMessage identification. Make it better later
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
}
@@ -101,7 +99,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
// if config not set -> try to get it based on a scanResponse.productType
val unsafeZendeskConfig = action.zendeskConfig ?: when {
- scanResponse?.isSaltPay() == true -> config.saltPayConfig?.zendesk
+ scanResponse?.cardTypesResolver?.isSaltPay() == true -> config.saltPayConfig?.zendesk
else -> config.zendesk
}
@@ -167,10 +165,9 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
scope.launch {
tangemSdkManager.changeDisplayedCardIdNumbersCount(null)
val result = tangemSdkManager.scanProduct(
- store.state.globalState.analyticsHandler,
- userTokensRepository,
- action.additionalBlockchainsToDerive,
- action.messageResId,
+ userTokensRepository = userTokensRepository,
+ additionalBlockchainsToDerive = action.additionalBlockchainsToDerive,
+ messageRes = action.messageResId,
)
withMainContext {
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
@@ -188,23 +185,20 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
}
is GlobalAction.FetchUserCountry -> {
scope.launch {
- val techService = store.state.domainNetworks.tangemTechService
- when (val result = techService.userCountry()) {
- is Result.Success -> {
+ // TODO("After adding DI") get dependencies by DI
+ runCatching { store.state.featureRepositoryProvider.homeRepository.getUserCountryCode() }
+ .onSuccess {
store.dispatchOnMain(
- GlobalAction.FetchUserCountry.Success(
- countryCode = result.data.code.lowercase(),
- ),
+ GlobalAction.FetchUserCountry.Success(countryCode = it.code.lowercase()),
)
}
- is Result.Failure -> {
+ .onFailure {
store.dispatchOnMain(
GlobalAction.FetchUserCountry.Success(
countryCode = Locale.getDefault().country.lowercase(),
),
)
}
- }
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
index b0cefc6d9e..7934a195f6 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
@@ -2,21 +2,20 @@ package com.tangem.tap.common.redux.global
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
+import com.tangem.tap.common.extensions.replaceBy
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.preferencesStorage
+import com.tangem.tap.proxy.AppStateHolder
import org.rekotlin.Action
-fun globalReducer(action: Action, state: AppState): GlobalState {
-
+@Suppress("LongMethod", "ComplexMethod")
+fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolder): GlobalState {
if (action !is GlobalAction) return state.globalState
val globalState = state.globalState
return when (action) {
- is GlobalAction.SetResources -> {
- globalState.copy(resources = action.resources)
- }
is GlobalAction.Onboarding.Start -> {
val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage
val onboardingManager = OnboardingManager(action.scanResponse, usedCardsPrefStorage)
@@ -34,11 +33,13 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.ScanFailsCounter.Reset -> {
globalState.copy(scanCardFailsCounter = 0)
}
- is GlobalAction.SaveScanNoteResponse ->{
+ is GlobalAction.SaveScanResponse -> {
+ appStateHolder.scanResponse = action.scanResponse
domainStore.dispatch(DomainGlobalAction.SaveScanNoteResponse(action.scanResponse))
globalState.copy(scanResponse = action.scanResponse)
}
is GlobalAction.ChangeAppCurrency -> {
+ appStateHolder.appFiatCurrency = action.appCurrency
globalState.copy(appCurrency = action.appCurrency)
}
is GlobalAction.RestoreAppCurrency.Success -> {
@@ -48,15 +49,22 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
globalState.copy(configManager = action.configManager)
}
is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager)
- is GlobalAction.SetGlobalAnalyticsHandler -> globalState.copy(analyticsHandler = action.analyticsHandler)
is GlobalAction.UpdateWalletSignedHashes -> {
val card = globalState.scanResponse?.card ?: return globalState
- val wallet = card.wallet(action.walletPublicKey) ?: return globalState
+ val wallet = card.wallets
+ .firstOrNull { it.publicKey.contentEquals(action.walletPublicKey) }
+ ?: return globalState
- val newCardInstance = card.updateWallet(wallet.copy(
- totalSignedHashes = action.walletSignedHashes,
- remainingSignatures = action.remainingSignatures
- ))
+ val newCardInstance = card.copy(
+ wallets = card.wallets.toMutableList().also { walletsMutable ->
+ walletsMutable.replaceBy(
+ item = wallet.copy(
+ totalSignedHashes = action.walletSignedHashes,
+ remainingSignatures = action.remainingSignatures,
+ ),
+ ) { it.index == wallet.index }
+ },
+ )
globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance))
}
is GlobalAction.SetFeedbackManager -> {
@@ -74,7 +82,7 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.SetIfCardVerifiedOnline ->
globalState.copy(cardVerifiedOnline = action.verified)
is GlobalAction.FetchUserCountry.Success -> globalState.copy(
- userCountryCode = action.countryCode
+ userCountryCode = action.countryCode,
)
else -> globalState
}
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 a02172db23..b897f1b5dc 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
@@ -1,7 +1,6 @@
package com.tangem.tap.common.redux.global
import com.tangem.domain.common.ScanResponse
-import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.feedback.FeedbackManager
import com.tangem.tap.common.redux.StateDialog
@@ -26,20 +25,9 @@ data class GlobalState(
val scanCardFailsCounter: Int = 0,
val dialog: StateDialog? = null,
val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(),
- val resources: AndroidResources = AndroidResources(),
- val analyticsHandler: GlobalAnalyticsEventHandler = GlobalAnalyticsEventHandler.stub(),
val userCountryCode: String? = null,
) : StateType
-
-data class AndroidResources(
- val strings: RString = RString(),
-) {
- data class RString(
- val addressWasCopied: Int = -1,
- val walletIsNotEmpty: Int = -1,
- )
-}
typealias CryptoCurrencyName = String
data class OnboardingState(
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt
index 69547d020f..ab7aa07259 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/FragmentShareTransition.kt
@@ -10,7 +10,7 @@ import java.lang.ref.WeakReference
data class FragmentShareTransition(
val shareElements: List,
val enterTransitionSet: TransitionSet,
- val exitTransitionSet: TransitionSet
+ val exitTransitionSet: TransitionSet,
)
/**
@@ -24,12 +24,12 @@ class ShareElement(view: View, name: String? = null) {
init {
name?.let { view.transitionName = it }
elementName = view.transitionName
- ?: throw UnsupportedOperationException("ShareElement require the name")
+ ?: throw UnsupportedOperationException("ShareElement require the name")
wView = WeakReference(view)
}
companion object {
- val imvFrontCard = "imv_front_card"
- val imvBackCard = "imv_back_card"
+ const val imvFrontCard = "imv_front_card"
+ const val imvBackCard = "imv_back_card"
}
}
\ No newline at end of file
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 e397d78d8a..71f3274e4c 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,6 +1,7 @@
package com.tangem.tap.common.redux.navigation
import android.net.Uri
+import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.rekotlin.Action
import java.lang.ref.WeakReference
@@ -10,14 +11,20 @@ sealed class NavigationAction : Action {
val screen: AppScreen,
val fragmentShareTransition: FragmentShareTransition? = null,
val addToBackstack: Boolean = true,
+ val bundle: Bundle? = null,
) : NavigationAction()
- data class PopBackTo(val screen: AppScreen? = null) : NavigationAction()
+ data class PopBackTo(
+ val screen: AppScreen? = null,
+ val inclusive: Boolean = false,
+ ) : NavigationAction()
data class OpenUrl(val url: String) : NavigationAction()
data class OpenDocument(val url: Uri) : NavigationAction()
+ object OpenBiometricsSettings : 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 8a2f5c5ac1..bf5df6a9a9 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,14 +1,20 @@
package com.tangem.tap.common.redux.navigation
import android.content.Intent
+import android.hardware.biometrics.BiometricManager
+import android.os.Build
+import android.provider.Settings
+import com.tangem.tap.activityResultCaller
import com.tangem.tap.common.CustomTabsManager
+import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.openFragment
import com.tangem.tap.common.extensions.popBackTo
import com.tangem.tap.common.extensions.shareText
import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.store
import org.rekotlin.Middleware
-val navigationMiddleware: Middleware = { dispatch, state ->
+val navigationMiddleware: Middleware = { _, state ->
{ next ->
{ action ->
if (action is NavigationAction) {
@@ -16,16 +22,30 @@ val navigationMiddleware: Middleware = { dispatch, state ->
when (action) {
is NavigationAction.NavigateTo -> {
navState?.activity?.get()?.openFragment(
- action.screen,
- action.addToBackstack,
- action.fragmentShareTransition
+ screen = action.screen,
+ addToBackstack = action.addToBackstack,
+ fgShareTransition = action.fragmentShareTransition,
+ bundle = action.bundle,
)
}
is NavigationAction.PopBackTo -> {
- if (action.screen == AppScreen.Home) {
- navState?.activity?.get()?.popBackTo(null, true)
- } else {
- navState?.activity?.get()?.popBackTo(action.screen)
+ if (navState?.backStack?.lastOrNull() != action.screen) {
+ when (val screen = action.screen) {
+ AppScreen.Home,
+ AppScreen.Welcome,
+ -> {
+ if (navState?.backStack?.contains(screen) == false) {
+ // Pop back to activity
+ navState.activity?.get()?.popBackTo(screen = null, inclusive = true)
+ store.dispatchOnMain(NavigationAction.NavigateTo(screen))
+ } else {
+ navState?.activity?.get()?.popBackTo(screen, action.inclusive)
+ }
+ }
+ else -> {
+ navState?.activity?.get()?.popBackTo(screen, action.inclusive)
+ }
+ }
}
}
is NavigationAction.OpenUrl -> {
@@ -38,11 +58,31 @@ val navigationMiddleware: Middleware = { dispatch, state ->
intent.data = action.url
navState?.activity?.get()?.startActivity(intent)
}
- is NavigationAction.Share -> {
- navState?.activity?.get()?.let {
- it.shareText(action.data)
+ is NavigationAction.OpenBiometricsSettings -> {
+ val settingsAction = when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
+ Settings.ACTION_BIOMETRIC_ENROLL
+ }
+ else -> {
+ Settings.ACTION_SECURITY_SETTINGS
+ }
}
+ val intent = Intent(settingsAction).apply {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ putExtra(
+ Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG,
+ )
+ }
+ }
+ activityResultCaller.activityResultLauncher?.launch(intent)
}
+ is NavigationAction.Share -> {
+ navState?.activity?.get()?.shareText(action.data)
+ }
+ is NavigationAction.ActivityCreated,
+ is NavigationAction.ActivityDestroyed,
+ -> Unit
}
}
next(action)
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt
index 7d63cb95ee..7febcfa59c 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt
@@ -4,14 +4,11 @@ import com.tangem.tap.common.extensions.getPreviousScreen
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Action
-class NavigationReducer {
- companion object {
- fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state)
- }
+object NavigationReducer {
+ fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state)
}
private fun internalReduce(action: Action, state: AppState): NavigationState {
-
val navigationAction = action as? NavigationAction ?: return state.navigationState
val navState = state.navigationState
@@ -20,6 +17,8 @@ private fun internalReduce(action: Action, state: AppState): NavigationState {
navState.copy(backStack = navState.backStack + navigationAction.screen)
}
is NavigationAction.PopBackTo -> {
+ if (navState.backStack.lastOrNull() == navigationAction.screen) return navState
+
val screen = navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen()
val index = navState.backStack.lastIndexOf(screen) + 1
state.navigationState.copy(backStack = navState.backStack.subList(0, index))
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 224e12755b..0b40b7bd8e 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
@@ -5,11 +5,13 @@ import org.rekotlin.StateType
import java.lang.ref.WeakReference
data class NavigationState(
- val backStack: List = listOf(AppScreen.Home),
+ val backStack: List = emptyList(),
val activity: WeakReference? = null,
) : StateType
-enum class AppScreen {
+enum class AppScreen(
+ val isDialogFragment: Boolean = false,
+) {
Home,
Shop,
Disclaimer,
@@ -19,5 +21,10 @@ enum class AppScreen {
Details, DetailsSecurity, CardSettings, AppSettings, ResetToFactory,
AddTokens, AddCustomToken,
WalletConnectSessions,
- QrScan
+ QrScan,
+ ReferralProgram,
+ Swap,
+ Welcome,
+ SaveWallet(isDialogFragment = true),
+ WalletSelector(isDialogFragment = true),
}
\ No newline at end of file
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 c8bd0a5405..8366078322 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
@@ -4,12 +4,13 @@ import android.app.Application
import android.content.Intent
import com.google.android.gms.wallet.PaymentData
import com.shopify.buy3.Storefront
-import com.tangem.common.extensions.ifNotNull
-import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
+import com.tangem.core.analytics.Analytics
+import com.tangem.tap.common.analytics.converters.ShopOrderToEventConverter
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
+import com.tangem.tap.common.shop.googlepay.GooglePayService
import com.tangem.tap.common.shop.shopify.ShopifyService
import com.tangem.tap.common.shop.shopify.ShopifyShop
import com.tangem.tap.common.shop.shopify.data.CheckoutItem
@@ -23,7 +24,6 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
private val shopifyService = ShopifyService(application, shopifyShop)
-
private val checkouts = mutableMapOf()
private val variants = mutableMapOf()
@@ -41,24 +41,27 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
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 }}"))
+ return Result.failure(
+ Exception(
+ "Shopify: products are missing, " +
+ "\nproducts available: ${variants.keys.map { it.sku }}",
+ ),
+ )
}
val twoCardsProduct = TangemProduct(
type = ProductType.WALLET_2_CARDS,
totalSum = TotalSum(
finalValue = variants[ProductType.WALLET_2_CARDS]?.priceV2?.format(),
- beforeDiscount = variants[ProductType.WALLET_2_CARDS]?.compareAtPriceV2?.format()
- )
+ beforeDiscount = variants[ProductType.WALLET_2_CARDS]?.compareAtPriceV2?.format(),
+ ),
)
val threeCardsProduct = TangemProduct(
type = ProductType.WALLET_3_CARDS,
totalSum = TotalSum(
finalValue = variants[ProductType.WALLET_3_CARDS]?.priceV2?.format(),
- beforeDiscount = variants[ProductType.WALLET_3_CARDS]?.compareAtPriceV2?.format()
- )
+ beforeDiscount = variants[ProductType.WALLET_3_CARDS]?.compareAtPriceV2?.format(),
+ ),
)
createCheckouts()
return Result.success(listOf(twoCardsProduct, threeCardsProduct))
@@ -85,8 +88,9 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
fun buyWithGooglePay(productType: ProductType) {
val totalPrice = checkouts[productType]!!.totalPriceV2.amount
googlePayService.payWithGooglePay(
- totalPriceCents = totalPrice, currencyCode = checkouts[productType]!!.currencyCode.name,
- merchantID = shopifyService.shop.merchantID
+ totalPriceCents = totalPrice,
+ currencyCode = checkouts[productType]!!.currencyCode.name,
+ merchantID = shopifyService.shop.merchantID,
)
}
@@ -105,7 +109,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
suspend fun handleGooglePayResult(
resultCode: Int,
data: Intent?,
- productType: ProductType
+ productType: ProductType,
): Result {
val result = googlePayService.handleResponseFromGooglePay(resultCode, data)
result.onSuccess {
@@ -120,7 +124,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
private suspend fun completeTokenizedPayment(
paymentData: PaymentData,
- productType: ProductType
+ productType: ProductType,
): Result {
val checkout = checkouts[productType]!!
val googlePayResponse =
@@ -141,19 +145,18 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
phone = addressGPay.phoneNumber
}
-
val payment = Storefront.TokenizedPaymentInputV3(
amount,
idempotencyKey,
address,
paymentData.toJson(),
- Storefront.PaymentTokenType.GOOGLE_PAY
+ Storefront.PaymentTokenType.GOOGLE_PAY,
)
.setTest(true)
return shopifyService.completeWithTokenizedPayment(
payment = payment,
- checkoutID = checkout.id
+ checkoutID = checkout.id,
)
}
@@ -182,10 +185,10 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
productType,
TotalSum(
finalValue = it.totalPriceV2.format(),
- beforeDiscount = variants[productType]!!.compareAtPriceV2.format()
+ beforeDiscount = variants[productType]!!.compareAtPriceV2.format(),
),
- appliedDiscount = it.getAppliedDiscount()
- )
+ appliedDiscount = it.getAppliedDiscount(),
+ ),
)
}
@@ -196,16 +199,19 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
return checkouts[productType]!!.webUrl
}
- suspend fun waitForCheckout(productType: ProductType, analyticsHandler: GlobalAnalyticsEventHandler?) {
+ suspend fun waitForCheckout(productType: ProductType) {
val result = shopifyService.checkout(true, checkouts[productType]!!.id)
- result.onSuccess {
- ifNotNull(analyticsHandler, it.order) { handler, order -> handler.handleShopifyOrderEvent(order) }
+ result.onSuccess { checkout ->
+ if (checkout.order != null && checkout.lineItems != null) {
+ val event = ShopOrderToEventConverter().convert(checkout to productType)
+ Analytics.send(event)
+ }
}
}
companion object {
- const val TANGEM_WALLET_2_CARDS_SKU = "TG115x2"
- const val TANGEM_WALLET_3_CARDS_SKU = "TG115x3"
+ const val TANGEM_WALLET_2_CARDS_SKU = "TG115X2-S"
+ const val TANGEM_WALLET_3_CARDS_SKU = "TG115X3-S"
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/googlepay/GooglePayService.kt b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt
index 13c3913ebe..757b207741 100644
--- a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt
+++ b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt
@@ -1,4 +1,4 @@
-package com.tangem.tap.common.shop
+package com.tangem.tap.common.shop.googlepay
import android.app.Activity
import android.app.Activity.RESULT_CANCELED
@@ -6,9 +6,12 @@ import android.app.Activity.RESULT_OK
import android.content.Intent
import android.util.Log
import com.google.android.gms.common.api.ApiException
-import com.google.android.gms.wallet.*
+import com.google.android.gms.wallet.AutoResolveHelper
+import com.google.android.gms.wallet.IsReadyToPayRequest
+import com.google.android.gms.wallet.PaymentData
+import com.google.android.gms.wallet.PaymentDataRequest
+import com.google.android.gms.wallet.PaymentsClient
import com.tangem.common.core.TangemSdkError
-import com.tangem.tap.common.shop.googlepay.GooglePayUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONException
@@ -22,7 +25,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
// var responseCallback: ((Result) -> Unit)? = null
suspend fun checkIfGooglePayAvailable(): Result {
-
val isReadyToPayJson = GooglePayUtil.isReadyToPayRequest() ?: return Result.success(false)
val request = IsReadyToPayRequest.fromJson(isReadyToPayJson.toString())
@@ -48,7 +50,7 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
totalPriceCents,
currencyCode = currencyCode,
countryCode = "RU",
- merchantID = merchantID
+ merchantID = merchantID,
)
if (paymentDataRequestJson == null) {
Timber.e("RequestPayment: can't fetch payment data request")
@@ -57,7 +59,9 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString())
AutoResolveHelper.resolveTask(
- paymentsClient.loadPaymentData(request), activity, LOAD_PAYMENT_DATA_REQUEST_CODE
+ paymentsClient.loadPaymentData(request),
+ activity,
+ LOAD_PAYMENT_DATA_REQUEST_CODE,
)
}
@@ -81,7 +85,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
} else {
Result.failure(Exception("$statusCode"))
}
-
}
else -> Result.failure(Exception("Unknown Status"))
}
@@ -117,7 +120,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
.getString("token")
return GooglePayResponse(address, token)
-
} catch (e: JSONException) {
Log.e("handlePaymentSuccess", "Error: " + e.toString())
}
@@ -144,5 +146,5 @@ data class Address(
val address3: String,
val locality: String,
val administrativeArea: String,
- val sortingCode: String
+ val sortingCode: String,
)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt
index 610ab10c9d..82509e1180 100644
--- a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt
+++ b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt
@@ -14,20 +14,7 @@ object GooglePayUtil {
put("apiVersionMinor", 0)
}
- private fun gatewayTokenizationSpecification(merchantID: String): JSONObject {
- return JSONObject().apply {
- put("type", "PAYMENT_GATEWAY")
- put(
- "parameters", JSONObject(
- mapOf(
- "gateway" to "shopify",
- "gatewayMerchantId" to merchantID
- )
- )
- )
- }
- }
-
+ @Suppress("MagicNumber")
private val allowedCardNetworks = JSONArray(
listOf(
"AMEX",
@@ -35,27 +22,46 @@ object GooglePayUtil {
"INTERAC",
"JCB",
"MASTERCARD",
- "VISA"
- )
+ "VISA",
+ ),
)
private val allowedCardAuthMethods = JSONArray(
listOf(
"PAN_ONLY",
- "CRYPTOGRAM_3DS"
- )
+ "CRYPTOGRAM_3DS",
+ ),
)
+ private val merchantInfo: JSONObject = JSONObject().put("merchantName", "Example Merchant")
+
+ private fun gatewayTokenizationSpecification(merchantID: String): JSONObject {
+ return JSONObject().apply {
+ put("type", "PAYMENT_GATEWAY")
+ put(
+ "parameters",
+ JSONObject(
+ mapOf(
+ "gateway" to "shopify",
+ "gatewayMerchantId" to merchantID,
+ ),
+ ),
+ )
+ }
+ }
+
private fun baseCardPaymentMethod(): JSONObject {
return JSONObject().apply {
-
val parameters = JSONObject().apply {
put("allowedAuthMethods", allowedCardAuthMethods)
put("allowedCardNetworks", allowedCardNetworks)
put("billingAddressRequired", true)
- put("billingAddressParameters", JSONObject().apply {
- put("format", "FULL")
- })
+ put(
+ "billingAddressParameters",
+ JSONObject().apply {
+ put("format", "FULL")
+ },
+ )
}
put("type", "CARD")
@@ -83,7 +89,6 @@ object GooglePayUtil {
baseRequest.apply {
put("allowedPaymentMethods", JSONArray().put(baseCardPaymentMethod()))
}
-
} catch (e: JSONException) {
null
}
@@ -92,7 +97,7 @@ object GooglePayUtil {
private fun getTransactionInfo(
price: String,
countryCode: String,
- currencyCode: String
+ currencyCode: String,
): JSONObject {
return JSONObject().apply {
put("totalPrice", price)
@@ -102,15 +107,11 @@ object GooglePayUtil {
}
}
- private val merchantInfo: JSONObject =
- JSONObject().put("merchantName", "Example Merchant")
-
-
fun getPaymentDataRequest(
price: String,
countryCode: String,
currencyCode: String,
- merchantID: String
+ merchantID: String,
): JSONObject? {
try {
return baseRequest.apply {
@@ -133,5 +134,4 @@ object GooglePayUtil {
}
}
-
const val PAYMENTS_ENVIRONMENT = WalletConstants.ENVIRONMENT_TEST
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt
index a33fb82b84..f51fbf0c4e 100644
--- a/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt
+++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt
@@ -16,11 +16,10 @@ import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
-
+@Suppress("LargeClass")
class ShopifyService(private val application: Application, val shop: ShopifyShop) {
val client: GraphClient by lazy { initClient() }
-
suspend fun getShopName(): Result {
val query = query { rootQuery: QueryRootQuery ->
rootQuery
@@ -40,16 +39,15 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
}
}
+ @Suppress("MagicNumber")
suspend fun getProducts(collectionTitleFilter: String? = null): Result> {
val filter = collectionTitleFilter?.let { "title:\"$it\"" }
val query = query { rootQuery: QueryRootQuery ->
- rootQuery
- .collections(
- { arg -> arg.first(250).query(filter) },
- ) { collectionConnectionQuery ->
- collectionConnectionQuery.collectionFieldsFragment()
- }
+ rootQuery.collections(
+ { arg -> arg.first(250).query(filter) },
+ CollectionConnectionQuery::collectionFieldsFragment,
+ )
}
return when (val result = queryAsync(query)) {
is GraphCallResult.Success -> {
@@ -66,7 +64,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
}
suspend fun checkout(pollUntilOrder: Boolean, checkoutID: ID): Result {
-
val query = query { rootQuery: QueryRootQuery ->
rootQuery
.node(checkoutID) { query ->
@@ -78,7 +75,8 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
}
}
val retryHandler = RetryHandler.build(
- 1, TimeUnit.SECONDS
+ delay = 1,
+ timeUnit = TimeUnit.SECONDS,
) {
this.retryWhen { result ->
when (result) {
@@ -100,7 +98,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
} else {
Result.failure(ShopifyError.Unknown)
}
-
}
is GraphCallResult.Failure -> {
Result.failure(result.error)
@@ -110,10 +107,8 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
suspend fun createCheckout(
checkoutItems: List,
- checkoutID: ID? = null
+ checkoutID: ID? = null,
): Result {
-
-
val storefrontLineItems: MutableList = checkoutItems
.map { CheckoutLineItemInput(it.quantity, it.id) }.toMutableList()
@@ -121,13 +116,14 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
mutation { mutationQuery: MutationQuery ->
mutationQuery
.checkoutLineItemsReplace(
- storefrontLineItems, checkoutID
+ storefrontLineItems,
+ checkoutID,
) { payloadQuery: CheckoutLineItemsReplacePayloadQuery ->
payloadQuery
.checkout { checkoutQuery: CheckoutQuery ->
checkoutQuery.checkoutFieldsFragment()
}
- .userErrors() { userErrorQuery: CheckoutUserErrorQuery ->
+ .userErrors { userErrorQuery: CheckoutUserErrorQuery ->
userErrorQuery
.field()
.message()
@@ -137,18 +133,18 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
} else {
val input = CheckoutCreateInput()
.setLineItemsInput(
- Input.value(storefrontLineItems)
+ Input.value(storefrontLineItems),
)
mutation { mutationQuery: MutationQuery ->
mutationQuery
.checkoutCreate(
- input
+ input,
) { payloadQuery: CheckoutCreatePayloadQuery ->
payloadQuery
.checkout { checkoutQuery: CheckoutQuery ->
checkoutQuery.checkoutFieldsFragment()
}
- .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
+ .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery ->
userErrorQuery
.field()
.message()
@@ -163,13 +159,14 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
val query = mutation { mutationQuery: MutationQuery ->
mutationQuery
.checkoutDiscountCodeApplyV2(
- discountCode, checkoutID
+ discountCode,
+ checkoutID,
) { payloadQuery: CheckoutDiscountCodeApplyV2PayloadQuery ->
payloadQuery
.checkout { checkoutQuery: CheckoutQuery ->
checkoutQuery.checkoutFieldsFragment()
}
- .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
+ .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery ->
userErrorQuery
.field()
.message()
@@ -183,44 +180,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
val query = mutation { mutationQuery: MutationQuery ->
mutationQuery
.checkoutDiscountCodeRemove(
- checkoutID
+ checkoutID,
) { payloadQuery: CheckoutDiscountCodeRemovePayloadQuery ->
payloadQuery
- .checkout { checkoutQuery: CheckoutQuery ->
- checkoutQuery.checkoutFieldsFragment()
- }
- .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
- userErrorQuery
- .field()
- .message()
- }
- }
- }
- return runCheckoutMutation(query)
- }
-
- suspend fun updateAddress(
- address: MailingAddress,
- checkoutID: ID,
- waitForShippingRates: Boolean
- ): Result {
- val input = MailingAddressInput()
- .setAddress1(address.address1)
- .setAddress2(address.address2)
- .setCity(address.city)
- .setCountry(address.country)
- .setFirstName(address.firstName)
- .setLastName(address.lastName)
- .setPhone(address.phone)
- .setProvince(address.province)
- .setZip(address.zip)
-
- val query = mutation { mutationQuery: MutationQuery ->
- mutationQuery
- .checkoutShippingAddressUpdateV2(
- input, checkoutID
- ) { shippingAddressUpdatePayloadQuery: CheckoutShippingAddressUpdateV2PayloadQuery ->
- shippingAddressUpdatePayloadQuery
.checkout { checkoutQuery: CheckoutQuery ->
checkoutQuery.checkoutFieldsFragment()
}
@@ -231,59 +193,18 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
}
}
}
-
- return runCheckoutMutation(query)
- }
-
- suspend fun updateEmail(email: String?, checkoutID: ID): Result {
- val query = mutation { mutationQuery: MutationQuery ->
- mutationQuery
- .checkoutEmailUpdateV2(
- checkoutID, email
- ) { emailUpdatePayloadQuery: CheckoutEmailUpdateV2PayloadQuery ->
- emailUpdatePayloadQuery
- .checkout { checkoutQuery: CheckoutQuery ->
- checkoutQuery.checkoutFieldsFragment()
- }
- .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
- userErrorQuery
- .field()
- .message()
- }
- }
- }
- return runCheckoutMutation(query)
- }
-
- suspend fun updateShippingRate(handle: String?, checkoutID: ID): Result {
- val query = mutation { mutationQuery: MutationQuery ->
- mutationQuery
- .checkoutShippingLineUpdate(
- checkoutID, handle
- ) { shippingLineUpdatePayloadQuery: CheckoutShippingLineUpdatePayloadQuery ->
- shippingLineUpdatePayloadQuery
- .checkout { checkoutQuery: CheckoutQuery ->
- checkoutQuery.checkoutFieldsFragment()
- }
- .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
- userErrorQuery
- .field()
- .message()
- }
- }
- }
return runCheckoutMutation(query)
}
suspend fun completeWithTokenizedPayment(
payment: TokenizedPaymentInputV3,
- checkoutID: ID
+ checkoutID: ID,
): Result {
-
val query = mutation { mutationQuery: MutationQuery ->
mutationQuery
.checkoutCompleteWithTokenizedPaymentV3(
- checkoutID, payment
+ checkoutID,
+ payment,
) { payloadQuery: CheckoutCompleteWithTokenizedPaymentV3PayloadQuery ->
payloadQuery
.payment { paymentQuery: PaymentQuery ->
@@ -295,7 +216,7 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
checkoutQuery
.ready()
}
- .checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
+ .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery ->
userErrorQuery
.field()
.message()
@@ -305,10 +226,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
return runCheckoutMutation(query)
}
- fun startGooglePaySession() {
-// PaySession()
- }
-
private suspend fun runCheckoutMutation(mutation: MutationQuery): Result {
return when (val result = mutationQueryAsync(mutation)) {
is GraphCallResult.Success -> {
@@ -322,13 +239,13 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
Result.success(checkout)
}
- is GraphCallResult.Failure -> Result.failure(result.error)
+ is GraphCallResult.Failure -> Result.failure(result.error)
}
}
private suspend fun queryAsync(
query: QueryRootQuery,
- retryHandler: RetryHandler
+ retryHandler: RetryHandler,
): GraphCallResult =
withContext(Dispatchers.IO) {
suspendCoroutine { continuation ->
@@ -358,12 +275,11 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
}
}
-
private fun initClient(): GraphClient {
return GraphClient.build(
application,
shop.domain,
- shop.storefrontApiKey
+ shop.storefrontApiKey,
) {
// httpCache(application.filesDir) {
// cacheMaxSizeBytes = (1024 * 1024 * 10)
@@ -371,13 +287,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
// HttpCachePolicy.Default.CACHE_FIRST.expireAfter(20, TimeUnit.MINUTES)
// }
}
-
}
}
-
sealed class ShopifyError : Throwable() {
object Unknown : ShopifyError()
- object GooglePayFailed : ShopifyError()
- class UserError(val errorMessage: String)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt
index 849c1f33c3..7565db2447 100644
--- a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt
+++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt
@@ -2,23 +2,24 @@ package com.tangem.tap.common.shop.shopify.data
import com.shopify.buy3.Storefront
+@Suppress("LongMethod", "MagicNumber")
fun Storefront.CheckoutQuery.checkoutFieldsFragment() {
- // id()
ready()
webUrl()
currencyCode()
lineItemsSubtotalPrice { it.amount() }
totalPriceV2 {
it.currencyCode()
- it.amount() }
+ it.amount()
+ }
lineItems({ arg -> arg.first(250) }) {
it.edges {
it.node {
// it.id()
it.title()
it.quantity()
- it.variant() {
- it.priceV2() { it.amount() }
+ it.variant {
+ it.priceV2 { it.amount() }
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt
index 572a7b68c7..1527fc9558 100644
--- a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt
+++ b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt
@@ -2,13 +2,15 @@ package com.tangem.tap.common.shop.shopify.data
import com.shopify.buy3.Storefront
+@Suppress("MagicNumber")
fun Storefront.CollectionConnectionQuery.collectionFieldsFragment() {
edges { collectionEdgeQuery ->
collectionEdgeQuery
.node { collectionQuery ->
collectionQuery
.title()
- .products({ arg -> arg.first(250) }
+ .products(
+ { arg -> arg.first(250) },
) { productConnectionQuery ->
productConnectionQuery
.edges { productEdgeQuery ->
diff --git a/app/src/main/java/com/tangem/tap/common/snackBar/MaxAmountSnackbar.kt b/app/src/main/java/com/tangem/tap/common/snackBar/MaxAmountSnackbar.kt
index 69f2114528..d224143c98 100644
--- a/app/src/main/java/com/tangem/tap/common/snackBar/MaxAmountSnackbar.kt
+++ b/app/src/main/java/com/tangem/tap/common/snackBar/MaxAmountSnackbar.kt
@@ -17,15 +17,15 @@ import com.tangem.wallet.R
[REDACTED_AUTHOR]
*/
class MaxAmountSnackbar(
- parent: ViewGroup,
- content: MaxAmountSnackbarView
+ parent: ViewGroup,
+ content: MaxAmountSnackbarView
) : BaseTransientBottomBar(parent, content, content) {
companion object {
fun make(view: View, onClick: () -> Unit): MaxAmountSnackbar {
val parent = view.findSuitableParent() ?: throw IllegalArgumentException(
- "No suitable parent found from the given view. Please provide a valid view."
+ "No suitable parent found from the given view. Please provide a valid view."
)
val inflater = LayoutInflater.from(view.context)
val customView = inflater.inflate(R.layout.view_snackbar_max_amount, parent, false) as MaxAmountSnackbarView
@@ -57,14 +57,13 @@ class MaxAmountSnackbar(
} while (view != null)
return fallback
}
-
}
}
class MaxAmountSnackbarView @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), ContentViewCallback {
init {
@@ -72,10 +71,9 @@ class MaxAmountSnackbarView @JvmOverloads constructor(
clipToPadding = false
}
-
override fun animateContentIn(delay: Int, duration: Int) {
}
override fun animateContentOut(delay: Int, duration: Int) {
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt b/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt
index 689095c534..2201e794cc 100644
--- a/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt
+++ b/app/src/main/java/com/tangem/tap/common/text/DecimalDigitsInputFilter.kt
@@ -8,13 +8,23 @@ import java.util.regex.Pattern
[REDACTED_AUTHOR]
*/
class DecimalDigitsInputFilter(
- digitsBeforeDecimal: Int,
- digitsAfterDecimal: Int,
- private val decimalSeparator: String
+ digitsBeforeDecimal: Int,
+ digitsAfterDecimal: Int,
+ private val decimalSeparator: String,
) : InputFilter {
- private val pattern: Pattern = Pattern.compile("(([1-9]{1}[0-9]{0,${digitsBeforeDecimal - 1}})?||[0]{1})((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?")
+ private val pattern: Pattern = Pattern.compile(
+ "(([1-9]{1}[0-9]{0,${digitsBeforeDecimal - 1}})?||[0]{1})" +
+ "((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?",
+ )
- override fun filter(source: CharSequence, sourceStart: Int, sourceEnd: Int, destination: Spanned, destinationStart: Int, destinationEnd: Int): CharSequence? {
+ override fun filter(
+ source: CharSequence,
+ sourceStart: Int,
+ sourceEnd: Int,
+ destination: Spanned,
+ destinationStart: Int,
+ destinationEnd: Int,
+ ): CharSequence? {
val destString = destination.toString()
val prefix = destString.substring(0, destinationStart)
val suffix = destString.substring(destinationEnd, destString.length)
@@ -47,4 +57,4 @@ class DecimalDigitsInputFilter(
}
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/text/Truncate.kt b/app/src/main/java/com/tangem/tap/common/text/Truncate.kt
index 3ed50ca1d2..0aeb17d6d2 100644
--- a/app/src/main/java/com/tangem/tap/common/text/Truncate.kt
+++ b/app/src/main/java/com/tangem/tap/common/text/Truncate.kt
@@ -16,9 +16,9 @@ interface Truncate {
companion object {
fun create(type: TruncateType): Truncate {
return when (type) {
- TruncateType.START -> TruncateStart()
- TruncateType.MIDDLE -> TruncateMiddle()
- TruncateType.END -> TruncateEnd()
+ TruncateType.START -> TruncateStart()
+ TruncateType.MIDDLE -> TruncateMiddle()
+ TruncateType.END -> TruncateEnd()
}
}
}
@@ -127,10 +127,10 @@ fun TextView.truncateWith(text: String, type: TruncateType, with: String = "..."
}
fun TextView.truncateStartWith(text: String, with: String = "..."): String =
- this.truncateWith(text, TruncateType.START, with)
+ this.truncateWith(text, TruncateType.START, with)
fun TextView.truncateMiddleWith(text: String, with: String = "..."): String =
- this.truncateWith(text, TruncateType.MIDDLE, with)
+ this.truncateWith(text, TruncateType.MIDDLE, with)
fun TextView.truncateEndWith(text: String, with: String = "..."): String =
- this.truncateWith(text, TruncateType.END, with)
\ No newline at end of file
+ this.truncateWith(text, TruncateType.END, with)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt
index 4b15ee3cb5..56f80d5d78 100644
--- a/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt
+++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/IndeterminateProgressButtonWidget.kt
@@ -38,6 +38,7 @@ open class IndeterminateProgressButtonWidget(
when (progressState) {
ProgressState.Done, ProgressState.Error -> switchToNone()
ProgressState.Loading -> switchToProgress()
+ else -> {}
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt
index 7bccdffc08..6a5bd17f60 100644
--- a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt
+++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt
@@ -17,6 +17,7 @@ import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
+@Suppress("MagicNumber")
class RefreshBalanceWidget(
private val root: ViewGroup,
) : ViewStateWidget {
@@ -57,6 +58,7 @@ class RefreshBalanceWidget(
viewSwitcher.showNext()
}
+ @Suppress("MagicNumber")
private fun animateState(state: ProgressState) {
when (state) {
ProgressState.Done, ProgressState.Error -> {
@@ -65,15 +67,25 @@ class RefreshBalanceWidget(
}
ProgressState.Loading -> {
progressViewAnimation = RotateAnimation(
- 0f, 360f,
- Animation.RELATIVE_TO_SELF, 0.5f,
- Animation.RELATIVE_TO_SELF, 0.5f,
+ /* fromDegrees = */
+ 0f,
+ /* toDegrees = */
+ 360f,
+ /* pivotXType = */
+ Animation.RELATIVE_TO_SELF,
+ /* pivotXValue = */
+ 0.5f,
+ /* pivotYType = */
+ Animation.RELATIVE_TO_SELF,
+ /* pivotYValue = */
+ 0.5f,
)
progressViewAnimation?.duration = 700
progressViewAnimation?.interpolator = AccelerateInterpolator()
progressViewAnimation?.repeatCount = -1
progressViewAnimation?.let { arrowView.startAnimation(it) }
}
+ else -> {}
}
}
@@ -93,14 +105,19 @@ class RefreshBalanceWidget(
private fun getState(): ProgressState = if (isArrowRefreshActive()) ProgressState.Done else ProgressState.Loading
}
+@Suppress("MagicNumber")
class ShowAnimation : AnimationSet(true) {
init {
addAnimation(
ScaleAnimation(
- 0f, 1f,
- 0f, 1f,
- Animation.RELATIVE_TO_SELF, 0.5f,
- Animation.RELATIVE_TO_SELF, 0.5f,
+ 0f,
+ 1f,
+ 0f,
+ 1f,
+ Animation.RELATIVE_TO_SELF,
+ 0.5f,
+ Animation.RELATIVE_TO_SELF,
+ 0.5f,
),
)
addAnimation(AlphaAnimation(0f, 1f))
diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt
index 48ba0a7291..10b6a1f7a6 100644
--- a/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt
+++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/ViewStateWidget.kt
@@ -2,7 +2,6 @@ package com.tangem.tap.common.toggleWidget
import android.view.View
-
/**
[REDACTED_AUTHOR]
*/
diff --git a/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt b/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt
index 32785f0147..e250846a42 100644
--- a/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt
+++ b/app/src/main/java/com/tangem/tap/common/transitions/OnboardingTransitions.kt
@@ -1,33 +1,13 @@
package com.tangem.tap.common.transitions
-import androidx.transition.*
-
-/**
-[REDACTED_AUTHOR]
- */
-class FrontCardEnterTransition : TransitionSet() {
- init {
- ordering = ORDERING_TOGETHER;
- addTransition(Fade())
- addTransition(ChangeImageTransform()) // изменения внутри ImageView
- addTransition(ChangeTransform()) // изменение размеров, углов накона
- addTransition(ChangeBounds()) // изменение положения
- }
-}
-
-class FrontCardExitTransition : TransitionSet() {
- init {
- ordering = ORDERING_TOGETHER;
- addTransition(Fade())
- addTransition(ChangeImageTransform())
- addTransition(ChangeTransform())
- addTransition(ChangeBounds())
- }
-}
+import androidx.transition.ChangeBounds
+import androidx.transition.ChangeTransform
+import androidx.transition.Fade
+import androidx.transition.TransitionSet
class HomeToOnboardingTransition : TransitionSet() {
init {
- ordering = ORDERING_TOGETHER;
+ ordering = ORDERING_TOGETHER
addTransition(Fade())
addTransition(ChangeTransform())
addTransition(ChangeBounds())
@@ -36,7 +16,7 @@ class HomeToOnboardingTransition : TransitionSet() {
class InternalNoteLayoutTransition : TransitionSet() {
init {
- ordering = ORDERING_TOGETHER;
+ ordering = ORDERING_TOGETHER
addTransition(ChangeTransform())
addTransition(ChangeBounds())
addTransition(Fade())
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
index 8cd6e7e5ba..365f235f11 100644
--- a/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt
+++ b/app/src/main/java/com/tangem/tap/common/ui/SimpleAlertDialog.kt
@@ -6,54 +6,49 @@ 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
- )
- }
+object SimpleAlertDialog {
+ 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()
- }
+object SimpleCancelableAlertDialog {
+ 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/domain/PayIdManager.kt b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
index cfd5c2b5c2..d66ac2ed73 100644
--- a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
@@ -10,7 +10,11 @@ import java.util.*
class PayIdManager {
- suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result = withContext(Dispatchers.IO) {
+ @Suppress("MagicNumber")
+ suspend fun verifyPayId(
+ payId: String,
+ blockchain: Blockchain,
+ ): Result = withContext(Dispatchers.IO) {
val splitPayId = payId.split("\$")
val user = splitPayId[0]
val baseUrl = "https://${splitPayId[1]}/"
@@ -22,25 +26,26 @@ class PayIdManager {
Blockchain.XRP -> "XRPL"
Blockchain.RSK -> "RSK"
else -> this.currency
- }.toLowerCase()
+ }.lowercase(Locale.getDefault())
}
companion object {
- val payIdRegExp = "^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$".toRegex()
+ private val payIdRegExp = ("^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9]" +
+ "(?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$").toRegex()
val payIdSupported: EnumSet = EnumSet.of(
- Blockchain.XRP,
- Blockchain.Ethereum,
- Blockchain.Bitcoin,
- Blockchain.Litecoin,
- Blockchain.Stellar,
- Blockchain.Cardano,
- Blockchain.CardanoShelley,
- Blockchain.Ducatus,
- Blockchain.BitcoinCash,
- Blockchain.Binance,
- Blockchain.RSK,
- Blockchain.Tezos
+ Blockchain.XRP,
+ Blockchain.Ethereum,
+ Blockchain.Bitcoin,
+ Blockchain.Litecoin,
+ Blockchain.Stellar,
+ Blockchain.Cardano,
+ Blockchain.CardanoShelley,
+ Blockchain.Ducatus,
+ Blockchain.BitcoinCash,
+ Blockchain.Binance,
+ Blockchain.RSK,
+ Blockchain.Tezos,
)
fun isPayId(value: String?): Boolean = value?.contains(payIdRegExp) ?: false
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 8b4136e90f..3d2a5764e2 100644
--- a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt
@@ -1,48 +1,49 @@
package com.tangem.tap.domain
import com.tangem.common.services.Result
+import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.domain.common.ThrottlerWithValues
-import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.tap.features.wallet.models.Currency
-import com.tangem.tap.store
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.withContext
import java.math.BigDecimal
-//TODO: refactoring: move to domain
-class RatesRepository {
-
- private val tangemTechService: TangemTechService
- get() = store.state.domainNetworks.tangemTechService
-
+class RatesRepository(
+ private val tangemTechApi: TangemTechApi,
+ private val dispatchers: CoroutineDispatcherProvider,
+) {
private val throttler = ThrottlerWithValues?>(60000)
- suspend fun loadFiatRate(currencyId: String, coinsList: List): Result {
-// get and submit previous result of equivalents.
- val throttledResult = coinsList.filter { throttler.isStillThrottled(it) }.map {
- Pair(it, throttler.geValue(it))
- }
-
- val currenciesToUpdate = coinsList.filter { !throttler.isStillThrottled(it) }
- val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct()
- if (coinIds.isEmpty()) return handleFiatRatesResult(throttledResult.toMap())
-
- return when (val result = tangemTechService.rates(currencyId, coinIds)) {
- is Result.Success -> {
- val ratesResultList: Map> = result.data.rates.mapValues {
- Result.Success(it.value.toBigDecimal())
- }
- val updatedCurrencies = throttledResult.toMap().toMutableMap()
- coinsList.forEach { currency ->
- ratesResultList[currency.coinId]?.let {
- updatedCurrencies[currency] = it
- throttler.updateThrottlingTo(currency)
- throttler.setValue(currency, it)
- }
- }
- handleFiatRatesResult(updatedCurrencies)
+ suspend fun loadFiatRate(currencyId: String, coinsList: List): Result =
+ withContext(dispatchers.io) {
+ // get and submit previous result of equivalents.
+ val throttledResult = coinsList.filter { throttler.isStillThrottled(it) }.map {
+ Pair(it, throttler.geValue(it))
}
- is Result.Failure -> Result.Failure(result.error)
+
+ val currenciesToUpdate = coinsList.filter { !throttler.isStillThrottled(it) }
+ val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct()
+ if (coinIds.isEmpty()) return@withContext handleFiatRatesResult(throttledResult.toMap())
+
+ runCatching { tangemTechApi.getRates(currencyId.lowercase(), coinIds.joinToString(",")) }
+ .onSuccess { response ->
+ val ratesResultList: Map> = response.rates.mapValues {
+ Result.Success(it.value.toBigDecimal())
+ }
+ val updatedCurrencies = throttledResult.toMap().toMutableMap()
+ coinsList.forEach { currency ->
+ ratesResultList[currency.coinId]?.let {
+ updatedCurrencies[currency] = it
+ throttler.updateThrottlingTo(currency)
+ throttler.setValue(currency, it)
+ }
+ }
+ return@withContext handleFiatRatesResult(updatedCurrencies)
+ }
+ .onFailure { Result.Failure(it) }
+
+ error("Unreachable code because runCatching must return result")
}
- }
private fun handleFiatRatesResult(rates: Map?>): Result.Success {
val success = mutableMapOf()
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 70576a26b9..288d399817 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
@@ -1,7 +1,5 @@
package com.tangem.tap.domain
-import CreateProductWalletTask
-import CreateProductWalletTaskResponse
import android.content.Context
import androidx.annotation.StringRes
import com.tangem.Message
@@ -10,14 +8,21 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CardFilter
import com.tangem.common.CompletionResult
import com.tangem.common.SuccessResponse
-import com.tangem.common.card.Card
+import com.tangem.common.UserCode
+import com.tangem.common.UserCodeType
+import com.tangem.common.biometric.BiometricManager
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.CardIdDisplayFormat
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.Config
import com.tangem.common.core.TangemSdkError
+import com.tangem.common.core.UserCodeRequestPolicy
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.hdWallet.DerivationPath
+import com.tangem.common.map
+import com.tangem.common.usersCode.UserCodeRepository
+import com.tangem.core.analytics.Analytics
+import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.ScanResponse
import com.tangem.operations.CommandResponse
import com.tangem.operations.ScanTask
@@ -26,15 +31,13 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.pins.CheckUserCodesCommand
import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.operations.pins.SetUserCodeCommand
-import com.tangem.tap.common.analytics.AnalyticsAnOld
-import com.tangem.tap.common.analytics.AnalyticsEventAnOld
-import com.tangem.tap.common.analytics.AnalyticsParamAnOld
-import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler
+import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
+import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
+import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
import com.tangem.tap.domain.tasks.product.ScanProductTask
import com.tangem.tap.domain.tokens.UserTokensRepository
-import com.tangem.tap.features.demo.DemoHelper
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
@@ -44,67 +47,67 @@ import kotlin.coroutines.suspendCoroutine
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
+ private val userCodeRepository by lazy {
+ UserCodeRepository(
+ biometricManager = tangemSdk.biometricManager,
+ secureStorage = tangemSdk.secureStorage,
+ )
+ }
+
+ val canUseBiometry: Boolean
+ get() = tangemSdk.biometricManager.canAuthenticate || needEnrollBiometrics
+
+ val needEnrollBiometrics: Boolean
+ get() = tangemSdk.biometricManager.canEnrollBiometrics
+
+ val biometricManager: BiometricManager
+ get() = tangemSdk.biometricManager
+
suspend fun scanProduct(
- analyticsHandler: GlobalAnalyticsEventHandler?,
userTokensRepository: UserTokensRepository,
+ cardId: String? = null,
additionalBlockchainsToDerive: Collection? = null,
messageRes: Int? = null,
): CompletionResult {
- analyticsHandler?.handleAnalyticsEvent(AnalyticsEventAnOld.READY_TO_SCAN, card = null)
-
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
return runTaskAsyncReturnOnMain(
- runnable = ScanProductTask(null, userTokensRepository, additionalBlockchainsToDerive),
- cardId = null, initialMessage = message,
- ).also { sendScanResultsToAnalytics(analyticsHandler, it) }
+ runnable = ScanProductTask(
+ card = null,
+ userTokensRepository = userTokensRepository,
+ additionalBlockchainsToDerive = additionalBlockchainsToDerive,
+ ),
+ cardId = cardId,
+ initialMessage = message,
+ ).also { sendScanResultsToAnalytics(it) }
}
suspend fun createProductWallet(
scanResponse: ScanResponse,
): CompletionResult {
return runTaskAsync(
- CreateProductWalletTask(scanResponse.productType),
+ CreateProductWalletTask(scanResponse.cardTypesResolver),
scanResponse.card.cardId,
Message(context.getString(R.string.initial_message_create_wallet_body)),
)
}
private fun sendScanResultsToAnalytics(
- analyticsHandler: GlobalAnalyticsEventHandler?,
result: CompletionResult,
) {
- when (result) {
- is CompletionResult.Success -> {
- analyticsHandler?.handleAnalyticsEvent(
- event = AnalyticsEventAnOld.CARD_IS_SCANNED,
- card = result.data.card,
- blockchain = result.data.walletData?.blockchain,
- )
- if (DemoHelper.isDemoCard(result.data)) {
- analyticsHandler?.handleAnalyticsEvent(
- event = AnalyticsEventAnOld.DEMO_MODE_ACTIVATED,
- params = mapOf(AnalyticsParamAnOld.CARD_ID.param to result.data.card.cardId),
- card = result.data.card,
- blockchain = result.data.walletData?.blockchain,
- )
- }
+ if (result is CompletionResult.Failure) {
+ (result.error as? TangemSdkError)?.let { error ->
+ Analytics.send(Basic.ScanError(error))
}
- is CompletionResult.Failure ->
- (result.error as? TangemSdkError)?.let { error ->
- analyticsHandler?.handleCardSdkErrorEvent(
- error = error,
- action = AnalyticsAnOld.ActionToLog.Scan,
- )
- }
}
}
- suspend fun createWallet(cardId: String?): CompletionResult {
+ suspend fun createWallet(cardId: String?): CompletionResult {
return runTaskAsyncReturnOnMain(
CreateWalletAndRescanTask(),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)),
)
+ .map { CardDTO(it) }
}
suspend fun derivePublicKeys(
@@ -114,12 +117,31 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId)
}
- suspend fun resetToFactorySettings(card: Card): CompletionResult {
+ suspend fun resetToFactorySettings(cardId: String): CompletionResult {
return runTaskAsyncReturnOnMain(
- ResetToFactorySettingsTask(),
- card.cardId,
- initialMessage = Message(context.getString(R.string.details_row_title_reset_factory_settings)),
+ runnable = ResetToFactorySettingsTask(),
+ cardId = cardId,
+ initialMessage = Message(context.getString(R.string.card_settings_reset_card_to_factory)),
)
+ .map { CardDTO(it) }
+ }
+
+ suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult {
+ return userCodeRepository.save(
+ cardsIds = cardsIds,
+ userCode = UserCode(
+ type = UserCodeType.AccessCode,
+ stringValue = accessCode,
+ ),
+ )
+ }
+
+ suspend fun deleteSavedUserCodes(cardsIds: Set): CompletionResult {
+ return userCodeRepository.delete(cardsIds.toSet())
+ }
+
+ suspend fun clearSavedUserCodes(): CompletionResult {
+ return userCodeRepository.clear()
}
suspend fun setPasscode(cardId: String?): CompletionResult {
@@ -154,11 +176,16 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
)
}
- suspend fun scanCard(): CompletionResult {
+ suspend fun scanCard(
+ cardId: String? = null,
+ allowRequestAccessCodeFromRepository: Boolean = false,
+ ): CompletionResult {
return runTaskAsyncReturnOnMain(
- ScanTask(),
+ runnable = ScanTask(allowRequestAccessCodeFromRepository),
+ cardId = cardId,
initialMessage = Message(context.getString(R.string.initial_message_tap_header)),
)
+ .map { CardDTO(it) }
}
suspend fun runTaskAsync(
@@ -176,23 +203,41 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
}
private suspend fun runTaskAsyncReturnOnMain(
- runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null,
+ runnable: CardSessionRunnable,
+ cardId: String? = null,
+ initialMessage: Message? = null,
): CompletionResult {
val result = runTaskAsync(runnable, cardId, initialMessage)
return withContext(Dispatchers.Main) { result }
}
+ @Suppress("MagicNumber")
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) {
tangemSdk.config.cardIdDisplayFormat = when {
scanResponse == null -> CardIdDisplayFormat.Full
- scanResponse.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4)
- scanResponse.isSaltPay() -> CardIdDisplayFormat.None
+ scanResponse.cardTypesResolver.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4)
+ scanResponse.cardTypesResolver.isSaltPay() -> CardIdDisplayFormat.None
else -> CardIdDisplayFormat.Full
}
}
fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
- return context.getString(stringResId, formatArgs)
+ return context.getString(stringResId, *formatArgs)
+ }
+
+ fun setAccessCodeRequestPolicy(
+ useBiometricsForAccessCode: Boolean,
+ ) {
+ tangemSdk.config.userCodeRequestPolicy = if (useBiometricsForAccessCode) {
+ UserCodeRequestPolicy.AlwaysWithBiometrics(codeType = UserCodeType.AccessCode)
+ } else {
+ UserCodeRequestPolicy.Default
+ }
+ }
+
+ fun useBiometricsForAccessCode(): Boolean {
+ val policy = tangemSdk.config.userCodeRequestPolicy
+ return policy is UserCodeRequestPolicy.AlwaysWithBiometrics && policy.codeType == UserCodeType.AccessCode
}
companion object {
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 af5cdb6a5c..1979b1df8a 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
@@ -5,13 +5,13 @@ import com.tangem.TangemSdk
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
-import com.tangem.common.card.Card
+import com.tangem.domain.common.CardDTO
import com.tangem.tap.domain.tasks.SignHashesTask
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class TangemSigner(
- private val card: Card,
+ private val card: CardDTO,
private val tangemSdk: TangemSdk,
private val initialMessage: Message,
private val accessCode: String? = null,
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 5a0cf85668..b0ee231286 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
@@ -2,7 +2,6 @@ package com.tangem.tap.domain
import androidx.annotation.StringRes
import com.tangem.common.core.TangemError
-import com.tangem.network.api.tangemTech.TangemTechError
import com.tangem.wallet.R
interface TapErrors
@@ -18,21 +17,20 @@ interface MultiMessageError : TapErrors {
sealed class TapError(
@StringRes val messageResource: Int,
- override val args: List? = null
+ override val args: List? = null,
) : Throwable(), TapErrors, ArgError {
object UnknownError : TapError(R.string.send_error_unknown)
open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage))
object ScanCardError : TapError(R.string.scan_card_error)
- object PayIdAlreadyCreated : TapError(R.string.wallet_create_payid_error_already_created)
- object PayIdCreatingError : TapError(R.string.wallet_create_payid_error_message)
- object PayIdEmptyField : TapError(R.string.wallet_create_payid_empty)
object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle)
object NoInternetConnection : TapError(R.string.wallet_notification_no_internet)
- object InsufficientBalance : TapError(R.string.send_error_insufficient_balance)
object BlockchainInternalError : TapError(R.string.send_error_blockchain_internal)
object AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance)
- data class AmountLowerExistentialDeposit(override val args: List) : TapError(R.string.send_error_minimum_balance_format)
+ data class AmountLowerExistentialDeposit(
+ override val args: List,
+ ) : TapError(R.string.send_error_minimum_balance_format)
+
object FeeExceedsBalance : TapError(R.string.send_validation_invalid_fee)
object TotalExceedsBalance : TapError(R.string.send_validation_invalid_total)
object InvalidAmountValue : TapError(R.string.send_validation_invalid_amount)
@@ -49,7 +47,6 @@ sealed class TapError(
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)
}
@@ -64,8 +61,7 @@ sealed class TapError(
) : TapError(-1), MultiMessageError
}
-sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemError {
- final override val code: Int = 50100
+sealed class TapSdkError(override val messageResId: Int?) : TangemError(code = 50100) {
override var customMessage: String = code.toString()
object CardForDifferentApp : TapSdkError(R.string.alert_unsupported_card)
@@ -79,13 +75,4 @@ fun TapErrors.assembleErrors(): MutableList?>> {
is TapError -> idList.add(Pair(this.messageResource, this.args))
}
return idList
-}
-
-fun TangemTechError.toTapError(): TapError {
- return when (this.code) {
- 404 -> NoDataError(this.description)
- else -> TapError.CustomError(customMessage = this.description)
- }
-}
-
-class NoDataError(message: String) : TapError.CustomError(customMessage = message)
+}
\ No newline at end of file
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 b15f2a7431..dd13b37e8d 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
@@ -6,35 +6,51 @@ 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.doOnFailure
+import com.tangem.common.doOnSuccess
import com.tangem.common.services.Result
+import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.ScanResponse
-import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.ThrottlerWithValues
import com.tangem.domain.common.extensions.withMainContext
+import com.tangem.operations.attestation.Attestation
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.configurable.config.ConfigManager
-import com.tangem.tap.domain.extensions.isMultiwalletAllowed
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
+import com.tangem.tap.domain.model.UserWallet
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.domain.walletStores.WalletStoresError
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.wallet.models.toBlockchainNetworks
import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.middlewares.handleBasicAnalyticsEvent
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.store
+import com.tangem.tap.tangemSdkManager
import com.tangem.tap.userTokensRepository
+import com.tangem.tap.walletStoresManager
+import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+import timber.log.Timber
class TapWalletManager {
val walletManagerFactory: WalletManagerFactory
by lazy { WalletManagerFactory(blockchainSdkConfig) }
- val rates: RatesRepository = RatesRepository()
+
+ // TODO("After adding DI") get dependencies by DI
+ val rates: RatesRepository by lazy {
+ RatesRepository(
+ tangemTechApi = store.state.domainNetworks.tangemTechService.api,
+ dispatchers = AppCoroutineDispatcherProvider(),
+ )
+ }
private val blockchainSdkConfig by lazy {
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
@@ -81,22 +97,80 @@ class TapWalletManager {
}
}
+ suspend fun onWalletSelected(userWallet: UserWallet, refresh: Boolean) {
+ val scanResponse = userWallet.scanResponse
+ val card = scanResponse.card
+ val attestationFailed = card.attestation.status == Attestation.Status.Failed
+
+ tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
+ store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse)
+ updateConfigManager(scanResponse)
+
+ withMainContext {
+ store.dispatch(WalletAction.UserWalletChanged(userWallet))
+ store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
+ store.dispatch(WalletConnectAction.ResetState)
+ store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
+ store.dispatch(WalletConnectAction.RestoreSessions(scanResponse))
+ store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
+ store.dispatch(WalletAction.Warnings.CheckIfNeeded)
+ }
+
+ loadData(userWallet, refresh)
+ }
+
+ suspend fun loadData(userWallet: UserWallet, refresh: Boolean = false) {
+ walletStoresManager.fetch(userWallet, refresh)
+ .doOnSuccess {
+ Timber.d("Wallet stores fetched for ${userWallet.walletId}")
+ store.dispatchOnMain(WalletAction.LoadData.Success)
+ handleBasicAnalyticsEvent()
+ }
+ .doOnFailure { error ->
+ val errorAction = when (error) {
+ is WalletStoresError -> when (error) {
+ is WalletStoresError.FetchFiatRatesError,
+ is WalletStoresError.UpdateWalletManagerError,
+ -> WalletAction.LoadData.Failure(error = null)
+ is WalletStoresError.WalletManagerNotCreated -> WalletAction.LoadData.Failure(
+ error = TapError.WalletManager.CreationError,
+ )
+ is WalletStoresError.UnknownBlockchain -> WalletAction.LoadData.Failure(
+ error = TapError.UnknownBlockchain,
+ )
+ is WalletStoresError.NoInternetConnection -> WalletAction.LoadData.Failure(
+ error = TapError.NoInternetConnection,
+ )
+ }
+ else -> WalletAction.LoadData.Failure(error = null)
+ }
+
+ Timber.e(error, "Wallet stores fetching failed for ${userWallet.walletId}")
+
+ store.dispatchOnMain(errorAction)
+ }
+ }
+
suspend fun onCardScanned(data: ScanResponse) {
walletManagersThrottler.clear()
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data)
updateConfigManager(data)
withMainContext {
- store.dispatch(WalletAction.ResetState(data.card.cardId))
+ store.dispatch(WalletAction.ResetState(data.card))
store.dispatch(WalletConnectAction.ResetState)
- store.dispatch(GlobalAction.SaveScanNoteResponse(data))
+ store.dispatch(GlobalAction.SaveScanResponse(data))
store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard))
- store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
+ store.dispatch(
+ WalletAction.MultiWallet.SetIsMultiwalletAllowed(
+ data.cardTypesResolver.isMultiwalletAllowed(),
+ ),
+ )
store.dispatch(WalletConnectAction.RestoreSessions(data))
store.dispatch(
WalletAction.MultiWallet.ShowWalletBackupWarning(
- show = data.card.settings.isBackupAllowed
- && data.card.backupStatus == Card.BackupStatus.NoBackup,
+ show = data.card.settings.isBackupAllowed &&
+ data.card.backupStatus == CardDTO.BackupStatus.NoBackup,
),
)
loadData(data)
@@ -105,12 +179,12 @@ class TapWalletManager {
fun updateConfigManager(data: ScanResponse) {
val configManager = store.state.globalState.configManager
- val blockchain = data.getBlockchain()
- if (data.card.isStart2Coin) {
+ val blockchain = data.cardTypesResolver.getBlockchain()
+ if (data.cardTypesResolver.isStart2Coin()) {
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
configManager?.turnOff(ConfigManager.isTopUpEnabled)
- } else if (blockchain == Blockchain.Bitcoin
- || data.walletData?.blockchain == Blockchain.Bitcoin.id
+ } else if (blockchain == Blockchain.Bitcoin ||
+ data.walletData?.blockchain == Blockchain.Bitcoin.id
) {
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
@@ -127,7 +201,8 @@ class TapWalletManager {
return
}
- if (data.card.isMultiwalletAllowed) {
+ if (data.cardTypesResolver.isMultiwalletAllowed()) {
+ dispatchOnMain(WalletAction.MultiWallet.ScheduleCheckForMissingDerivation)
loadMultiWalletData(data)
} else {
loadSingleWalletData(data)
@@ -143,24 +218,22 @@ class TapWalletManager {
private fun checkIfDerivationsAreMissing(blockchainNetworks: List, scanResponse: ScanResponse) {
blockchainNetworks.map {
if (it.tokens.isNotEmpty()) {
- WalletAction.MultiWallet.AddTokens(it.tokens, it, false)
+ WalletAction.MultiWallet.AddTokens(it.tokens, it)
}
}
val missingDerivations = blockchainNetworks
.filter {
it.derivationPath != null && !scanResponse.hasDerivation(it.blockchain, it.derivationPath)
}
- if (missingDerivations.isNotEmpty()) {
- store.dispatch(WalletAction.MultiWallet.AddMissingDerivations(missingDerivations))
- }
+ store.dispatch(WalletAction.MultiWallet.AddMissingDerivations(missingDerivations))
}
private suspend fun loadSingleWalletData(data: ScanResponse) {
- val blockchain = data.getBlockchain()
+ val blockchain = data.cardTypesResolver.getBlockchain()
val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
if (blockchain != Blockchain.Unknown && primaryWalletManager != null) {
- val primaryToken = data.getPrimaryToken()
+ val primaryToken = data.cardTypesResolver.getPrimaryToken()
dispatchOnMain(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain))
if (primaryToken != null) {
@@ -171,7 +244,6 @@ class TapWalletManager {
WalletAction.MultiWallet.AddBlockchains(
blockchains = listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)),
walletManagers = listOf(primaryWalletManager),
- save = false,
),
WalletAction.LoadFiatRate(),
)
@@ -187,7 +259,6 @@ class TapWalletManager {
WalletAction.MultiWallet.AddBlockchains(
blockchains = blockchainNetworks,
walletManagers = walletManagers,
- save = false,
),
)
@@ -197,7 +268,6 @@ class TapWalletManager {
WalletAction.MultiWallet.AddTokens(
tokens = it.tokens,
blockchain = it,
- save = false,
),
)
}
@@ -207,7 +277,9 @@ class TapWalletManager {
}
suspend fun reloadData(data: ScanResponse) {
- loadUserCurrencies(data, walletManagerFactory)
+ if (data.cardTypesResolver.isMultiwalletAllowed()) {
+ loadUserCurrencies(data, walletManagerFactory)
+ }
withContext(Dispatchers.Main) {
getActionIfUnknownBlockchainOrEmptyWallet(data)?.let {
store.dispatch(it)
@@ -225,10 +297,11 @@ class TapWalletManager {
private fun getActionIfUnknownBlockchainOrEmptyWallet(data: ScanResponse): WalletAction? {
return when {
// check order is important
- data.isTangemTwins() && !data.twinsIsTwinned() -> {
+ data.cardTypesResolver.isTangemTwins() && !data.twinsIsTwinned() -> {
WalletAction.EmptyWallet
}
- data.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
+ data.cardTypesResolver.getBlockchain() == Blockchain.Unknown &&
+ !data.cardTypesResolver.isMultiwalletAllowed() -> {
WalletAction.LoadData.Failure(TapError.UnknownBlockchain)
}
data.isDemoCard() -> {
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 bd530b0796..a79f3452b0 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,6 +1,8 @@
package com.tangem.tap.domain.configurable.config
import com.tangem.blockchain.common.BlockchainSdkConfig
+import com.tangem.blockchain.common.BlockchairCredentials
+import com.tangem.blockchain.common.QuickNodeCredentials
import com.tangem.tap.common.shop.shopify.ShopifyShop
import com.tangem.tap.common.zendesk.ZendeskConfig
import com.tangem.tap.domain.configurable.Loader
@@ -37,7 +39,7 @@ class ConfigManager {
fun load(configLoader: Loader, onComplete: ((config: Config) -> Unit)? = null) {
configLoader.load { configModel ->
setupFeature(configModel.features)
- setupKey(configModel.configValues)
+ setupConfigValues(configModel.configValues)
onComplete?.invoke(config)
}
}
@@ -66,53 +68,52 @@ class ConfigManager {
config = config.copy(
isTopUpEnabled = model.isTopUpEnabled,
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
- isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
+ isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed,
)
defaultConfig = defaultConfig.copy(
isTopUpEnabled = model.isTopUpEnabled,
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
- isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
+ isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed,
)
}
- private fun setupKey(configValues: ConfigValueModel?) {
+ private fun setupConfigValues(configValues: ConfigValueModel?) {
val values = configValues ?: return
- config = config.copy(
- coinMarketCapKey = values.coinMarketCapKey,
- moonPayApiKey = values.moonPayApiKey,
- moonPayApiSecretKey = values.moonPayApiSecretKey,
- mercuryoWidgetId = values.mercuryoWidgetId,
- mercuryoSecret = values.mercuryoSecret,
+
+ config = createConfig(config, values)
+ defaultConfig = config.copy()
+ }
+
+ private fun createConfig(config: Config, configValues: ConfigValueModel): Config {
+ return config.copy(
+ coinMarketCapKey = configValues.coinMarketCapKey,
+ moonPayApiKey = configValues.moonPayApiKey,
+ moonPayApiSecretKey = configValues.moonPayApiSecretKey,
+ mercuryoWidgetId = configValues.mercuryoWidgetId,
+ mercuryoSecret = configValues.mercuryoSecret,
blockchainSdkConfig = BlockchainSdkConfig(
- blockchairApiKey = values.blockchairApiKey,
- blockchairAuthorizationToken = values.blockchairAuthorizationToken,
- blockcypherTokens = values.blockcypherTokens,
- infuraProjectId = values.infuraProjectId,
- tronGridApiKey = values.tronGridApiKey
+ blockchairCredentials = BlockchairCredentials(
+ apiKey = configValues.blockchairApiKeys,
+ authToken = configValues.blockchairAuthorizationToken,
+ ),
+ blockcypherTokens = configValues.blockcypherTokens,
+ quickNodeCredentials = QuickNodeCredentials(
+ apiKey = configValues.quiknodeApiKey,
+ subdomain = configValues.quiknodeSubdomain,
+ ),
+ bscQuickNodeCredentials = QuickNodeCredentials(
+ apiKey = configValues.bscQuiknodeApiKey,
+ subdomain = configValues.bscQuiknodeSubdomain,
+ ),
+ infuraProjectId = configValues.infuraProjectId,
+ tronGridApiKey = configValues.tronGridApiKey,
+ saltPayAuthToken = configValues.saltPay.credentials.token,
),
- appsFlyerDevKey = values.appsFlyerDevKey,
- amplitudeApiKey = values.amplitudeApiKey,
- shopify = values.shopifyShop,
- zendesk = values.zendesk,
- saltPayConfig = values.saltPay,
- )
- defaultConfig = defaultConfig.copy(
- coinMarketCapKey = values.coinMarketCapKey,
- moonPayApiKey = values.moonPayApiKey,
- moonPayApiSecretKey = values.moonPayApiSecretKey,
- mercuryoWidgetId = values.mercuryoWidgetId,
- mercuryoSecret = values.mercuryoSecret,
- blockchainSdkConfig = BlockchainSdkConfig(
- blockchairApiKey = values.blockchairApiKey,
- blockchairAuthorizationToken = values.blockchairAuthorizationToken,
- blockcypherTokens = values.blockcypherTokens,
- infuraProjectId = values.infuraProjectId
- ),
- appsFlyerDevKey = values.appsFlyerDevKey,
- amplitudeApiKey = values.amplitudeApiKey,
- shopify = values.shopifyShop,
- zendesk = values.zendesk,
- saltPayConfig = values.saltPay,
+ appsFlyerDevKey = configValues.appsFlyer.appsFlyerDevKey,
+ amplitudeApiKey = configValues.amplitudeApiKey,
+ shopify = configValues.shopifyShop,
+ zendesk = configValues.zendesk,
+ saltPayConfig = configValues.saltPay,
)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/FeaturesLocalLoader.kt
similarity index 100%
rename from app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigLoaders.kt
rename to app/src/main/java/com/tangem/tap/domain/configurable/config/FeaturesLocalLoader.kt
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 1c537e7909..586ba0c63b 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
@@ -15,17 +15,22 @@ class FeatureModel(
val isCreatingTwinCardsAllowed: Boolean,
)
+@Suppress("LongParameterList")
class ConfigValueModel(
val coinMarketCapKey: String,
val mercuryoWidgetId: String,
val mercuryoSecret: String,
val moonPayApiKey: String,
val moonPayApiSecretKey: String,
- val blockchairApiKey: String?,
+ val blockchairApiKeys: List,
val blockchairAuthorizationToken: String?,
+ val quiknodeSubdomain: String,
+ val quiknodeApiKey: String,
+ val bscQuiknodeSubdomain: String,
+ val bscQuiknodeApiKey: String,
val blockcypherTokens: Set?,
val infuraProjectId: String?,
- val appsFlyerDevKey: String,
+ val appsFlyer: AppsFlyer,
val shopifyShop: ShopifyShop?,
val zendesk: ZendeskConfig?,
val saltPay: SaltPayConfig,
@@ -33,6 +38,11 @@ class ConfigValueModel(
val amplitudeApiKey: String,
)
+data class AppsFlyer(
+ val appsFlyerDevKey: String,
+ val appsFlyerAppID: String,
+)
+
class ConfigModel(
val features: FeatureModel?,
val configValues: ConfigValueModel?,
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt
index 03c10daf6f..ddadfcfc70 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessage.kt
@@ -24,7 +24,6 @@ data class WarningMessage(
blockchains?.map { Blockchain.fromId(it.uppercase()) }
}
-
var isHidden = false
enum class Priority {
@@ -40,15 +39,14 @@ data class WarningMessage(
enum class Type {
@Json(name = "permanent")
- Permanent, // нельзя скрыть
+ Permanent, // нельзя скрыть
@Json(name = "temporary")
- Temporary, // можно скрыть (кнопка ОК)
+ Temporary, // можно скрыть (кнопка ОК)
AppRating,
TestCard
-
}
enum class Location {
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 683302ba73..a0e382136a 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
@@ -39,8 +39,8 @@ class WarningMessagesManager {
val foundWarning = findWarning(warning)
return when {
foundWarning == null -> false
- foundWarning.type == WarningMessage.Type.Temporary
- || foundWarning.type == WarningMessage.Type.AppRating -> {
+ foundWarning.type == WarningMessage.Type.Temporary ||
+ foundWarning.type == WarningMessage.Type.AppRating -> {
if (foundWarning.isHidden) {
false
} else {
@@ -70,6 +70,8 @@ class WarningMessagesManager {
}
companion object {
+ const val REMAINING_SIGNATURES_WARNING = 10
+
fun devCardWarning(): WarningMessage = WarningMessage(
"",
"",
@@ -77,7 +79,7 @@ class WarningMessagesManager {
priority = WarningMessage.Priority.Critical,
listOf(WarningMessage.Location.MainScreen),
null,
- R.string.alert_title,
+ R.string.common_warning,
R.string.alert_developer_card,
WarningMessage.Origin.Local,
)
@@ -89,7 +91,7 @@ class WarningMessagesManager {
priority = WarningMessage.Priority.Info,
listOf(WarningMessage.Location.MainScreen),
null,
- R.string.alert_title,
+ R.string.common_warning,
R.string.alert_card_signed_transactions,
WarningMessage.Origin.Local,
)
@@ -142,7 +144,7 @@ class WarningMessagesManager {
priority = WarningMessage.Priority.Critical,
listOf(WarningMessage.Location.MainScreen),
blockchains = null,
- titleResId = R.string.alert_title,
+ titleResId = R.string.common_warning,
messageResId = R.string.warning_low_signatures_format,
origin = WarningMessage.Origin.Local,
messageFormatArg = remainingSignatures.toString(),
@@ -155,7 +157,7 @@ class WarningMessagesManager {
priority = WarningMessage.Priority.Critical,
listOf(WarningMessage.Location.MainScreen, WarningMessage.Location.SendScreen),
null,
- R.string.alert_title,
+ R.string.common_warning,
R.string.warning_testnet_card_message,
WarningMessage.Origin.Local,
)
@@ -167,11 +169,9 @@ class WarningMessagesManager {
priority = WarningMessage.Priority.Critical,
listOf(WarningMessage.Location.MainScreen),
null,
- R.string.alert_title,
+ R.string.common_warning,
R.string.alert_demo_message,
WarningMessage.Origin.Local,
)
-
- const val REMAINING_SIGNATURES_WARNING = 10
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt
index 673ef1787b..9adb6b24aa 100644
--- a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt
+++ b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt
@@ -5,7 +5,7 @@ import com.tangem.blockchain.common.Token
import com.tangem.common.card.EllipticCurve
import java.math.BigDecimal
-
+@Suppress("MagicNumber")
fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? {
return when (this) {
Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(1.5) else BigDecimal.ONE
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 ab2ba5610b..005f05500c 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
@@ -1,58 +1,38 @@
package com.tangem.tap.domain.extensions
-import com.tangem.common.card.Card
-import com.tangem.common.card.CardWallet
-import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
-import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
+import com.tangem.domain.common.CardDTO
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.TwinCardNumber
-import com.tangem.domain.common.extensions.calculateHmacSha256
import com.tangem.domain.common.getTwinCardNumber
-import com.tangem.domain.common.isTangemTwin
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
+val CardDTO.remainingSignatures: Int?
+ get() = this.wallets.firstOrNull()?.remainingSignatures
-val Card.isWalletDataSupported: Boolean
+val CardDTO.isWalletDataSupported: Boolean
get() = this.firmwareVersion.major >= 4
-val Card.isMultiwalletAllowed: Boolean
- get() {
- return !isTangemTwin() && !isStart2Coin && !isTangemNote && !isSaltPay
- && (firmwareVersion >= FirmwareVersion.MultiWalletAvailable ||
- getSingleWallet()?.curve == EllipticCurve.Secp256k1)
- }
+val CardDTO.isFirmwareMultiwalletAllowed: Boolean
+ get() = firmwareVersion >= FirmwareVersion.MultiWalletAvailable && settings.maxWalletsCount > 1
-val Card.isHdWalletAllowedByApp: Boolean
+val CardDTO.isHdWalletAllowedByApp: Boolean
get() = settings.isHDWalletAllowed && !isSaltPay
-fun Card.getSingleWallet(): CardWallet? {
- return wallets.firstOrNull()
+@Suppress("UnnecessaryParentheses")
+fun CardDTO.hasSignedHashes(): Boolean {
+ return wallets.any { (it.totalSignedHashes ?: 0) > 0 }
}
-fun Card.hasWallets(): Boolean = wallets.isNotEmpty()
-
-fun Card.hasNoWallets(): Boolean = wallets.isEmpty()
-
-fun Card.hasSingleWallet(): Boolean = wallets.size == 1
-
-fun Card.hasSignedHashes(): Boolean {
- return wallets.any { it.totalSignedHashes ?: 0 > 0 }
+fun CardDTO.signedHashesCount(): Int {
+ return wallets.sumOf { it.totalSignedHashes ?: 0 }
}
-fun Card.signedHashesCount(): Int {
- return wallets.map { it.totalSignedHashes ?: 0 }.sum()
-}
-
-suspend fun Card.getOrLoadCardArtworkUrl(cardInfo: Result? = null): String {
+suspend fun CardDTO.getOrLoadCardArtworkUrl(cardInfo: Result? = null): String {
fun ifAnyError(): String {
return when {
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
@@ -67,42 +47,28 @@ suspend fun Card.getOrLoadCardArtworkUrl(cardInfo: Result {
val artworkId = cardInfoResult.data.artwork?.id
- if (artworkId == null || artworkId.isEmpty()) {
+ if (artworkId.isNullOrEmpty()) {
ifAnyError()
} else {
OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
}
}
+
is Result.Failure -> ifAnyError()
}
}
-fun Card.getArtworkUrl(artworkId: String?): String? {
+fun CardDTO.getArtworkUrl(artworkId: String?): String? {
return when {
artworkId != null -> {
OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
}
+
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
else -> null
}
-}
-
-fun Card.getUserWalletId(): String {
- val walletPublicKey = this.wallets.firstOrNull()?.publicKey ?: return ""
- return UserWalletId(walletPublicKey).stringValue
-}
-
-class UserWalletId(val walletPublicKey: ByteArray) {
- val stringValue: String = calculateUserId(walletPublicKey)
-
- private fun calculateUserId(walletPublicKey: ByteArray): String {
- val message = "UserWalletID".toByteArray()
- val keyHash = walletPublicKey.calculateSha256()
- return message.calculateHmacSha256(keyHash).toHexString()
- }
}
\ No newline at end of file
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 ec95e13dc6..07e92a313f 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
@@ -7,13 +7,12 @@ import com.tangem.blockchain.common.DerivationStyle
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.card.CardWallet
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
+import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.SaltPayWorkaround
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isTestCard
@@ -38,7 +37,7 @@ fun WalletManagerFactory.makeWalletManagerForApp(
val seedKey = wallet.extendedPublicKey
return when {
- scanResponse.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> {
+ scanResponse.cardTypesResolver.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> {
makeTwinWalletManager(
walletPublicKey = wallet.publicKey,
pairPublicKey = scanResponse.secondTwinPublicKey!!.hexToBytes(),
@@ -46,7 +45,7 @@ fun WalletManagerFactory.makeWalletManagerForApp(
curve = wallet.curve,
)
}
- scanResponse.card.isHdWalletAllowedByApp && (seedKey != null && derivationParams != null) -> {
+ scanResponse.card.isHdWalletAllowedByApp && seedKey != null && derivationParams != null -> {
val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()]
val derivationPath = when (derivationParams) {
is DerivationParams.Default -> blockchain.derivationPath(derivationParams.style)
@@ -73,7 +72,8 @@ fun WalletManagerFactory.makeWalletManagerForApp(
}
fun WalletManagerFactory.makeWalletManagerForApp(
- scanResponse: ScanResponse, blockchainNetwork: BlockchainNetwork,
+ scanResponse: ScanResponse,
+ blockchainNetwork: BlockchainNetwork,
): WalletManager? {
return makeWalletManagerForApp(
scanResponse,
@@ -82,7 +82,7 @@ fun WalletManagerFactory.makeWalletManagerForApp(
)
}
-private fun getDerivationParams(derivationPath: String?, card: Card): DerivationParams? {
+private fun getDerivationParams(derivationPath: String?, card: CardDTO): DerivationParams? {
return derivationPath?.let {
DerivationParams.Custom(
DerivationPath(it),
@@ -120,9 +120,9 @@ fun WalletManagerFactory.makePrimaryWalletManager(
scanResponse: ScanResponse,
): WalletManager? {
val blockchain = if (scanResponse.card.isTestCard) {
- scanResponse.getBlockchain().getTestnetVersion() ?: return null
+ scanResponse.cardTypesResolver.getBlockchain().getTestnetVersion() ?: return null
} else {
- scanResponse.getBlockchain()
+ scanResponse.cardTypesResolver.getBlockchain()
}
val derivationParams = getDerivationParams(null, scanResponse.card)
return makeWalletManagerForApp(
@@ -132,7 +132,7 @@ fun WalletManagerFactory.makePrimaryWalletManager(
)
}
-private fun selectWallet(wallets: List): CardWallet? {
+private fun selectWallet(wallets: List): CardDTO.Wallet? {
return when (wallets.size) {
0 -> null
1 -> wallets[0]
@@ -143,13 +143,14 @@ private fun selectWallet(wallets: List): CardWallet? {
fun WalletManagerFactory.makeSaltPayWalletManager(
scanResponse: ScanResponse,
): EthereumWalletManager {
- val blockchain = scanResponse.getBlockchain()
- if (blockchain != Blockchain.SaltPay && blockchain != Blockchain.SaltPayTestnet)
- throw IllegalArgumentException()
+ val blockchain = scanResponse.cardTypesResolver.getBlockchain()
+ if (blockchain != Blockchain.SaltPay) {
+ error("WalletManager for the SaltPay can be created based only on Blockchain.SaltPay")
+ }
val token = SaltPayWorkaround.tokenFrom(blockchain)
val cardWallet = scanResponse.card.wallets.firstOrNull().guard {
- throw NullPointerException("SaltPay card must have one wallet at least")
+ error("SaltPay card must have one wallet at least")
}
return makeWalletManager(
diff --git a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt
new file mode 100644
index 0000000000..dd9a0b27a1
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt
@@ -0,0 +1,21 @@
+package com.tangem.tap.domain.model
+
+import java.math.BigDecimal
+
+/**
+ * Represents fiat balance of [WalletStoreModel] list
+ * @property amount Amount of the total balance
+ * */
+sealed class TotalFiatBalance {
+ open val amount: BigDecimal = BigDecimal.ZERO
+
+ object Loading : TotalFiatBalance()
+
+ data class Error(
+ override val amount: BigDecimal,
+ ) : TotalFiatBalance()
+
+ data class Loaded(
+ override val amount: BigDecimal,
+ ) : TotalFiatBalance()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt
new file mode 100644
index 0000000000..a76fbb9d1a
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt
@@ -0,0 +1,35 @@
+package com.tangem.tap.domain.model
+
+import com.tangem.domain.common.ScanResponse
+import com.tangem.domain.common.util.UserWalletId
+
+/**
+ * Represents user's wallet which stored in app persistence
+ * @param name User wallet name
+ * @param walletId User wallet [UserWalletId]
+ * @param artworkUrl User wallet card artwork URL
+ * @param cardsInWallet List of cards IDs assigned with this user's wallet
+ * @param isMultiCurrency Indicates whether this user wallet can work with more than one currency
+ * @param scanResponse [ScanResponse] of primary user's wallet card.
+ * TODO: Replace with [com.tangem.domain.common.CardDTO]
+ * @property cardId ID of user's wallet primary card
+ * @property hasAccessCode Indicates if the user's wallet primary card has access code
+ * @property isLocked Indicates if this primary card has no currency wallets
+ * */
+data class UserWallet(
+ val name: String,
+ val walletId: UserWalletId,
+ val artworkUrl: String,
+ val cardsInWallet: Set,
+ val isMultiCurrency: Boolean,
+ val scanResponse: ScanResponse,
+) {
+ val cardId: String
+ get() = scanResponse.card.cardId
+
+ val hasAccessCode: Boolean
+ get() = scanResponse.card.isAccessCodeSet
+
+ val isLocked: Boolean
+ get() = scanResponse.card.wallets.isEmpty()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt
new file mode 100644
index 0000000000..3429afa07c
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt
@@ -0,0 +1,73 @@
+package com.tangem.tap.domain.model
+
+import com.tangem.tap.domain.model.WalletDataModel.Status
+import com.tangem.tap.features.wallet.models.Currency
+import com.tangem.tap.features.wallet.models.PendingTransaction
+import com.tangem.tap.features.wallet.redux.AddressData
+import java.math.BigDecimal
+
+/**
+ * Contains info about wallet store currency
+ * @param currency Wallet's [Currency]
+ * @param status Wallet's [Status], represents current status of that currency
+ * @param walletAddresses List of wallet data [AddressData]
+ * @param existentialDeposit Amount that must be held on currency's balance, if balance is below that amount all
+ * founds will be destroyed. Null if currency don't have existential deposit
+ * @param fiatRate Wallet's fiat rate, used to calculate fiat balance. Null if not provided
+ * @param isCardSingleToken shows that [Currency] is a card token
+ * */
+data class WalletDataModel(
+ val currency: Currency,
+ val status: Status,
+ val walletAddresses: List,
+ val existentialDeposit: BigDecimal?,
+ val fiatRate: BigDecimal?,
+ val isCardSingleToken: Boolean,
+) {
+
+ /**
+ * Represent current status of currency
+ * @property amount Currency amount
+ * @property pendingTransactions List of currency [PendingTransaction] sent in currency's blockchain
+ * @property errorMessage Status error message, null if not provided
+ * @property isErrorStatus true if current status is error status, false otherwise
+ * */
+ sealed class Status {
+ open val amount: BigDecimal = BigDecimal.ZERO
+ open val pendingTransactions: List = emptyList()
+ open val errorMessage: String? = null
+ open val isErrorStatus: Boolean = false
+ }
+
+ object Loading : Status()
+
+ data class VerifiedOnline(
+ override val amount: BigDecimal,
+ ) : Status()
+
+ data class TransactionInProgress(
+ override val amount: BigDecimal,
+ override val pendingTransactions: List,
+ ) : Status()
+
+ data class SameCurrencyTransactionInProgress(
+ override val amount: BigDecimal,
+ override val pendingTransactions: List,
+ ) : Status()
+
+ data class NoAccount(
+ val amountToCreateAccount: BigDecimal?,
+ ) : Status() {
+ override val isErrorStatus: Boolean = true
+ }
+
+ data class Unreachable(
+ override val errorMessage: String?,
+ ) : Status() {
+ override val isErrorStatus: Boolean = true
+ }
+
+ object MissedDerivation : Status() {
+ override val isErrorStatus: Boolean = true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt
new file mode 100644
index 0000000000..7bca1028c0
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt
@@ -0,0 +1,85 @@
+package com.tangem.tap.domain.model
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.common.hdWallet.DerivationPath
+import com.tangem.domain.common.util.UserWalletId
+import com.tangem.tap.domain.model.WalletStoreModel.WalletRent
+import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.features.wallet.models.Currency
+import java.math.BigDecimal
+
+/**
+ * Contains info about the blockchain and its currencies
+ *
+ * @param userWalletId ID of the associated [UserWallet]
+ * @param blockchain [Blockchain] of this WalletStore
+ * @param derivationPath [DerivationPath] of this store, null if the card does not support the
+ * [HD Wallet](https://coinsutra.com/hd-wallets-deterministic-wallet/)
+ * @param walletsData List of [WalletDataModel] which represents store's blockchain currency and tokens currencies
+ * @param walletRent [WalletRent], null if store has no rent or currency balance is greater then
+ * [WalletRent.exemptionAmount]
+ * @param blockchainNetwork [BlockchainNetwork].
+ * TODO: Remove after WalletMiddleware refactoring
+ * @param walletManager [WalletManager], may be null if it fails to create this manager.
+ * TODO: Remove after WalletMiddleware refactoring
+ *
+ * @property blockchainWalletData Returns the [WalletDataModel] of the blockchain of this wallet store
+ * or throw [NoSuchElementException] if this wallet store not contains [WalletDataModel] of the blockchain
+ * */
+data class WalletStoreModel(
+ val userWalletId: UserWalletId,
+ val blockchain: Blockchain,
+ val derivationPath: DerivationPath?,
+ val walletsData: List,
+ val walletRent: WalletRent?,
+ @Deprecated("Don't use it, will be removed")
+ val blockchainNetwork: BlockchainNetwork,
+ @Deprecated("Don't use it, will be removed")
+ val walletManager: WalletManager?,
+) {
+
+ val blockchainWalletData: WalletDataModel
+ get() = walletsData.first { it.currency is Currency.Blockchain }
+
+ /**
+ * Represents wallet blockchain rent
+ * @param rent Amount that will be charged in overtime if the blockchain does not have an amount greater than
+ * the [WalletRent.exemptionAmount]
+ * @param exemptionAmount Amount that should be on the blockchain balance not to pay rent
+ * */
+ data class WalletRent(
+ val rent: BigDecimal,
+ val exemptionAmount: BigDecimal,
+ )
+
+ // TODO: Remove the generated methods after blockchainNetwork and walletManager are removed from the model
+ // region Generated
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is WalletStoreModel) return false
+
+ if (userWalletId != other.userWalletId) return false
+ if (blockchain != other.blockchain) return false
+ if (derivationPath != other.derivationPath) return false
+ if (walletsData != other.walletsData) return false
+ if (walletRent != other.walletRent) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = userWalletId.hashCode()
+ result = 31 * result + blockchain.hashCode()
+ result = 31 * result + (derivationPath?.hashCode() ?: 0)
+ result = 31 * result + walletsData.hashCode()
+ result = 31 * result + (walletRent?.hashCode() ?: 0)
+ return result
+ }
+
+ override fun toString(): String {
+ return "WalletStoreModel(userWalletId=$userWalletId, blockchain=$blockchain, derivationPath=$derivationPath, " +
+ "walletsData=$walletsData, walletRent=$walletRent)"
+ }
+ // endregion Generated
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt
new file mode 100644
index 0000000000..a2c699667a
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt
@@ -0,0 +1,92 @@
+package com.tangem.tap.domain.model.builders
+
+import com.tangem.common.extensions.toHexString
+import com.tangem.common.services.Result
+import com.tangem.domain.common.CardDTO
+import com.tangem.domain.common.ProductType
+import com.tangem.domain.common.ScanResponse
+import com.tangem.domain.common.TapWorkarounds.isStart2Coin
+import com.tangem.domain.common.TwinCardNumber
+import com.tangem.domain.common.TwinsHelper
+import com.tangem.operations.attestation.OnlineCardVerifier
+import com.tangem.operations.attestation.TangemApi
+import com.tangem.tap.domain.model.UserWallet
+import com.tangem.tap.features.wallet.redux.Artwork
+
+class UserWalletBuilder(
+ private val scanResponse: ScanResponse,
+ private val onlineCardVerifier: OnlineCardVerifier = OnlineCardVerifier(),
+) {
+ private var backupCardsIds: Set = emptySet()
+
+ private val CardDTO.isBackupNotAllowed: Boolean
+ get() = !this.settings.isBackupAllowed
+
+ private val ScanResponse.userWalletName: String
+ get() = when (productType) {
+ ProductType.Note -> "Note"
+ ProductType.Twins -> "Twin"
+ ProductType.SaltPay -> "SaltPay"
+ ProductType.Start2Coin -> "Start2Coin"
+ ProductType.Wallet -> when {
+ card.isBackupNotAllowed -> "Tangem card"
+ card.isStart2Coin -> "Start2Coin"
+ else -> "Wallet"
+ }
+ }
+
+ fun backupCardsIds(backupCardsIds: Set?) = this.apply {
+ if (backupCardsIds != null) {
+ this.backupCardsIds = backupCardsIds
+ }
+ }
+
+ suspend fun build(): UserWallet? {
+ return with(scanResponse) {
+ UserWalletIdBuilder.scanResponse(scanResponse)
+ .build()
+ ?.let {
+ UserWallet(
+ walletId = it,
+ name = userWalletName,
+ artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey),
+ cardsInWallet = backupCardsIds.plus(card.cardId),
+ scanResponse = this,
+ isMultiCurrency = cardTypesResolver.isMultiwalletAllowed(),
+ )
+ }
+ }
+ }
+
+ private suspend fun loadArtworkUrl(cardId: String, cardPublicKey: ByteArray): String {
+ return when (val result = onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) {
+ is Result.Success -> {
+ val artworkId = result.data.artwork?.id
+ if (artworkId.isNullOrEmpty()) {
+ getFallbackArtworkUrl(cardId)
+ } else {
+ getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
+ }
+ }
+
+ is Result.Failure -> getFallbackArtworkUrl(cardId)
+ }
+ }
+
+ private fun getFallbackArtworkUrl(cardId: String): String {
+ return when {
+ cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
+ cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
+ else -> when (TwinsHelper.getTwinCardNumber(cardId)) {
+ TwinCardNumber.First -> Artwork.TWIN_CARD_1
+ TwinCardNumber.Second -> Artwork.TWIN_CARD_2
+ else -> Artwork.DEFAULT_IMG_URL
+ }
+ }
+ }
+
+ private fun getUrlForArtwork(cardId: String, cardPublicKeyHex: String, artworkId: String): String {
+ return TangemApi.Companion.BaseUrl.VERIFY.url + TangemApi.ARTWORK +
+ "?artworkId=$artworkId&CID=$cardId&publicKey=$cardPublicKeyHex"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt
new file mode 100644
index 0000000000..ebe875ad66
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt
@@ -0,0 +1,71 @@
+package com.tangem.tap.domain.model.builders
+
+import com.tangem.common.extensions.calculateSha256
+import com.tangem.common.extensions.hexToBytes
+import com.tangem.crypto.Secp256k1
+import com.tangem.domain.common.CardDTO
+import com.tangem.domain.common.ProductType
+import com.tangem.domain.common.ScanResponse
+import com.tangem.domain.common.TapWorkarounds.isTangemTwins
+import com.tangem.domain.common.extensions.calculateHmacSha256
+import com.tangem.domain.common.util.UserWalletId
+
+class UserWalletIdBuilder private constructor(
+ private val publicKey: ByteArray?,
+ private val pairTwinPublicKey: ByteArray? = null,
+) {
+ fun build(): UserWalletId? {
+ val seed = if (publicKey != null) {
+ if (pairTwinPublicKey != null) {
+ Secp256k1.sum(publicKey, pairTwinPublicKey)
+ } else {
+ publicKey
+ }
+ } else null
+
+ return seed?.let {
+ UserWalletId(value = calculateUserWalletId(it))
+ }
+ }
+
+ private fun calculateUserWalletId(seed: ByteArray?): ByteArray? {
+ val message = MESSAGE_FOR_WALLET_ID.toByteArray()
+ val keyHash = seed?.calculateSha256()
+
+ return if (keyHash != null) {
+ message.calculateHmacSha256(keyHash)
+ } else null
+ }
+
+ companion object {
+ private const val MESSAGE_FOR_WALLET_ID = "UserWalletID"
+
+ @Throws(IllegalArgumentException::class)
+ fun card(card: CardDTO): UserWalletIdBuilder {
+ require(!card.isTangemTwins) {
+ "For twin cards use scanResponse to ID calculation"
+ }
+
+ return UserWalletIdBuilder(findPublicKey(card.wallets))
+ }
+
+ fun scanResponse(scanResponse: ScanResponse): UserWalletIdBuilder {
+ return UserWalletIdBuilder(
+ publicKey = findPublicKey(scanResponse.card.wallets),
+ pairTwinPublicKey = when (scanResponse.productType) {
+ ProductType.Twins -> scanResponse.secondTwinPublicKey?.hexToBytes()
+ ProductType.Note,
+ ProductType.Wallet,
+ ProductType.SaltPay,
+ ProductType.Start2Coin,
+ -> null
+ },
+ )
+ }
+
+ private fun findPublicKey(wallets: List): ByteArray? {
+ return wallets.firstOrNull()
+ ?.publicKey
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt
new file mode 100644
index 0000000000..b48ae715a2
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt
@@ -0,0 +1,155 @@
+package com.tangem.tap.domain.model.builders
+
+import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.common.hdWallet.DerivationPath
+import com.tangem.domain.common.util.UserWalletId
+import com.tangem.tap.domain.model.WalletDataModel
+import com.tangem.tap.domain.model.WalletStoreModel
+import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.features.wallet.models.Currency
+import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
+import java.math.BigDecimal
+
+interface WalletStoreBuilder {
+ fun build(): WalletStoreModel
+
+ interface BlockchainNetworkWalletStoreBuilder : WalletStoreBuilder {
+ fun walletManager(walletManager: WalletManager?): WalletStoreBuilder
+ }
+
+ interface WalletMangerWalletStoreBuilder : WalletStoreBuilder
+
+ companion object {
+ operator fun invoke(
+ userWalletId: UserWalletId,
+ blockchainNetwork: BlockchainNetwork,
+ ): BlockchainNetworkWalletStoreBuilder {
+ return BlockchainNetworkWalletStoreBuilderImpl(userWalletId, blockchainNetwork)
+ }
+
+ operator fun invoke(
+ userWalletId: UserWalletId,
+ walletManager: WalletManager,
+ ): WalletMangerWalletStoreBuilder {
+ return WalletMangerWalletStoreBuilderImpl(userWalletId, walletManager)
+ }
+ }
+}
+
+private class BlockchainNetworkWalletStoreBuilderImpl(
+ private val userWalletId: UserWalletId,
+ private val blockchainNetwork: BlockchainNetwork,
+) : WalletStoreBuilder.BlockchainNetworkWalletStoreBuilder {
+ private var walletManager: WalletManager? = null
+
+ override fun walletManager(walletManager: WalletManager?) = this.apply {
+ this.walletManager = walletManager
+ }
+
+ override fun build(): WalletStoreModel {
+ val blockchainWalletData = blockchainNetwork.getBlockchainWalletData(walletManager)
+ val tokensWalletsData = blockchainNetwork.getTokensWalletsData(walletManager)
+
+ return WalletStoreModel(
+ userWalletId = userWalletId,
+ blockchain = blockchainNetwork.blockchain,
+ derivationPath = blockchainNetwork.derivationPath?.let { DerivationPath(it) },
+ walletsData = listOf(blockchainWalletData) + tokensWalletsData,
+ walletRent = null,
+ walletManager = walletManager,
+ blockchainNetwork = blockchainNetwork,
+ )
+ }
+}
+
+private class WalletMangerWalletStoreBuilderImpl(
+ private val userWalletId: UserWalletId,
+ private val walletManager: WalletManager,
+) : WalletStoreBuilder.WalletMangerWalletStoreBuilder {
+
+ override fun build(): WalletStoreModel {
+ val wallet = walletManager.wallet
+ val blockchainWalletData = wallet.blockchain.toBlockchainWalletData(walletManager)
+ val tokenWalletsData = wallet.getTokens().firstOrNull()?.toTokenWalletData(walletManager)
+
+ return WalletStoreModel(
+ userWalletId = userWalletId,
+ blockchain = wallet.blockchain,
+ derivationPath = wallet.publicKey.derivationPath,
+ walletsData = listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData),
+ walletRent = null,
+ walletManager = walletManager,
+ blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager),
+ )
+ }
+}
+
+private fun BlockchainNetwork.getBlockchainWalletData(walletManager: WalletManager?): WalletDataModel {
+ return WalletDataModel(
+ currency = Currency.Blockchain(
+ blockchain = blockchain,
+ derivationPath = derivationPath,
+ ),
+ status = WalletDataModel.Loading,
+ walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(),
+ existentialDeposit = getExistentialDeposit(walletManager),
+ fiatRate = null,
+ isCardSingleToken = false,
+ )
+}
+
+private fun BlockchainNetwork.getTokensWalletsData(walletManager: WalletManager?): List {
+ return this.tokens
+ .map { token ->
+ WalletDataModel(
+ currency = Currency.Token(
+ token = token,
+ blockchain = blockchain,
+ derivationPath = derivationPath,
+ ),
+ status = WalletDataModel.Loading,
+ walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(),
+ existentialDeposit = getExistentialDeposit(walletManager),
+ fiatRate = null,
+ isCardSingleToken = walletManager?.cardTokens?.contains(token) ?: false,
+ )
+ }
+}
+
+private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): WalletDataModel {
+ val wallet = walletManager.wallet
+ return WalletDataModel(
+ currency = Currency.Blockchain(
+ blockchain = this,
+ derivationPath = wallet.publicKey.derivationPath?.rawPath,
+ ),
+ status = WalletDataModel.Loading,
+ walletAddresses = wallet.createAddressesData(),
+ existentialDeposit = getExistentialDeposit(walletManager),
+ fiatRate = null,
+ isCardSingleToken = false,
+ )
+}
+
+private fun Token.toTokenWalletData(walletManager: WalletManager): WalletDataModel {
+ val wallet = walletManager.wallet
+ return WalletDataModel(
+ currency = Currency.Token(
+ token = this,
+ blockchain = wallet.blockchain,
+ derivationPath = wallet.publicKey.derivationPath?.rawPath,
+ ),
+ status = WalletDataModel.Loading,
+ walletAddresses = wallet.createAddressesData(),
+ existentialDeposit = getExistentialDeposit(walletManager),
+ fiatRate = null,
+ isCardSingleToken = walletManager.cardTokens.contains(this),
+ )
+}
+
+private fun getExistentialDeposit(walletManager: WalletManager?): BigDecimal? {
+ return (walletManager as? ExistentialDepositProvider)?.getExistentialDeposit()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt
index 66e41dc48e..daa0901b14 100644
--- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt
+++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/domain/converter/AddCustomTokenErrorConverter.kt
@@ -15,8 +15,9 @@ internal class AddCustomTokenErrorConverter(
private val context: Context,
) : ModuleMessageConverter {
+ @Suppress("MagicNumber")
override fun convert(message: DomainModuleError): ConvertedMessage? {
- val customTokenError = (message as? AddCustomTokenError) ?: throw UnsupportedOperationException()
+ val customTokenError = message as? AddCustomTokenError ?: throw UnsupportedOperationException()
val rawMessage = when (customTokenError) {
AddCustomTokenError.Warning.PotentialScamToken -> R.string.custom_token_validation_error_not_found
diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt
index ac8c8cd359..9db1c78996 100644
--- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt
+++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt
@@ -31,4 +31,4 @@ class SaltPayErrorConverter(
is SaltPayActivationError -> SaltPayActivationErrorConverter(context).convert(message)
else -> null
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt
index 65de384d44..1771a2db47 100644
--- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt
+++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt
@@ -14,7 +14,7 @@ internal class SaltPayActivationErrorConverter(
) : ModuleMessageConverter {
override fun convert(message: SaltPayActivationError): ConvertedDialogMessage? {
- val saltPayError = (message as? SaltPayActivationError) ?: throw UnsupportedOperationException()
+ val saltPayError = message as? SaltPayActivationError ?: throw UnsupportedOperationException()
val dialogMessage = when (saltPayError) {
SaltPayActivationError.NoGas -> {
diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt
new file mode 100644
index 0000000000..222768169b
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt
@@ -0,0 +1,255 @@
+package com.tangem.tap.domain.scanCard
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.common.core.TangemError
+import com.tangem.common.core.TangemSdkError
+import com.tangem.common.doOnFailure
+import com.tangem.common.doOnSuccess
+import com.tangem.common.services.Result
+import com.tangem.core.analytics.Analytics
+import com.tangem.core.analytics.AnalyticsEvent
+import com.tangem.domain.common.ScanResponse
+import com.tangem.domain.common.extensions.withMainContext
+import com.tangem.operations.backup.BackupService
+import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
+import com.tangem.tap.backupService
+import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor
+import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa
+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.features.disclaimer.DisclaimerType
+import com.tangem.tap.features.disclaimer.createDisclaimer
+import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
+import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
+import com.tangem.tap.features.onboarding.OnboardingHelper
+import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
+import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
+import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler
+import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
+import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
+import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState
+import com.tangem.tap.preferencesStorage
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import com.tangem.tap.tangemSdkManager
+import com.tangem.tap.userTokensRepository
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+// TODO: Create repository for that
+object ScanCardProcessor {
+ suspend fun scan(
+ analyticsEvent: AnalyticsEvent? = null,
+ additionalBlockchainsToDerive: Collection? = null,
+ cardId: String? = null,
+ onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {},
+ onScanStateChange: suspend (scanInProgress: Boolean) -> Unit = {},
+ onWalletNotCreated: suspend () -> Unit = {},
+ disclaimerWillShow: () -> Unit = {},
+ onFailure: suspend (error: TangemError) -> Unit = {},
+ onSuccess: suspend (scanResponse: ScanResponse) -> Unit = {},
+ ) = withMainContext {
+ onProgressStateChange(true)
+ onScanStateChange(true)
+
+ tangemSdkManager.changeDisplayedCardIdNumbersCount(null)
+
+ val result = tangemSdkManager.scanProduct(
+ userTokensRepository = userTokensRepository,
+ cardId = cardId,
+ additionalBlockchainsToDerive = additionalBlockchainsToDerive,
+ )
+
+ store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
+
+ result
+ .doOnFailure { error ->
+ onScanStateChange(false)
+ onFailure(error)
+ }
+ .doOnSuccess { scanResponse ->
+ tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
+
+ onScanStateChange(false)
+ sendAnalytics(analyticsEvent, scanResponse.card.batchId)
+
+ checkForUnfinishedBackupForSaltPay(
+ backupService = backupService,
+ scanResponse = scanResponse,
+ onFailure = onFailure,
+ nextHandler = { scanResponse1 ->
+ showDisclaimerIfNeed(
+ scanResponse = scanResponse1,
+ disclaimerWillShow = disclaimerWillShow,
+ onFailure = onFailure,
+ nextHandler = { scanResponse2 ->
+ onScanSuccess(
+ scanResponse = scanResponse2,
+ onProgressStateChange = onProgressStateChange,
+ onSuccess = onSuccess,
+ onWalletNotCreated = onWalletNotCreated,
+ onFailure = onFailure,
+ )
+ },
+ )
+ },
+ )
+ }
+ }
+
+ private fun sendAnalytics(
+ analyticsEvent: AnalyticsEvent?,
+ batchId: String,
+ ) {
+ Analytics.addParamsInterceptor(BatchIdParamsInterceptor(batchId))
+ analyticsEvent?.let { Analytics.send(it) }
+ }
+
+ /**
+ * It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards
+ * see BackupAction.CheckForUnfinishedBackup
+ * If user touches card other than Visa SaltPay - show dialog and block next processing
+ */
+ private suspend inline fun checkForUnfinishedBackupForSaltPay(
+ backupService: BackupService,
+ scanResponse: ScanResponse,
+ nextHandler: (ScanResponse) -> Unit,
+ onFailure: suspend (error: TangemError) -> Unit,
+ ) {
+ if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) {
+ nextHandler(scanResponse)
+ return
+ }
+
+ val isTheSamePrimaryCard = backupService.primaryCardId
+ ?.let { it == scanResponse.card.cardId }
+ ?: false
+
+ if (scanResponse.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) {
+ val error = SaltPayActivationError.PutVisaCard
+ SaltPayExceptionHandler.handle(error)
+ onFailure(TangemSdkError.ExceptionError(error))
+ } else {
+ nextHandler(scanResponse)
+ }
+ }
+
+ private suspend inline fun showDisclaimerIfNeed(
+ scanResponse: ScanResponse,
+ crossinline disclaimerWillShow: () -> Unit = {},
+ crossinline nextHandler: suspend (ScanResponse) -> Unit,
+ crossinline onFailure: suspend (error: TangemError) -> Unit,
+ ) {
+ val disclaimer = DisclaimerType.get(scanResponse.card).createDisclaimer(scanResponse.card)
+ store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer))
+
+ if (disclaimer.isAccepted()) {
+ nextHandler(scanResponse)
+ } else {
+ scope.launch {
+ delay(DELAY_SDK_DIALOG_CLOSE)
+ disclaimerWillShow()
+ dispatchOnMain(
+ DisclaimerAction.Show(
+ fromScreen = AppScreen.Home,
+ callback = DisclaimerCallback(
+ onAccept = {
+ scope.launch(Dispatchers.Main) {
+ nextHandler(scanResponse)
+ }
+ },
+ onDismiss = {
+ scope.launch(Dispatchers.Main) {
+ onFailure(TangemSdkError.UserCancelled())
+ }
+ },
+ ),
+ ),
+ )
+ }
+ }
+ }
+
+ @Suppress("LongMethod", "MagicNumber")
+ private suspend inline fun onScanSuccess(
+ scanResponse: ScanResponse,
+ crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit,
+ crossinline onWalletNotCreated: suspend () -> Unit,
+ crossinline onSuccess: suspend (ScanResponse) -> Unit,
+ crossinline onFailure: suspend (error: TangemError) -> Unit,
+ ) {
+ val globalState = store.state.globalState
+ val tapWalletManager = globalState.tapWalletManager
+ tapWalletManager.updateConfigManager(scanResponse)
+
+ store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse))
+
+ if (scanResponse.cardTypesResolver.isSaltPay()) {
+ if (scanResponse.cardTypesResolver.isSaltPayVisa()) {
+ val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
+ val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager)
+ delay(500)
+ withMainContext {
+ when (result) {
+ is Result.Success -> {
+ val isOnboardingCase = result.data
+ if (isOnboardingCase) {
+ onWalletNotCreated()
+ store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
+ store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager, config))
+ store.dispatchOnMain(OnboardingSaltPayAction.Update)
+ navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) }
+ } else {
+ delay(DELAY_SDK_DIALOG_CLOSE)
+ onSuccess(scanResponse)
+ }
+ }
+ is Result.Failure -> {
+ delay(DELAY_SDK_DIALOG_CLOSE)
+ SaltPayExceptionHandler.handle(result.error)
+ onFailure(TangemSdkError.ExceptionError(result.error))
+ }
+ }
+ }
+ } else {
+ delay(DELAY_SDK_DIALOG_CLOSE)
+ if (scanResponse.card.backupStatus?.isActive == false) {
+ val error = SaltPayActivationError.PutVisaCard
+ SaltPayExceptionHandler.handle(error)
+ onFailure(TangemSdkError.ExceptionError(error))
+ } else {
+ onSuccess(scanResponse)
+ }
+ }
+ } else {
+ if (OnboardingHelper.isOnboardingCase(scanResponse)) {
+ onWalletNotCreated()
+ store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true))
+ val appScreen = OnboardingHelper.whereToNavigate(scanResponse)
+ navigateTo(appScreen) { onProgressStateChange(it) }
+ } else {
+ if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
+ onWalletNotCreated()
+ store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse)))
+ navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) }
+ } else {
+ delay(DELAY_SDK_DIALOG_CLOSE)
+ onSuccess(scanResponse)
+ }
+ }
+ }
+ }
+
+ private suspend inline fun navigateTo(
+ screen: AppScreen,
+ onProgressStateChange: (showProgress: Boolean) -> Unit,
+ ) {
+ delay(DELAY_SDK_DIALOG_CLOSE)
+ store.dispatchOnMain(NavigationAction.NavigateTo(screen))
+ onProgressStateChange(false)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt b/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt
index 858dc8b5d1..3e98a50996 100644
--- a/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt
+++ b/app/src/main/java/com/tangem/tap/domain/statePrinter/OnboardingWalletStateConverter.kt
@@ -24,7 +24,7 @@ class OnboardingWalletStateConverter : StringStateConverter {
BackupStepAdapter(),
BigIntegerAdapter(),
- )
+ )
}
override fun convert(stateHolder: AppState): String {
@@ -56,6 +56,7 @@ class BackupStepAdapter {
}
}
+ @Suppress("MagicNumber")
@FromJson
fun fromJson(json: String): BackupStep {
return when (json) {
diff --git a/app/src/main/java/com/tangem/tap/domain/statePrinter/ScanResponseConverter.kt b/app/src/main/java/com/tangem/tap/domain/statePrinter/ScanResponseConverter.kt
index f8597dc48b..e59536ab1e 100644
--- a/app/src/main/java/com/tangem/tap/domain/statePrinter/ScanResponseConverter.kt
+++ b/app/src/main/java/com/tangem/tap/domain/statePrinter/ScanResponseConverter.kt
@@ -29,14 +29,12 @@ class ScanResponseConverter : StringStateConverter {
)
}
derivedKeysMap[keyWalletPubKey.bytes.toHexString()] = exPubKeysMap
-
}
scanResponseMap["derivedKeys"] = derivedKeysMap
val json = converter.prettyPrint(scanResponseMap)
return json
-
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt
index 140a0ef72d..5c39928f88 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletsTask.kt
@@ -13,9 +13,9 @@ import com.tangem.operations.wallet.CreateWalletTask
class CreateWalletsTask(curves: List? = null) : CardSessionRunnable {
private val curves = curves ?: listOf(
- EllipticCurve.Secp256k1,
- EllipticCurve.Ed25519,
- EllipticCurve.Secp256r1,
+ EllipticCurve.Secp256k1,
+ EllipticCurve.Ed25519,
+ EllipticCurve.Secp256r1,
)
private var index = 0
@@ -26,10 +26,10 @@ class CreateWalletsTask(curves: List? = null) : CardSessionRunnab
}
private fun createWallet(
- curve: EllipticCurve, session: CardSession,
+ curve: EllipticCurve,
+ session: CardSession,
callback: (result: CompletionResult) -> Unit
) {
-
CreateWalletTask(curve).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt
deleted file mode 100644
index 3c471f4bc4..0000000000
--- a/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-//package com.tangem.tap.domain.tasks
-//
-//import com.tangem.common.CompletionResult
-//import com.tangem.common.core.CardSession
-//import com.tangem.common.core.CardSessionRunnable
-//import com.tangem.common.core.CompletionCallback
-//import com.tangem.common.hdWallet.DerivationPath
-//import com.tangem.common.hdWallet.ExtendedPublicKey
-//import com.tangem.operations.CommandResponse
-//import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
-//import com.tangem.tap.common.extensions.ByteArrayKey
-//
-//class ExtendedPublicKeyList(
-// items: Collection
-//): ArrayList(items), CommandResponse
-//
-//
-//class DerivationTaskResponse(
-// val entries: Map
-//): CommandResponse
-//
-//class DerivationTask(
-// private val derivations: Map>
-//) : CardSessionRunnable {
-//
-// val response: MutableMap = mutableMapOf()
-//
-// override fun run(session: CardSession, callback: CompletionCallback) {
-// derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback)
-// }
-//
-// private fun derive(
-// keys: List,
-// index: Int,
-// session: CardSession,
-// callback: CompletionCallback
-// ) {
-// if (index == keys.count()) {
-// callback(CompletionResult.Success(DerivationTaskResponse(response.toMap())))
-// return
-// }
-//
-// val key = keys[index]
-// val paths = derivations[key]!!
-// DeriveWalletPublicKeysTask(key.bytes, paths).run(session) { result ->
-// when (result) {
-// is CompletionResult.Success -> {
-// response[key] = result.data[key]
-// derive(keys = keys, index = index + 1, session = session, callback = callback)
-// }
-// is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
-// }
-// }
-// }
-//}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt
index 58520125ce..3e5d37ac4b 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/SignHashTask.kt
@@ -34,4 +34,4 @@ class SignHashTask(
}
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt
index 982ed86857..7bbd4d1811 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt
@@ -1,3 +1,5 @@
+package com.tangem.tap.domain.tasks.product
+
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
@@ -9,68 +11,94 @@ import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
+import com.tangem.common.map
+import com.tangem.domain.common.CardDTO
+import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.KeyWalletPublicKey
-import com.tangem.domain.common.ProductType
import com.tangem.domain.common.TapWorkarounds.derivationStyle
-import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.operations.CommandResponse
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.backup.StartPrimaryCardLinkingTask
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.derivation.ExtendedPublicKeysMap
-import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
-import com.tangem.tap.domain.tasks.product.CreateWalletsTask
-import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
-import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
import com.tangem.tap.features.demo.DemoHelper
-
+import com.tangem.operations.wallet.CreateWalletResponse as SdkCreateWalletResponse
data class CreateProductWalletTaskResponse(
- val card: Card,
+ val card: CardDTO,
val derivedKeys: Map = mapOf(),
- val primaryCard: PrimaryCard? = null
-) : CommandResponse
+ val primaryCard: PrimaryCard? = null,
+) : CommandResponse {
+ constructor(
+ card: Card,
+ derivedKeys: Map = mapOf(),
+ primaryCard: PrimaryCard? = null,
+ ) : this(
+ card = CardDTO(card),
+ derivedKeys = derivedKeys,
+ primaryCard = primaryCard,
+ )
+}
+
+private data class CreateWalletResponse(
+ val cardId: String,
+ val wallet: CardDTO.Wallet,
+) {
+ constructor(
+ sdkResponse: SdkCreateWalletResponse,
+ ) : this(
+ cardId = sdkResponse.cardId,
+ wallet = CardDTO.Wallet(sdkResponse.wallet),
+ )
+}
class CreateProductWalletTask(
- private val type: ProductType,
+ private val cardTypesResolver: CardTypesResolver,
) : CardSessionRunnable {
+ override val allowsRequestAccessCodeFromRepository: Boolean = false
+
override fun run(
session: CardSession,
- callback: (result: CompletionResult