Updated on 2026-08-14
This commit is contained in:
commit
8cf59cdcbf
239 changed files with 4687 additions and 2357 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -32,4 +32,3 @@ local.properties
|
||||||
**/fastlane/Preview.html
|
**/fastlane/Preview.html
|
||||||
**/fastlane/screenshots
|
**/fastlane/screenshots
|
||||||
**/fastlane/test_output
|
**/fastlane/test_output
|
||||||
/buildSrc/build/
|
|
||||||
|
|
|
||||||
|
|
@ -1,117 +1,20 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
alias(deps.plugins.android.application)
|
||||||
kotlin("android")
|
alias(deps.plugins.kotlin.android)
|
||||||
kotlin("kapt")
|
alias(deps.plugins.kotlin.kapt)
|
||||||
kotlin("plugin.serialization")
|
alias(deps.plugins.kotlin.serialization)
|
||||||
id("com.google.gms.google-services")
|
alias(deps.plugins.google.services)
|
||||||
id("com.google.firebase.crashlytics")
|
alias(deps.plugins.hilt.android)
|
||||||
id("com.google.dagger.hilt.android")
|
alias(deps.plugins.firebase.crashlytics)
|
||||||
}
|
id("configuration")
|
||||||
|
|
||||||
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<com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension> {
|
|
||||||
// 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 {
|
dependencies {
|
||||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
|
implementation(files("libs/walletconnect-1.5.6.aar"))
|
||||||
implementation(project(":domain"))
|
implementation(project(":domain"))
|
||||||
implementation(project(":common"))
|
implementation(project(":common"))
|
||||||
implementation(project(":core:analytics"))
|
implementation(project(":core:analytics"))
|
||||||
|
implementation(project(":core:featuretoggles"))
|
||||||
implementation(project(":core:res"))
|
implementation(project(":core:res"))
|
||||||
implementation(project(":core:ui"))
|
implementation(project(":core:ui"))
|
||||||
implementation(project(":core:datasource"))
|
implementation(project(":core:datasource"))
|
||||||
|
|
@ -123,91 +26,93 @@ dependencies {
|
||||||
implementation(project(":features:referral:presentation"))
|
implementation(project(":features:referral:presentation"))
|
||||||
implementation(project(":features:referral:domain"))
|
implementation(project(":features:referral:domain"))
|
||||||
implementation(project(":features:referral:data"))
|
implementation(project(":features:referral:data"))
|
||||||
|
implementation(project(":features:swap:api"))
|
||||||
implementation(project(":features:swap:presentation"))
|
implementation(project(":features:swap:presentation"))
|
||||||
implementation(project(":features:swap:domain"))
|
implementation(project(":features:swap:domain"))
|
||||||
implementation(project(":features:swap:data"))
|
implementation(project(":features:swap:data"))
|
||||||
|
implementation(project(":features:tester:api"))
|
||||||
|
implementation(project(":features:tester:impl"))
|
||||||
|
|
||||||
/** AndroidX libraries */
|
/** AndroidX libraries */
|
||||||
implementation(AndroidX.coreKtx)
|
implementation(deps.androidx.core.ktx)
|
||||||
implementation(AndroidX.appCompat)
|
implementation(deps.androidx.appCompat)
|
||||||
implementation(AndroidX.fragmentKtx)
|
implementation(deps.androidx.fragment.ktx)
|
||||||
implementation(AndroidX.constraintLayout)
|
implementation(deps.androidx.constraintLayout)
|
||||||
implementation(AndroidX.browser)
|
implementation(deps.androidx.activity.compose)
|
||||||
implementation(AndroidX.lifecycleRuntimeKtx)
|
implementation(deps.androidx.browser)
|
||||||
implementation(AndroidX.lifecycleCommonJava8)
|
implementation(deps.lifecycle.runtime.ktx)
|
||||||
implementation(AndroidX.lifecycleViewModelKtx)
|
implementation(deps.lifecycle.common.java8)
|
||||||
implementation(AndroidX.lifecycleLiveDataKtx)
|
implementation(deps.lifecycle.viewModel.ktx)
|
||||||
implementation(AndroidX.activityCompose)
|
|
||||||
|
|
||||||
/** Compose libraries */
|
/** Compose libraries */
|
||||||
implementation(Compose.material)
|
implementation(deps.compose.material)
|
||||||
implementation(Compose.animation)
|
implementation(deps.compose.animation)
|
||||||
implementation(Compose.foundation)
|
implementation(deps.compose.foundation)
|
||||||
implementation(Compose.ui)
|
implementation(deps.compose.ui)
|
||||||
implementation(Compose.uiTooling)
|
implementation(deps.compose.ui.tooling)
|
||||||
implementation(Compose.coil)
|
implementation(deps.compose.coil)
|
||||||
|
implementation(deps.compose.shimmer)
|
||||||
|
|
||||||
/** Firebase libraries */
|
/** Firebase libraries */
|
||||||
implementation(platform(Firebase.bom))
|
implementation(platform(deps.firebase.bom))
|
||||||
implementation(Firebase.firebaseAnalytics)
|
implementation(deps.firebase.analytics)
|
||||||
implementation(Firebase.firebaseCrashlytics)
|
implementation(deps.firebase.crashlytics)
|
||||||
|
|
||||||
/** Tangem libraries */
|
/** Tangem libraries */
|
||||||
implementation(Tangem.blockchain) {
|
implementation(deps.tangem.blockchain) {
|
||||||
exclude(module = "joda-time")
|
exclude(module = "joda-time")
|
||||||
}
|
}
|
||||||
implementation(Tangem.cardCore)
|
implementation(deps.tangem.card.core)
|
||||||
implementation(Tangem.cardAndroid) {
|
implementation(deps.tangem.card.android) {
|
||||||
exclude(module = "joda-time")
|
exclude(module = "joda-time")
|
||||||
}
|
}
|
||||||
|
|
||||||
/** DI */
|
/** DI */
|
||||||
implementation(Library.hilt)
|
implementation(deps.hilt.android)
|
||||||
kapt(Library.hiltKapt)
|
kapt(deps.hilt.kapt)
|
||||||
|
|
||||||
/** Other libraries */
|
/** Other libraries */
|
||||||
implementation(Library.materialComponent)
|
implementation(deps.material)
|
||||||
implementation(Library.googlePlayCore)
|
implementation(deps.googlePlay.core)
|
||||||
implementation(Library.googlePlayCoreKtx)
|
implementation(deps.googlePlay.core.ktx)
|
||||||
coreLibraryDesugaring(Library.desugarJdkLibs)
|
implementation(deps.googlePlay.services.wallet)
|
||||||
implementation(Library.timber)
|
coreLibraryDesugaring(deps.desugar)
|
||||||
implementation(Library.reKotlin)
|
implementation(deps.timber)
|
||||||
implementation(Library.zxingQrCore)
|
implementation(deps.reKotlin)
|
||||||
implementation(Library.zxingQrBarcodeScanner)
|
implementation(deps.zxing.qrCore)
|
||||||
implementation(Library.otaliastudiosCameraView)
|
implementation(deps.zxing.qrBarcodeScanner)
|
||||||
implementation(Library.coil)
|
implementation(deps.otaliastudiosCameraView)
|
||||||
implementation(Library.appsflyer)
|
implementation(deps.coil)
|
||||||
implementation(Library.amplitude)
|
implementation(deps.appsflyer)
|
||||||
implementation(Library.kotsonGsonExt)
|
implementation(deps.amplitude)
|
||||||
|
implementation(deps.kotsonGson)
|
||||||
//TODO: refactoring: remove it when all network services moved to the datasource module
|
//TODO: refactoring: remove it when all network services moved to the datasource module
|
||||||
implementation(Library.retrofit)
|
implementation(deps.retrofit)
|
||||||
implementation(Library.retrofitMoshiConverter)
|
implementation(deps.retrofit.moshi)
|
||||||
implementation(Library.moshi)
|
implementation(deps.moshi)
|
||||||
implementation(Library.moshiKotlin)
|
implementation(deps.moshi.kotlin)
|
||||||
implementation(Library.okHttp)
|
implementation(deps.okHttp)
|
||||||
implementation(Library.okHttpLogging)
|
implementation(deps.okHttp.logging)
|
||||||
implementation(Library.zendeskChat)
|
implementation(deps.zendesk.chat)
|
||||||
implementation(Library.zendeskMessaging)
|
implementation(deps.zendesk.messaging)
|
||||||
implementation(Library.spongecastleCryptoCore)
|
implementation(deps.spongecastle.core)
|
||||||
implementation(Library.lottie)
|
implementation(deps.lottie)
|
||||||
implementation(Library.shopifyBuySdk) {
|
implementation(deps.shopify.buy) {
|
||||||
exclude(group = "com.shopify.graphql.support")
|
exclude(group = "com.shopify.graphql.support")
|
||||||
exclude(module = "joda-time")
|
exclude(module = "joda-time")
|
||||||
}
|
}
|
||||||
implementation(Library.accompanistAppCompatTheme)
|
implementation(deps.compose.accompanist.appCompatTheme)
|
||||||
implementation(Library.accompanistSystemUiController)
|
implementation(deps.compose.accompanist.systemUiController)
|
||||||
implementation(Library.accompanistWebView)
|
implementation(deps.compose.accompanist.webView)
|
||||||
implementation(Library.xmlShimmer)
|
implementation(deps.xmlShimmer)
|
||||||
implementation(Library.viewBindingDelegate)
|
implementation(deps.viewBindingDelegate)
|
||||||
implementation(Library.armadillo)
|
implementation(deps.armadillo)
|
||||||
implementation(Library.googlePlayServicesWallet)
|
implementation(deps.mviCore.watcher)
|
||||||
implementation(Library.composeShimmer)
|
implementation(deps.kotlin.serialization)
|
||||||
implementation(Library.mviCoreWatcher)
|
|
||||||
implementation(Library.kotlinSerialization)
|
|
||||||
|
|
||||||
/** Testing libraries */
|
/** Testing libraries */
|
||||||
testImplementation(Test.junit)
|
testImplementation(deps.test.junit)
|
||||||
testImplementation(Test.truth)
|
testImplementation(deps.test.truth)
|
||||||
androidTestImplementation(Test.junitAndroidExt)
|
androidTestImplementation(deps.test.junit.android)
|
||||||
androidTestImplementation(Test.espresso)
|
androidTestImplementation(deps.test.espresso)
|
||||||
}
|
}
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="com.tangem.wallet">
|
|
||||||
|
|
||||||
|
|
||||||
<application
|
|
||||||
tools:ignore="GoogleAppIndexingWarning">
|
|
||||||
|
|
||||||
<!-- <provider-->
|
|
||||||
<!-- android:name="com.tangem.data.LogFileProvider"-->
|
|
||||||
<!-- android:authorities="@string/log_file_provider_authorities"-->
|
|
||||||
<!-- android:enabled="true"-->
|
|
||||||
<!-- android:exported="false"-->
|
|
||||||
<!-- android:grantUriPermissions="true" />-->
|
|
||||||
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<string name="tangem_app_name">DebugTangem</string>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<string name="tangem_app_name" translatable="false">DebugTangem</string>
|
<string name="tangem_app_name" translatable="false">Debug Tangem</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
9
app/src/debug/res/xml/network_security_config.xml
Normal file
9
app/src/debug/res/xml/network_security_config.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<base-config cleartextTrafficPermitted="true">
|
||||||
|
<trust-anchors>
|
||||||
|
<certificates src="user" />
|
||||||
|
<certificates src="system" />
|
||||||
|
</trust-anchors>
|
||||||
|
</base-config>
|
||||||
|
</network-security-config>
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="com.tangem.wallet">
|
|
||||||
|
|
||||||
|
|
||||||
<application
|
|
||||||
tools:ignore="GoogleAppIndexingWarning">
|
|
||||||
|
|
||||||
<!-- <provider-->
|
|
||||||
<!-- android:name="com.tangem.data.LogFileProvider"-->
|
|
||||||
<!-- android:authorities="@string/log_file_provider_authorities"-->
|
|
||||||
<!-- android:enabled="true"-->
|
|
||||||
<!-- android:exported="false"-->
|
|
||||||
<!-- android:grantUriPermissions="true" />-->
|
|
||||||
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<string name="tangem_app_name">BetaTangem</string>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<string name="tangem_app_name">BetaTangem</string>
|
|
||||||
|
|
||||||
</resources>
|
|
||||||
6
app/src/external/res/values/strings.xml
vendored
Normal file
6
app/src/external/res/values/strings.xml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="tangem_app_name" translatable="false">External Tangem</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
6
app/src/internal/res/values/strings.xml
Normal file
6
app/src/internal/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="tangem_app_name" translatable="false">Internal Tangem</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
9
app/src/internal/res/xml/network_security_config.xml
Normal file
9
app/src/internal/res/xml/network_security_config.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<base-config cleartextTrafficPermitted="true">
|
||||||
|
<trust-anchors>
|
||||||
|
<certificates src="user" />
|
||||||
|
<certificates src="system" />
|
||||||
|
</trust-anchors>
|
||||||
|
</base-config>
|
||||||
|
</network-security-config>
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit f5f6ac954894ef66331db8fe383b9773feb69ef4
|
Subproject commit c714678d9c46dee1b15ef9e8d27aa23afa1dfd8d
|
||||||
|
|
@ -148,6 +148,17 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "the-open-network",
|
||||||
|
"symbol": "TON",
|
||||||
|
"name": "Toncoin",
|
||||||
|
"networks":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"networkId": "the-open-network/test"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id" : "Tangem Coin A-TCA",
|
"id" : "Tangem Coin A-TCA",
|
||||||
"symbol" : "TCA",
|
"symbol" : "TCA",
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.tangem.TangemSdk
|
import com.tangem.TangemSdk
|
||||||
|
import com.tangem.features.tester.api.TesterRouter
|
||||||
import com.tangem.operations.backup.BackupService
|
import com.tangem.operations.backup.BackupService
|
||||||
import com.tangem.tangem_sdk_new.extensions.init
|
import com.tangem.sdk.extensions.init
|
||||||
import com.tangem.tangem_sdk_new.extensions.initWithBiometrics
|
import com.tangem.sdk.extensions.initWithBiometrics
|
||||||
import com.tangem.tap.common.ActivityResultCallbackHolder
|
import com.tangem.tap.common.ActivityResultCallbackHolder
|
||||||
import com.tangem.tap.common.DialogManager
|
import com.tangem.tap.common.DialogManager
|
||||||
import com.tangem.tap.common.OnActivityResultCallback
|
import com.tangem.tap.common.OnActivityResultCallback
|
||||||
|
|
@ -33,6 +34,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||||
import com.tangem.tap.features.shop.redux.ShopAction
|
import com.tangem.tap.features.shop.redux.ShopAction
|
||||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
|
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
import com.tangem.wallet.databinding.ActivityMainBinding
|
import com.tangem.wallet.databinding.ActivityMainBinding
|
||||||
|
|
@ -71,6 +73,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appStateHolder: AppStateHolder
|
lateinit var appStateHolder: AppStateHolder
|
||||||
|
|
||||||
|
/** Router for opening tester menu */
|
||||||
|
@Inject
|
||||||
|
lateinit var testerRouter: TesterRouter
|
||||||
|
|
||||||
private var snackbar: Snackbar? = null
|
private var snackbar: Snackbar? = null
|
||||||
private val dialogManager = DialogManager()
|
private val dialogManager = DialogManager()
|
||||||
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
|
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
|
||||||
|
|
@ -97,6 +103,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
||||||
GooglePayService(createPaymentsClient(this), this),
|
GooglePayService(createPaymentsClient(this), this),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
store.dispatch(DaggerGraphAction.SetActivityDependencies(testerRouter))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initUserWalletsListManager() {
|
private fun initUserWalletsListManager() {
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,13 @@ import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||||
import com.tangem.blockchain.common.WalletManagerFactory
|
import com.tangem.blockchain.common.WalletManagerFactory
|
||||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
|
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||||
import com.tangem.datasource.api.common.MoshiConverter
|
import com.tangem.datasource.api.common.MoshiConverter
|
||||||
|
import com.tangem.datasource.asset.AssetReader
|
||||||
import com.tangem.datasource.config.ConfigManager
|
import com.tangem.datasource.config.ConfigManager
|
||||||
import com.tangem.datasource.config.FeaturesLocalLoader
|
import com.tangem.datasource.config.FeaturesLocalLoader
|
||||||
import com.tangem.datasource.config.models.Config
|
import com.tangem.datasource.config.models.Config
|
||||||
import com.tangem.datasource.utils.AndroidAssetReader
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
import com.tangem.datasource.utils.AssetReader
|
|
||||||
import com.tangem.domain.DomainLayer
|
import com.tangem.domain.DomainLayer
|
||||||
import com.tangem.domain.common.LogConfig
|
import com.tangem.domain.common.LogConfig
|
||||||
import com.tangem.tap.common.IntentHandler
|
import com.tangem.tap.common.IntentHandler
|
||||||
|
|
@ -48,11 +49,12 @@ import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository
|
||||||
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
|
||||||
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
|
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
|
||||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||||
import com.tangem.tap.network.NetworkConnectivity
|
|
||||||
import com.tangem.tap.persistence.PreferencesStorage
|
import com.tangem.tap.persistence.PreferencesStorage
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
|
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||||
import com.tangem.wallet.BuildConfig
|
import com.tangem.wallet.BuildConfig
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import org.rekotlin.Store
|
import org.rekotlin.Store
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -65,7 +67,6 @@ lateinit var activityResultCaller: ActivityResultCaller
|
||||||
lateinit var preferencesStorage: PreferencesStorage
|
lateinit var preferencesStorage: PreferencesStorage
|
||||||
lateinit var walletConnectRepository: WalletConnectRepository
|
lateinit var walletConnectRepository: WalletConnectRepository
|
||||||
lateinit var shopService: TangemShopService
|
lateinit var shopService: TangemShopService
|
||||||
lateinit var assetReader: AssetReader
|
|
||||||
lateinit var userTokensRepository: UserTokensRepository
|
lateinit var userTokensRepository: UserTokensRepository
|
||||||
|
|
||||||
private val walletStoresRepository by lazy { WalletStoresRepository.provideDefaultImplementation() }
|
private val walletStoresRepository by lazy { WalletStoresRepository.provideDefaultImplementation() }
|
||||||
|
|
@ -116,6 +117,15 @@ class TapApplication : Application(), ImageLoaderFactory {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var configManager: ConfigManager
|
lateinit var configManager: ConfigManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var assetReader: AssetReader
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var featureTogglesManager: FeatureTogglesManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var networkConnectionManager: NetworkConnectionManager
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
|
|
@ -136,15 +146,15 @@ class TapApplication : Application(), ImageLoaderFactory {
|
||||||
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
|
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
|
||||||
|
|
||||||
DomainLayer.init()
|
DomainLayer.init()
|
||||||
NetworkConnectivity.createInstance(store, this)
|
|
||||||
preferencesStorage = PreferencesStorage(this)
|
preferencesStorage = PreferencesStorage(this)
|
||||||
walletConnectRepository = WalletConnectRepository(this)
|
walletConnectRepository = WalletConnectRepository(this)
|
||||||
|
|
||||||
assetReader = AndroidAssetReader(this)
|
|
||||||
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
|
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
|
||||||
initConfigManager(configLoader, ::initWithConfigDependency)
|
initConfigManager(configLoader, ::initWithConfigDependency)
|
||||||
initWarningMessagesManager()
|
initWarningMessagesManager()
|
||||||
|
|
||||||
|
loadNativeLibraries()
|
||||||
|
|
||||||
if (LogConfig.network.blockchainSdkNetwork) {
|
if (LogConfig.network.blockchainSdkNetwork) {
|
||||||
BlockchainSdkRetrofitBuilder.interceptors = listOf(
|
BlockchainSdkRetrofitBuilder.interceptors = listOf(
|
||||||
HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY },
|
HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY },
|
||||||
|
|
@ -154,10 +164,23 @@ class TapApplication : Application(), ImageLoaderFactory {
|
||||||
userTokensRepository = UserTokensRepository.init(
|
userTokensRepository = UserTokensRepository.init(
|
||||||
context = this,
|
context = this,
|
||||||
tangemTechService = store.state.domainNetworks.tangemTechService,
|
tangemTechService = store.state.domainNetworks.tangemTechService,
|
||||||
|
networkConnectionManager = networkConnectionManager,
|
||||||
)
|
)
|
||||||
appStateHolder.mainStore = store
|
appStateHolder.mainStore = store
|
||||||
appStateHolder.userTokensRepository = userTokensRepository
|
appStateHolder.userTokensRepository = userTokensRepository
|
||||||
appStateHolder.walletStoresManager = walletStoresManager
|
appStateHolder.walletStoresManager = walletStoresManager
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
action = DaggerGraphAction.SetApplicationDependencies(
|
||||||
|
assetReader = assetReader,
|
||||||
|
networkConnectionManager = networkConnectionManager,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
featureTogglesManager.init()
|
||||||
|
}
|
||||||
|
|
||||||
initTopUpController()
|
initTopUpController()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,6 +203,10 @@ class TapApplication : Application(), ImageLoaderFactory {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadNativeLibraries() {
|
||||||
|
System.loadLibrary("TrustWalletCore")
|
||||||
|
}
|
||||||
|
|
||||||
private fun initConfigManager(loader: FeaturesLocalLoader, onComplete: (Config) -> Unit) {
|
private fun initConfigManager(loader: FeaturesLocalLoader, onComplete: (Config) -> Unit) {
|
||||||
configManager.load(loader) { config ->
|
configManager.load(loader) { config ->
|
||||||
store.dispatch(GlobalAction.SetConfigManager(configManager))
|
store.dispatch(GlobalAction.SetConfigManager(configManager))
|
||||||
|
|
@ -217,6 +244,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
||||||
) {
|
) {
|
||||||
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply {
|
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply {
|
||||||
appVersion = try {
|
appVersion = try {
|
||||||
|
// TODO don't use deprecated method
|
||||||
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||||
pInfo.versionName
|
pInfo.versionName
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,8 @@ object TangemSdkErrorMapper {
|
||||||
is TangemSdkError.UserCanceledBiometricsAuthentication -> error
|
is TangemSdkError.UserCanceledBiometricsAuthentication -> error
|
||||||
is TangemSdkError.EncryptionOperationFailed -> error
|
is TangemSdkError.EncryptionOperationFailed -> error
|
||||||
is TangemSdkError.InvalidEncryptionKey -> error
|
is TangemSdkError.InvalidEncryptionKey -> error
|
||||||
|
is TangemSdkError.KeyGenerationException -> error
|
||||||
|
is TangemSdkError.MnemonicException -> error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -24,6 +24,8 @@ sealed class AnalyticsParam {
|
||||||
sealed class CardBalanceState(val value: String) {
|
sealed class CardBalanceState(val value: String) {
|
||||||
object Empty : CardBalanceState("Empty")
|
object Empty : CardBalanceState("Empty")
|
||||||
object Full : CardBalanceState("Full")
|
object Full : CardBalanceState("Full")
|
||||||
|
object CustomToken : CardBalanceState("Custom token")
|
||||||
|
object BlockchainError : CardBalanceState("Blockchain error")
|
||||||
companion object
|
companion object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,6 +81,7 @@ sealed class AnalyticsParam {
|
||||||
|
|
||||||
companion object Key {
|
companion object Key {
|
||||||
const val Source = "Source"
|
const val Source = "Source"
|
||||||
|
const val Balance = "Balance"
|
||||||
const val Batch = "Batch"
|
const val Batch = "Batch"
|
||||||
const val ProductType = "Product Type"
|
const val ProductType = "Product Type"
|
||||||
const val Firmware = "Firmware"
|
const val Firmware = "Firmware"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,13 @@ sealed class Basic(
|
||||||
error: Throwable? = null,
|
error: Throwable? = null,
|
||||||
) : AnalyticsEvent("Basic", event, params, error) {
|
) : AnalyticsEvent("Basic", event, params, error) {
|
||||||
|
|
||||||
|
class BalanceLoaded(balance: AnalyticsParam.CardBalanceState) : Basic(
|
||||||
|
event = "Balance Loaded",
|
||||||
|
params = mapOf(
|
||||||
|
AnalyticsParam.Balance to balance.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
class CardWasScanned(
|
class CardWasScanned(
|
||||||
source: AnalyticsParam.ScannedFrom,
|
source: AnalyticsParam.ScannedFrom,
|
||||||
) : Basic(
|
) : Basic(
|
||||||
|
|
@ -51,4 +58,6 @@ sealed class Basic(
|
||||||
event = "Scan",
|
event = "Scan",
|
||||||
error = error,
|
error = error,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class WalletOpened : Basic(event = "Wallet Opened")
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,6 @@ sealed class MainScreen(
|
||||||
) : AnalyticsEvent("Main Screen", event, params) {
|
) : AnalyticsEvent("Main Screen", event, params) {
|
||||||
|
|
||||||
class ScreenOpened : MainScreen("Screen opened")
|
class ScreenOpened : MainScreen("Screen opened")
|
||||||
|
|
||||||
class ButtonScanCard : MainScreen("Button - Scan Card")
|
class ButtonScanCard : MainScreen("Button - Scan Card")
|
||||||
class ButtonMyWallets : MainScreen("Button - My Wallets")
|
class ButtonMyWallets : MainScreen("Button - My Wallets")
|
||||||
class ButtonBuy : MainScreen("Button - Buy")
|
class ButtonBuy : MainScreen("Button - Buy")
|
||||||
|
|
|
||||||
|
|
@ -71,16 +71,16 @@ sealed class Onboarding(
|
||||||
class ButtonSetPinCode : Onboarding("Onboarding", "Button - Set PIN Code")
|
class ButtonSetPinCode : Onboarding("Onboarding", "Button - Set PIN Code")
|
||||||
class CardConnectionScreenOpened : Onboarding("Onboarding", "Card Connection Screen Opened")
|
class CardConnectionScreenOpened : Onboarding("Onboarding", "Card Connection Screen Opened")
|
||||||
class ButtonConnect : Onboarding("Onboarding", "Button - Connect")
|
class ButtonConnect : Onboarding("Onboarding", "Button - Connect")
|
||||||
class PinCodeSet : Onboarding("Onboarding", "PIN code set")
|
class PinCodeSet : Onboarding("Onboarding", "PIN Code Set")
|
||||||
|
|
||||||
class KYCScreenOpened : Onboarding("Onboarding", "KYC screen opened")
|
class KYCScreenOpened : Onboarding("Onboarding", "KYC screen opened")
|
||||||
class KYCStarted : Onboarding("Onboarding", "KYC started")
|
class KYCStarted : Onboarding("Onboarding", "KYC Started")
|
||||||
class KYCInProgress : Onboarding("Onboarding", "KYC in progress")
|
class KYCInProgress : Onboarding("Onboarding", "KYC In Progress")
|
||||||
class KYCRejected : Onboarding("Onboarding", "KYC rejected")
|
class KYCRejected : Onboarding("Onboarding", "KYC Rejected")
|
||||||
|
|
||||||
class ClaimScreenOpened : Onboarding("Onboarding", "Claim screen opened")
|
class ClaimScreenOpened : Onboarding("Onboarding", "Claim Screen Opened")
|
||||||
class ButtonClaim : Onboarding("Onboarding", "Button - Claim")
|
class ButtonClaim : Onboarding("Onboarding", "Button - Claim")
|
||||||
class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim was successfully")
|
class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim Was Successfully")
|
||||||
|
|
||||||
class ButtonChat : Onboarding("Onboarding", "Button - Chat")
|
class ButtonChat : Onboarding("Onboarding", "Button - Chat")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,16 +60,16 @@ sealed class Settings(
|
||||||
params: Map<String, String> = mapOf(),
|
params: Map<String, String> = mapOf(),
|
||||||
) : Settings(category = "Settings / App Settings", event = event, params = params) {
|
) : Settings(category = "Settings / App Settings", event = event, params = params) {
|
||||||
|
|
||||||
class SaveWalletSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings(
|
class SaveWalletSwitcherChanged(state: AnalyticsParam.OnOffState) : AppSettings(
|
||||||
event = "Save Wallet Switcher Changed",
|
event = "Save Wallet Switcher Changed",
|
||||||
params = mapOf("State" to state.value),
|
params = mapOf("State" to state.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
class SaveAccessCodeSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings(
|
class SaveAccessCodeSwitcherChanged(state: AnalyticsParam.OnOffState) : AppSettings(
|
||||||
event = "Save Access Code Switcher Changed",
|
event = "Save Access Code Switcher Changed",
|
||||||
params = mapOf("State" to state.value),
|
params = mapOf("State" to state.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
object ButtonEnableBiometricAuthentication : AppSettings("Button - Enable Biometric Authentication")
|
object ButtonEnableBiometricAuthentication : AppSettings(event = "Button - Enable Biometric Authentication")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ sealed class Token(
|
||||||
|
|
||||||
class Bought(type: CurrencyType) : Token(
|
class Bought(type: CurrencyType) : Token(
|
||||||
category = "Token",
|
category = "Token",
|
||||||
event = "Token bought",
|
event = "Token Bought",
|
||||||
params = mapOf("Token" to type.value),
|
params = mapOf("Token" to type.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
import com.tangem.sdk.extensions.dpToPx
|
||||||
import com.tangem.tangem_sdk_new.extensions.pxToDp
|
import com.tangem.sdk.extensions.pxToDp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
|
||||||
Blockchain.Kusama -> R.drawable.ic_kusama_no_color
|
Blockchain.Kusama -> R.drawable.ic_kusama_no_color
|
||||||
Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism_no_color
|
Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism_no_color
|
||||||
Blockchain.Dash -> R.drawable.ic_dash_no_color
|
Blockchain.Dash -> R.drawable.ic_dash_no_color
|
||||||
|
Blockchain.Kaspa -> R.drawable.ic_kaspa_no_color
|
||||||
else -> R.drawable.ic_tangem_logo
|
else -> R.drawable.ic_tangem_logo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
|
||||||
import com.tangem.tap.features.send.ui.SendFragment
|
import com.tangem.tap.features.send.ui.SendFragment
|
||||||
import com.tangem.tap.features.shop.ui.ShopFragment
|
import com.tangem.tap.features.shop.ui.ShopFragment
|
||||||
import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment
|
import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment
|
||||||
import com.tangem.tap.features.tokens.ui.AddTokensFragment
|
import com.tangem.tap.features.tokens.presentation.AddTokensFragment
|
||||||
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
|
||||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||||
import com.tangem.tap.features.walletSelector.ui.WalletSelectorBottomSheetFragment
|
import com.tangem.tap.features.walletSelector.ui.WalletSelectorBottomSheetFragment
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,12 @@ fun Store<*>.dispatchNotification(resId: Int) {
|
||||||
dispatchOnMain(GlobalAction.ShowNotification(resId))
|
dispatchOnMain(GlobalAction.ShowNotification(resId))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) {
|
suspend fun Store<AppState>.onUserWalletSelected(
|
||||||
state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh)
|
userWallet: UserWallet,
|
||||||
|
refresh: Boolean = false,
|
||||||
|
sendAnalyticsEvent: Boolean = false,
|
||||||
|
) {
|
||||||
|
state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh, sendAnalyticsEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Store<*>.dispatchToastNotification(resId: Int) {
|
fun Store<*>.dispatchToastNotification(resId: Int) {
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ import com.tangem.tap.features.demo.isDemoCard
|
||||||
import com.tangem.tap.features.wallet.models.Currency
|
import com.tangem.tap.features.wallet.models.Currency
|
||||||
import com.tangem.tap.features.wallet.redux.AddressData
|
import com.tangem.tap.features.wallet.redux.AddressData
|
||||||
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
|
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
|
||||||
import com.tangem.tap.network.NetworkConnectivity
|
|
||||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||||
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -47,7 +47,8 @@ suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Timber.e(exception)
|
Timber.e(exception)
|
||||||
|
|
||||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||||
|
if (!networkConnectionManager.isOnline) {
|
||||||
Result.Failure(TapError.NoInternetConnection)
|
Result.Failure(TapError.NoInternetConnection)
|
||||||
} else {
|
} else {
|
||||||
val blockchain = wallet.blockchain
|
val blockchain = wallet.blockchain
|
||||||
|
|
@ -87,8 +88,6 @@ fun WalletManager?.getAddressData(): AddressData? {
|
||||||
return if (addressDataList.isEmpty()) null else addressDataList[0]
|
return if (addressDataList.isEmpty()) null else addressDataList[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun WalletManager.getTxHistory(currency: Currency): List<TransactionData> = wallet.getTxHistory(currency)
|
|
||||||
|
|
||||||
fun WalletManager.getBlockchainTxHistory(): List<TransactionData> = wallet.getBlockchainTxHistory()
|
fun WalletManager.getBlockchainTxHistory(): List<TransactionData> = wallet.getBlockchainTxHistory()
|
||||||
|
|
||||||
fun WalletManager.getTokenTxHistory(token: Token): List<TransactionData> = wallet.getTokenTxHistory(token)
|
fun WalletManager.getTokenTxHistory(token: Token): List<TransactionData> = wallet.getTokenTxHistory(token)
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import android.widget.FrameLayout
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.transition.TransitionInflater
|
import androidx.transition.TransitionInflater
|
||||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidgetState
|
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidgetState
|
||||||
import com.tangem.tap.domain.twins.TwinsCardWidget
|
import com.tangem.tap.domain.twins.TwinsCardWidget
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
import com.tangem.wallet.databinding.TestLeapfrogFragmentBinding
|
import com.tangem.wallet.databinding.TestLeapfrogFragmentBinding
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.common.recyclerView
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
import com.tangem.sdk.extensions.dpToPx
|
||||||
|
|
||||||
class SpaceItemDecoration(
|
class SpaceItemDecoration(
|
||||||
private val horizontalSpaceDp: Float,
|
private val horizontalSpaceDp: Float,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
|
||||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
|
import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
|
||||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
|
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||||
import org.rekotlin.Action
|
import org.rekotlin.Action
|
||||||
|
|
||||||
fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder): AppState {
|
fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder): AppState {
|
||||||
|
|
@ -46,6 +47,7 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
|
||||||
walletSelectorState = WalletSelectorReducer.reduce(action, state),
|
walletSelectorState = WalletSelectorReducer.reduce(action, state),
|
||||||
sprinklrState = SprinklrReducer.reduce(action, state),
|
sprinklrState = SprinklrReducer.reduce(action, state),
|
||||||
signInState = SignInReducer.reduce(action, state),
|
signInState = SignInReducer.reduce(action, state),
|
||||||
|
daggerGraphState = DaggerGraphReducer.reduce(action, state),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ import com.tangem.tap.features.walletSelector.redux.WalletSelectorMiddleware
|
||||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
||||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||||
|
import com.tangem.tap.proxy.redux.DaggerGraphMiddleware
|
||||||
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||||
import org.rekotlin.Middleware
|
import org.rekotlin.Middleware
|
||||||
import org.rekotlin.StateType
|
import org.rekotlin.StateType
|
||||||
|
|
@ -66,7 +68,8 @@ data class AppState(
|
||||||
val saveWalletState: SaveWalletState = SaveWalletState(),
|
val saveWalletState: SaveWalletState = SaveWalletState(),
|
||||||
val walletSelectorState: WalletSelectorState = WalletSelectorState(),
|
val walletSelectorState: WalletSelectorState = WalletSelectorState(),
|
||||||
val sprinklrState: SprinklrState = SprinklrState(),
|
val sprinklrState: SprinklrState = SprinklrState(),
|
||||||
val signInState: SignInState = SignInState()
|
val signInState: SignInState = SignInState(),
|
||||||
|
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
|
||||||
) : StateType {
|
) : StateType {
|
||||||
|
|
||||||
val domainState: DomainState
|
val domainState: DomainState
|
||||||
|
|
@ -108,7 +111,8 @@ data class AppState(
|
||||||
LockUserWalletsTimerMiddleware().middleware,
|
LockUserWalletsTimerMiddleware().middleware,
|
||||||
AccessCodeRequestPolicyMiddleware().middleware,
|
AccessCodeRequestPolicyMiddleware().middleware,
|
||||||
SprinklrMiddleware().middleware,
|
SprinklrMiddleware().middleware,
|
||||||
SignInMiddleware.middleware
|
SignInMiddleware.middleware,
|
||||||
|
DaggerGraphMiddleware.daggerGraphMiddleware,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import com.tangem.datasource.config.ConfigManager
|
||||||
import com.tangem.domain.common.ScanResponse
|
import com.tangem.domain.common.ScanResponse
|
||||||
import com.tangem.domain.common.extensions.withMainContext
|
import com.tangem.domain.common.extensions.withMainContext
|
||||||
import com.tangem.operations.attestation.Attestation
|
import com.tangem.operations.attestation.Attestation
|
||||||
|
import com.tangem.tap.common.analytics.events.Basic
|
||||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||||
import com.tangem.tap.common.extensions.setContext
|
import com.tangem.tap.common.extensions.setContext
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
|
|
@ -36,8 +37,11 @@ class TapWalletManager {
|
||||||
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
|
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun onWalletSelected(userWallet: UserWallet, refresh: Boolean) {
|
suspend fun onWalletSelected(userWallet: UserWallet, refresh: Boolean, sendAnalyticsEvent: Boolean) {
|
||||||
Analytics.setContext(userWallet.scanResponse)
|
Analytics.setContext(userWallet.scanResponse)
|
||||||
|
if (sendAnalyticsEvent) {
|
||||||
|
Analytics.send(Basic.WalletOpened())
|
||||||
|
}
|
||||||
val scanResponse = userWallet.scanResponse
|
val scanResponse = userWallet.scanResponse
|
||||||
val card = scanResponse.card
|
val card = scanResponse.card
|
||||||
val attestationFailed = card.attestation.status == Attestation.Status.Failed
|
val attestationFailed = card.attestation.status == Attestation.Status.Failed
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import com.tangem.common.services.Result
|
||||||
import com.tangem.datasource.api.common.MoshiConverter
|
import com.tangem.datasource.api.common.MoshiConverter
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||||
|
import com.tangem.datasource.asset.AssetReader
|
||||||
import com.tangem.domain.common.extensions.toNetworkId
|
import com.tangem.domain.common.extensions.toNetworkId
|
||||||
import com.tangem.datasource.utils.AssetReader
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ class LoadAvailableCoinsService(
|
||||||
return withContext(dispatchers.io) {
|
return withContext(dispatchers.io) {
|
||||||
runCatching {
|
runCatching {
|
||||||
tangemTechApi.getCoins(
|
tangemTechApi.getCoins(
|
||||||
networkIds = supportedBlockchains.toSet().map(Blockchain::toNetworkId).joinToString(","),
|
networkIds = supportedBlockchains.toSet().joinToString(",", transform = Blockchain::toNetworkId),
|
||||||
active = true,
|
active = true,
|
||||||
searchText = searchInput,
|
searchText = searchInput,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
|
|
@ -74,8 +74,8 @@ class LoadAvailableCoinsService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTestnetCoins(): List<Currency> {
|
private fun getTestnetCoins(): List<Currency> {
|
||||||
val json = assetReader.readAssetAsString(FILE_NAME_TESTNET_COINS)
|
val json = assetReader.readJson(FILE_NAME_TESTNET_COINS)
|
||||||
return currenciesAdapter.fromJson(json)!!.coins
|
return currenciesAdapter.fromJson(json)!!.coins
|
||||||
.map { Currency.fromJsonObject(it) }
|
.map { Currency.fromJsonObject(it) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
package com.tangem.tap.domain.tokens
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter
|
|
||||||
import com.squareup.moshi.Types
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.blockchain.common.DerivationStyle
|
|
||||||
import com.tangem.datasource.api.common.MoshiConverter
|
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
|
||||||
import com.tangem.domain.common.extensions.toNetworkId
|
|
||||||
import com.tangem.tap.common.FileReader
|
|
||||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
|
||||||
import com.tangem.tap.domain.tokens.models.TokenDao
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
@Deprecated("Use this only for migration")
|
|
||||||
class OldUserTokensRepository(
|
|
||||||
private val fileReader: FileReader,
|
|
||||||
private val tangemTechApi: TangemTechApi,
|
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
|
||||||
) {
|
|
||||||
private val moshi = MoshiConverter.networkMoshi
|
|
||||||
private val blockchainsAdapter: JsonAdapter<List<Blockchain>> = moshi.adapter(
|
|
||||||
Types.newParameterizedType(List::class.java, Blockchain::class.java),
|
|
||||||
)
|
|
||||||
private val tokensAdapter: JsonAdapter<List<TokenDao>> = moshi.adapter(
|
|
||||||
Types.newParameterizedType(List::class.java, TokenDao::class.java),
|
|
||||||
)
|
|
||||||
private val blockchainNetworkAdapter: JsonAdapter<List<BlockchainNetwork>> =
|
|
||||||
moshi.adapter(Types.newParameterizedType(List::class.java, BlockchainNetwork::class.java))
|
|
||||||
|
|
||||||
@Deprecated("Use BlockchainNetwork instead")
|
|
||||||
private fun loadSavedTokens(cardId: String): List<TokenDao> {
|
|
||||||
val json = try {
|
|
||||||
fileReader.readFile(getFileNameForTokens(cardId))
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
return try {
|
|
||||||
tokensAdapter.fromJson(json) ?: emptyList()
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Use BlockchainNetwork instead")
|
|
||||||
private fun loadSavedBlockchains(cardId: String): List<Blockchain> {
|
|
||||||
return try {
|
|
||||||
val json = fileReader.readFile(getFileNameForBlockchains(cardId))
|
|
||||||
blockchainsAdapter.fromJson(json)?.distinct() ?: emptyList()
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Use TokensRepository instead")
|
|
||||||
suspend fun loadSavedCurrencies(
|
|
||||||
cardId: String,
|
|
||||||
isHdWalletSupported: Boolean = false,
|
|
||||||
): List<BlockchainNetwork> {
|
|
||||||
return try {
|
|
||||||
val json = fileReader.readFile(getFileNameForBlockchains(cardId))
|
|
||||||
blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList()
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
tryToLoadPreviousFormatAndMigrate(cardId, isHdWalletSupported)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun tryToLoadPreviousFormatAndMigrate(
|
|
||||||
cardId: String,
|
|
||||||
isHdWalletSupported: Boolean = false,
|
|
||||||
): List<BlockchainNetwork> {
|
|
||||||
return try {
|
|
||||||
loadSavedCurrenciesOldWay(
|
|
||||||
cardId,
|
|
||||||
isHdWalletSupported,
|
|
||||||
)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun loadSavedCurrenciesOldWay(
|
|
||||||
cardId: String,
|
|
||||||
isHdWalletSupported: Boolean = false,
|
|
||||||
): List<BlockchainNetwork> {
|
|
||||||
val blockchains = loadSavedBlockchains(cardId)
|
|
||||||
val tokens = loadSavedTokens(cardId)
|
|
||||||
val ids = getTokensIds(tokens)
|
|
||||||
val derivationStyle = if (isHdWalletSupported) DerivationStyle.LEGACY else null
|
|
||||||
val blockchainNetworks = blockchains.map { blockchain ->
|
|
||||||
BlockchainNetwork(
|
|
||||||
blockchain = blockchain,
|
|
||||||
derivationPath = blockchain.derivationPath(derivationStyle)?.rawPath,
|
|
||||||
tokens = tokens
|
|
||||||
.filter { it.blockchainDao.toBlockchain() == blockchain }
|
|
||||||
.map {
|
|
||||||
val token = it.toToken()
|
|
||||||
token.copy(id = ids[token.contractAddress])
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return blockchainNetworks
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getTokensIds(tokens: List<TokenDao>): Map<String, String> = withContext(dispatchers.io) {
|
|
||||||
tokens.map {
|
|
||||||
async {
|
|
||||||
tangemTechApi.getCoins(
|
|
||||||
contractAddress = it.contractAddress,
|
|
||||||
networkIds = it.blockchainDao.toBlockchain().toNetworkId(),
|
|
||||||
active = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map {
|
|
||||||
runCatching { it.await() }
|
|
||||||
.onSuccess { return@map it.coins.firstOrNull()?.id }
|
|
||||||
.onFailure { return@map null }
|
|
||||||
|
|
||||||
error("Unreachable code because runCatching must return result")
|
|
||||||
}
|
|
||||||
.mapIndexedNotNull { index, id ->
|
|
||||||
if (id == null) null else tokens[index].contractAddress to id
|
|
||||||
}
|
|
||||||
.toMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
|
|
||||||
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
|
|
||||||
private fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
|
|
||||||
private fun getFileNameForBlockchains(cardId: String): String =
|
|
||||||
"${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.core.TangemSdkError
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechService
|
import com.tangem.datasource.api.tangemTech.TangemTechService
|
||||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||||
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
import com.tangem.domain.common.CardDTO
|
import com.tangem.domain.common.CardDTO
|
||||||
import com.tangem.tap.common.AndroidFileReader
|
import com.tangem.tap.common.AndroidFileReader
|
||||||
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
|
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
|
||||||
|
|
@ -15,7 +16,6 @@ import com.tangem.tap.features.demo.DemoHelper
|
||||||
import com.tangem.tap.features.wallet.models.Currency
|
import com.tangem.tap.features.wallet.models.Currency
|
||||||
import com.tangem.tap.features.wallet.models.toBlockchainNetworks
|
import com.tangem.tap.features.wallet.models.toBlockchainNetworks
|
||||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
import com.tangem.tap.features.wallet.models.toCurrencies
|
||||||
import com.tangem.tap.network.NetworkConnectivity
|
|
||||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -25,33 +25,31 @@ class UserTokensRepository(
|
||||||
private val storageService: UserTokensStorageService,
|
private val storageService: UserTokensStorageService,
|
||||||
private val tangemTechApi: TangemTechApi,
|
private val tangemTechApi: TangemTechApi,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
private val networkConnectionManager: NetworkConnectionManager,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// TODO("After adding DI") replace with CoroutineDispatcherProvider
|
|
||||||
suspend fun getUserTokens(card: CardDTO): List<Currency> = withContext(dispatchers.io) {
|
suspend fun getUserTokens(card: CardDTO): List<Currency> = withContext(dispatchers.io) {
|
||||||
val userId = getUserWalletId(card) ?: return@withContext emptyList()
|
val userWalletId = getUserWalletId(card) ?: return@withContext emptyList()
|
||||||
|
|
||||||
if (DemoHelper.isDemoCardId(card.cardId)) {
|
if (DemoHelper.isDemoCardId(card.cardId)) {
|
||||||
return@withContext loadTokensOffline(card, userId).ifEmpty(::loadDemoCurrencies)
|
return@withContext loadTokensOffline(userWalletId = userWalletId).ifEmpty(::loadDemoCurrencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
if (!networkConnectionManager.isOnline) return@withContext loadTokensOffline(userWalletId = userWalletId)
|
||||||
return@withContext loadTokensOffline(card, userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return@withContext remoteGetUserTokens(card, userId)
|
return@withContext remoteGetUserTokens(userWalletId = userWalletId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO("After adding DI") replace with CoroutineDispatcherProvider
|
|
||||||
suspend fun saveUserTokens(card: CardDTO, tokens: List<Currency>) = withContext(dispatchers.io) {
|
suspend fun saveUserTokens(card: CardDTO, tokens: List<Currency>) = withContext(dispatchers.io) {
|
||||||
val userId = getUserWalletId(card) ?: return@withContext
|
val userWalletId = getUserWalletId(card) ?: return@withContext
|
||||||
val userTokens = tokens.toUserTokensResponse()
|
val userTokens = tokens.toUserTokensResponse()
|
||||||
remoteSaveUserTokens(userId, userTokens)
|
remoteSaveUserTokens(userWalletId = userWalletId, userTokens = userTokens)
|
||||||
storageService.saveUserTokens(userId, userTokens)
|
storageService.saveUserTokens(userWalletId = userWalletId, tokens = userTokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun loadBlockchainsToDerive(card: CardDTO): List<BlockchainNetwork> = withContext(dispatchers.io) {
|
suspend fun loadBlockchainsToDerive(card: CardDTO): List<BlockchainNetwork> = withContext(dispatchers.io) {
|
||||||
val userId = getUserWalletId(card) ?: return@withContext emptyList()
|
val userWalletId = getUserWalletId(card) ?: return@withContext emptyList()
|
||||||
val blockchainNetworks = loadTokensOffline(card = card, userId = userId).toBlockchainNetworks()
|
val blockchainNetworks = loadTokensOffline(userWalletId = userWalletId).toBlockchainNetworks()
|
||||||
|
|
||||||
if (DemoHelper.isDemoCardId(card.cardId)) {
|
if (DemoHelper.isDemoCardId(card.cardId)) {
|
||||||
return@withContext blockchainNetworks.ifEmpty(loadDemoCurrencies()::toBlockchainNetworks)
|
return@withContext blockchainNetworks.ifEmpty(loadDemoCurrencies()::toBlockchainNetworks)
|
||||||
|
|
@ -60,8 +58,8 @@ class UserTokensRepository(
|
||||||
return@withContext blockchainNetworks
|
return@withContext blockchainNetworks
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadTokensOffline(card: CardDTO, userId: String): List<Currency> {
|
private fun loadTokensOffline(userWalletId: String): List<Currency> {
|
||||||
return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card)
|
return storageService.getUserTokens(userWalletId = userWalletId) ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadDemoCurrencies(): List<Currency> {
|
private fun loadDemoCurrencies(): List<Currency> {
|
||||||
|
|
@ -76,54 +74,49 @@ class UserTokensRepository(
|
||||||
.flatMap(BlockchainNetwork::toCurrencies)
|
.flatMap(BlockchainNetwork::toCurrencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Currency>.toUserTokensResponse() = UserTokensResponse(
|
private fun List<Currency>.toUserTokensResponse(): UserTokensResponse {
|
||||||
tokens = CurrencyConverter.convertList(this),
|
return UserTokensResponse(
|
||||||
group = GROUP_DEFAULT_VALUE,
|
tokens = CurrencyConverter.convertList(input = this),
|
||||||
sort = SORT_DEFAULT_VALUE,
|
group = GROUP_DEFAULT_VALUE,
|
||||||
)
|
sort = SORT_DEFAULT_VALUE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun handleGetUserTokensFailure(card: CardDTO, userId: String, error: Throwable): List<Currency> {
|
private suspend fun handleGetUserTokensFailure(userWalletId: String, error: Throwable): List<Currency> {
|
||||||
return when {
|
return when {
|
||||||
error is TangemSdkError.NetworkError && error.customMessage.contains(NOT_FOUND_HTTP_CODE) ->
|
error is TangemSdkError.NetworkError && error.customMessage.contains(NOT_FOUND_HTTP_CODE) -> {
|
||||||
storageService.getUserTokens(card).also {
|
storageService
|
||||||
remoteSaveUserTokens(userId = userId, userTokens = it.toUserTokensResponse())
|
.getUserTokens(userWalletId)
|
||||||
}
|
?.also { remoteSaveUserTokens(userWalletId = userWalletId, userTokens = it.toUserTokensResponse()) }
|
||||||
|
?: emptyList()
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card)
|
storageService.getUserTokens(userWalletId)?.distinct() ?: emptyList()
|
||||||
tokens.distinct()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun remoteGetUserTokens(card: CardDTO, userId: String): List<Currency> {
|
private suspend fun remoteGetUserTokens(userWalletId: String): List<Currency> {
|
||||||
runCatching {
|
return runCatching { tangemTechApi.getUserTokens(userWalletId) }
|
||||||
tangemTechApi.getUserTokens(userId)
|
.fold(
|
||||||
}.onSuccess { response ->
|
onSuccess = { response ->
|
||||||
return response.tokens
|
response.tokens
|
||||||
.mapNotNull(Currency.Companion::fromTokenResponse).also {
|
.mapNotNull(Currency.Companion::fromTokenResponse)
|
||||||
storageService.saveUserTokens(userId, it.toUserTokensResponse())
|
.also { storageService.saveUserTokens(userWalletId, it.toUserTokensResponse()) }
|
||||||
}
|
.distinct()
|
||||||
.distinct()
|
},
|
||||||
}.onFailure {
|
onFailure = { handleGetUserTokensFailure(userWalletId = userWalletId, error = it) },
|
||||||
return handleGetUserTokensFailure(card = card, userId = userId, error = it)
|
)
|
||||||
}
|
|
||||||
error("Unreachable code because runCatching must return result")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun remoteSaveUserTokens(userId: String, userTokens: UserTokensResponse) {
|
private suspend fun remoteSaveUserTokens(userWalletId: String, userTokens: UserTokensResponse) {
|
||||||
// it can throw okhttp3.internal.http2.StreamResetException: stream was reset: INTERNAL_ERROR
|
// it can throw okhttp3.internal.http2.StreamResetException: stream was reset: INTERNAL_ERROR
|
||||||
// if the /user-tokens endpoint disabled
|
// if the /user-tokens endpoint disabled
|
||||||
runCatching {
|
runCatching { tangemTechApi.saveUserTokens(userWalletId, userTokens) }
|
||||||
tangemTechApi.saveUserTokens(userId, userTokens)
|
.onFailure { Timber.e(it) }
|
||||||
}.onFailure {
|
|
||||||
Timber.e(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getUserWalletId(card: CardDTO): String? {
|
private fun getUserWalletId(card: CardDTO): String? = UserWalletIdBuilder.card(card).build()?.stringValue
|
||||||
return UserWalletIdBuilder.card(card).build()
|
|
||||||
?.stringValue
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val GROUP_DEFAULT_VALUE = "none"
|
private const val GROUP_DEFAULT_VALUE = "none"
|
||||||
|
|
@ -131,24 +124,16 @@ class UserTokensRepository(
|
||||||
private const val NOT_FOUND_HTTP_CODE = "404"
|
private const val NOT_FOUND_HTTP_CODE = "404"
|
||||||
|
|
||||||
// TODO("After adding DI") get dependencies by DI
|
// TODO("After adding DI") get dependencies by DI
|
||||||
fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository {
|
fun init(
|
||||||
val fileReader = AndroidFileReader(context)
|
context: Context,
|
||||||
val dispatchers = AppCoroutineDispatcherProvider()
|
tangemTechService: TangemTechService,
|
||||||
|
networkConnectionManager: NetworkConnectionManager,
|
||||||
val oldUserTokensRepository = OldUserTokensRepository(
|
): UserTokensRepository {
|
||||||
fileReader = fileReader,
|
|
||||||
tangemTechApi = tangemTechService.api,
|
|
||||||
dispatchers = dispatchers,
|
|
||||||
)
|
|
||||||
val storageService = UserTokensStorageService(
|
|
||||||
oldUserTokensRepository = oldUserTokensRepository,
|
|
||||||
fileReader = fileReader,
|
|
||||||
)
|
|
||||||
|
|
||||||
return UserTokensRepository(
|
return UserTokensRepository(
|
||||||
storageService = storageService,
|
storageService = UserTokensStorageService(fileReader = AndroidFileReader(context)),
|
||||||
tangemTechApi = tangemTechService.api,
|
tangemTechApi = tangemTechService.api,
|
||||||
dispatchers = dispatchers,
|
dispatchers = AppCoroutineDispatcherProvider(),
|
||||||
|
networkConnectionManager = networkConnectionManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,16 @@ import com.squareup.moshi.JsonAdapter
|
||||||
import com.tangem.Log
|
import com.tangem.Log
|
||||||
import com.tangem.datasource.api.common.MoshiConverter
|
import com.tangem.datasource.api.common.MoshiConverter
|
||||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||||
import com.tangem.domain.common.CardDTO
|
|
||||||
import com.tangem.tap.common.FileReader
|
import com.tangem.tap.common.FileReader
|
||||||
import com.tangem.tap.features.wallet.models.Currency
|
import com.tangem.tap.features.wallet.models.Currency
|
||||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
|
||||||
|
|
||||||
class UserTokensStorageService(
|
class UserTokensStorageService(private val fileReader: FileReader) {
|
||||||
private val oldUserTokensRepository: OldUserTokensRepository,
|
|
||||||
private val fileReader: FileReader,
|
|
||||||
) {
|
|
||||||
private val userTokensAdapter: JsonAdapter<UserTokensResponse> =
|
private val userTokensAdapter: JsonAdapter<UserTokensResponse> =
|
||||||
MoshiConverter.networkMoshi.adapter(UserTokensResponse::class.java)
|
MoshiConverter.networkMoshi.adapter(UserTokensResponse::class.java)
|
||||||
|
|
||||||
fun getUserTokens(userId: String): List<Currency>? {
|
fun getUserTokens(userWalletId: String): List<Currency>? {
|
||||||
return try {
|
return try {
|
||||||
val json = fileReader.readFile(getFileNameForUserTokens(userId))
|
val json = fileReader.readFile(getFileNameForUserTokens(userWalletId))
|
||||||
userTokensAdapter.fromJson(json)?.tokens?.mapNotNull { Currency.fromTokenResponse(it) }
|
userTokensAdapter.fromJson(json)?.tokens?.mapNotNull { Currency.fromTokenResponse(it) }
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Log.error { exception.stackTraceToString() }
|
Log.error { exception.stackTraceToString() }
|
||||||
|
|
@ -26,16 +21,9 @@ class UserTokensStorageService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("")
|
fun saveUserTokens(userWalletId: String, tokens: UserTokensResponse) {
|
||||||
suspend fun getUserTokens(card: CardDTO): List<Currency> {
|
|
||||||
val blockchainNetworks =
|
|
||||||
oldUserTokensRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
|
|
||||||
return blockchainNetworks.flatMap { it.toCurrencies() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveUserTokens(userId: String, tokens: UserTokensResponse) {
|
|
||||||
val json = userTokensAdapter.toJson(tokens)
|
val json = userTokensAdapter.toJson(tokens)
|
||||||
fileReader.rewriteFile(json, getFileNameForUserTokens(userId))
|
fileReader.rewriteFile(json, getFileNameForUserTokens(userWalletId))
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ import com.tangem.common.KeyPair
|
||||||
import com.tangem.common.extensions.hexToBytes
|
import com.tangem.common.extensions.hexToBytes
|
||||||
import com.tangem.common.extensions.toHexString
|
import com.tangem.common.extensions.toHexString
|
||||||
import com.tangem.datasource.api.common.MoshiConverter
|
import com.tangem.datasource.api.common.MoshiConverter
|
||||||
|
import com.tangem.datasource.asset.AssetReader
|
||||||
import com.tangem.domain.common.CardDTO
|
import com.tangem.domain.common.CardDTO
|
||||||
import com.tangem.domain.common.ScanResponse
|
import com.tangem.domain.common.ScanResponse
|
||||||
import com.tangem.operations.wallet.CreateWalletResponse
|
import com.tangem.operations.wallet.CreateWalletResponse
|
||||||
import com.tangem.datasource.utils.AssetReader
|
|
||||||
import com.tangem.tap.tangemSdkManager
|
import com.tangem.tap.tangemSdkManager
|
||||||
|
|
||||||
class TwinCardsManager(
|
class TwinCardsManager(
|
||||||
|
|
@ -89,7 +89,7 @@ class TwinCardsManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getIssuers(reader: AssetReader): List<Issuer> {
|
private fun getIssuers(reader: AssetReader): List<Issuer> {
|
||||||
val file = reader.readAssetAsString("tangem-app-config/issuers")
|
val file = reader.readJson(fileName = "tangem-app-config/issuers")
|
||||||
return getAdapter().fromJson(file)!!
|
return getAdapter().fromJson(file)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import android.view.View
|
||||||
import androidx.core.animation.doOnEnd
|
import androidx.core.animation.doOnEnd
|
||||||
import com.tangem.common.extensions.VoidCallback
|
import com.tangem.common.extensions.VoidCallback
|
||||||
import com.tangem.domain.common.TwinCardNumber
|
import com.tangem.domain.common.TwinCardNumber
|
||||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView
|
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapView
|
||||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState
|
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapViewState
|
||||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
import com.tangem.common.json.TangemSdkAdapter
|
import com.tangem.common.json.TangemSdkAdapter
|
||||||
import com.tangem.common.services.secure.SecureStorage
|
import com.tangem.common.services.secure.SecureStorage
|
||||||
import com.tangem.tangem_sdk_new.storage.AndroidSecureStorage
|
import com.tangem.sdk.storage.AndroidSecureStorage
|
||||||
import com.tangem.tangem_sdk_new.storage.createEncryptedSharedPreferences
|
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
||||||
import com.tangem.tap.domain.TangemSdkManager
|
import com.tangem.tap.domain.TangemSdkManager
|
||||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||||
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
|
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,10 @@ internal class DefaultWalletCurrenciesManager(
|
||||||
userWallet: UserWallet,
|
userWallet: UserWallet,
|
||||||
currenciesToAdd: List<Currency>,
|
currenciesToAdd: List<Currency>,
|
||||||
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
|
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
|
||||||
|
if (currenciesToAdd.isEmpty()) {
|
||||||
|
return@withContext CompletionResult.Success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
val card = userWallet.scanResponse.card
|
val card = userWallet.scanResponse.card
|
||||||
val currenciesToAddWithMissingBlockchains = currenciesToAdd.addMissingBlockchainsIfNeeded(card)
|
val currenciesToAddWithMissingBlockchains = currenciesToAdd.addMissingBlockchainsIfNeeded(card)
|
||||||
listeners.forEach { it.willCurrenciesAdd(userWallet, currenciesToAddWithMissingBlockchains) }
|
listeners.forEach { it.willCurrenciesAdd(userWallet, currenciesToAddWithMissingBlockchains) }
|
||||||
|
|
@ -86,6 +90,10 @@ internal class DefaultWalletCurrenciesManager(
|
||||||
userWallet: UserWallet,
|
userWallet: UserWallet,
|
||||||
currenciesToRemove: List<Currency>,
|
currenciesToRemove: List<Currency>,
|
||||||
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
|
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
|
||||||
|
if (currenciesToRemove.isEmpty()) {
|
||||||
|
return@withContext CompletionResult.Success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
listeners.forEach { it.willCurrenciesRemove(userWallet, currenciesToRemove) }
|
listeners.forEach { it.willCurrenciesRemove(userWallet, currenciesToRemove) }
|
||||||
val card = userWallet.scanResponse.card
|
val card = userWallet.scanResponse.card
|
||||||
val remainingCurrencies = getSavedCurrencies(userWallet.walletId)
|
val remainingCurrencies = getSavedCurrencies(userWallet.walletId)
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ import com.tangem.tap.features.wallet.models.Currency
|
||||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||||
import com.tangem.tap.features.wallet.models.filterByCoin
|
import com.tangem.tap.features.wallet.models.filterByCoin
|
||||||
import com.tangem.tap.features.wallet.models.getPendingTransactions
|
import com.tangem.tap.features.wallet.models.getPendingTransactions
|
||||||
import com.tangem.tap.network.NetworkConnectivity
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
|
import com.tangem.tap.store
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
|
@ -62,8 +63,6 @@ internal class DefaultWalletAmountsRepository(
|
||||||
private val tangemTechApi: TangemTechApi,
|
private val tangemTechApi: TangemTechApi,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : WalletAmountsRepository {
|
) : WalletAmountsRepository {
|
||||||
private val walletStoresStorage = WalletStoresStorage
|
|
||||||
private val walletManagersStorage = WalletManagerStorage
|
|
||||||
|
|
||||||
override suspend fun updateAmountsForUserWallets(
|
override suspend fun updateAmountsForUserWallets(
|
||||||
userWallets: List<UserWallet>,
|
userWallets: List<UserWallet>,
|
||||||
|
|
@ -76,8 +75,7 @@ internal class DefaultWalletAmountsRepository(
|
||||||
awaitAll(
|
awaitAll(
|
||||||
async { fetchAmountsForUserWallets(userWallets) },
|
async { fetchAmountsForUserWallets(userWallets) },
|
||||||
async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) },
|
async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) },
|
||||||
)
|
).fold()
|
||||||
.fold()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,8 +102,7 @@ internal class DefaultWalletAmountsRepository(
|
||||||
awaitAll(
|
awaitAll(
|
||||||
async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) },
|
async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) },
|
||||||
async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) },
|
async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) },
|
||||||
)
|
).fold()
|
||||||
.fold()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -123,41 +120,41 @@ internal class DefaultWalletAmountsRepository(
|
||||||
walletStores: List<WalletStoreModel>?,
|
walletStores: List<WalletStoreModel>?,
|
||||||
fiatCurrency: FiatCurrency,
|
fiatCurrency: FiatCurrency,
|
||||||
): CompletionResult<Unit> {
|
): CompletionResult<Unit> {
|
||||||
// FIXME: Use NetworkConnectionManager when it is added to DI
|
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
if (!networkConnectionManager.isOnline) {
|
||||||
return CompletionResult.Failure(WalletStoresError.NoInternetConnection)
|
return CompletionResult.Failure(WalletStoresError.NoInternetConnection)
|
||||||
}
|
}
|
||||||
|
|
||||||
val walletStoresInternal = walletStores ?: getWalletStores(userWallets)
|
val walletStoresInternal = walletStores ?: getWalletStores(userWallets)
|
||||||
val currencies = walletStoresInternal
|
val currencies = walletStoresInternal.asSequence().flatMap { it.walletsData }.map { it.currency }
|
||||||
.asSequence()
|
|
||||||
.flatMap { it.walletsData }
|
|
||||||
.map { it.currency }
|
|
||||||
|
|
||||||
val coinsIds = currencies.mapNotNull { it.coinId }.distinct().toList()
|
val coinsIds = currencies.mapNotNull { it.coinId }.distinct().toList()
|
||||||
|
|
||||||
return withContext(dispatchers.io) {
|
return withContext(dispatchers.io) {
|
||||||
runCatching { tangemTechApi.getRates(fiatCurrency.code.lowercase(), coinsIds.joinToString(",")) }
|
runCatching {
|
||||||
.onSuccess {
|
tangemTechApi.getRates(
|
||||||
updateWalletStoresWithFiatRates(walletStores = walletStoresInternal, fiatRates = it.rates)
|
fiatCurrency.code.lowercase(),
|
||||||
return@withContext CompletionResult.Success(Unit)
|
coinsIds.joinToString(","),
|
||||||
}
|
)
|
||||||
.onFailure {
|
}.onSuccess {
|
||||||
val error = WalletStoresError.FetchFiatRatesError(
|
updateWalletStoresWithFiatRates(walletStores = walletStoresInternal, fiatRates = it.rates)
|
||||||
currencies = currencies.map(Currency::currencySymbol).toList(),
|
return@withContext CompletionResult.Success(Unit)
|
||||||
cause = it,
|
}.onFailure {
|
||||||
)
|
val error = WalletStoresError.FetchFiatRatesError(
|
||||||
|
currencies = currencies.map(Currency::currencySymbol).toList(),
|
||||||
|
cause = it,
|
||||||
|
)
|
||||||
|
|
||||||
Timber.e(
|
Timber.e(
|
||||||
error,
|
error,
|
||||||
"""
|
"""
|
||||||
Unable to fetch fiat rates
|
Unable to fetch fiat rates
|
||||||
|- Coins ids: $coinsIds
|
|- Coins ids: $coinsIds
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return@withContext CompletionResult.Failure(error)
|
return@withContext CompletionResult.Failure(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
error("Unreachable code because runCatching must return result")
|
error("Unreachable code because runCatching must return result")
|
||||||
}
|
}
|
||||||
|
|
@ -166,9 +163,7 @@ internal class DefaultWalletAmountsRepository(
|
||||||
private suspend fun fetchAmountsForUserWallets(
|
private suspend fun fetchAmountsForUserWallets(
|
||||||
userWallets: List<UserWallet>,
|
userWallets: List<UserWallet>,
|
||||||
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
|
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
|
||||||
userWallets.map { async { fetchAmountsForUserWallet(it) } }
|
userWallets.map { async { fetchAmountsForUserWallet(it) } }.awaitAll().fold()
|
||||||
.awaitAll()
|
|
||||||
.fold()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchAmountsForUserWallet(
|
private suspend fun fetchAmountsForUserWallet(
|
||||||
|
|
@ -186,8 +181,8 @@ internal class DefaultWalletAmountsRepository(
|
||||||
scanResponse: ScanResponse,
|
scanResponse: ScanResponse,
|
||||||
walletStores: List<WalletStoreModel>,
|
walletStores: List<WalletStoreModel>,
|
||||||
): CompletionResult<Unit> = coroutineScope {
|
): CompletionResult<Unit> = coroutineScope {
|
||||||
// FIXME: Use NetworkConnectionManager when it is added to DI
|
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
if (!networkConnectionManager.isOnline) {
|
||||||
walletStores.forEach {
|
walletStores.forEach {
|
||||||
updateWalletStoreWithUnreachable(it)
|
updateWalletStoreWithUnreachable(it)
|
||||||
}
|
}
|
||||||
|
|
@ -200,9 +195,7 @@ internal class DefaultWalletAmountsRepository(
|
||||||
val walletManager = walletStore.walletManager
|
val walletManager = walletStore.walletManager
|
||||||
fetchAmountsForWalletStore(userWalletId, scanResponse, walletStore, walletManager)
|
fetchAmountsForWalletStore(userWalletId, scanResponse, walletStore, walletManager)
|
||||||
}
|
}
|
||||||
}
|
}.awaitAll().fold()
|
||||||
.awaitAll()
|
|
||||||
.fold()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchAmountsForWalletStore(
|
private suspend fun fetchAmountsForWalletStore(
|
||||||
|
|
@ -223,24 +216,25 @@ internal class DefaultWalletAmountsRepository(
|
||||||
updateWalletStoreWithUnreachable(walletStore)
|
updateWalletStoreWithUnreachable(walletStore)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
updateWalletManager(scanResponse, walletManager)
|
updateWalletManager(scanResponse, walletManager).map {
|
||||||
.map { updateWalletManagerInStorage(userWalletId, walletManager) }
|
updateWalletManagerInStorage(
|
||||||
.flatMap {
|
userWalletId,
|
||||||
updateWalletStoreWithAmounts(
|
walletManager,
|
||||||
walletStore = walletStore,
|
)
|
||||||
updatedWallet = walletManager.wallet,
|
}.flatMap {
|
||||||
// FIXME: move DemoHelper to Demo core module maybe
|
updateWalletStoreWithAmounts(
|
||||||
isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId),
|
walletStore = walletStore,
|
||||||
)
|
updatedWallet = walletManager.wallet,
|
||||||
}
|
// FIXME: move DemoHelper to Demo core module maybe
|
||||||
.flatMap { fetchWalletStoreRentIfNeeded(walletStore, walletManager) }
|
isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId),
|
||||||
.flatMapOnFailure { error ->
|
)
|
||||||
updateWalletStoreWithError(
|
}.flatMap { fetchWalletStoreRentIfNeeded(walletStore, walletManager) }.flatMapOnFailure { error ->
|
||||||
walletStore = walletStore,
|
updateWalletStoreWithError(
|
||||||
wallet = walletManager.wallet,
|
walletStore = walletStore,
|
||||||
error = error,
|
wallet = walletManager.wallet,
|
||||||
)
|
error = error,
|
||||||
}
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -267,8 +261,7 @@ internal class DefaultWalletAmountsRepository(
|
||||||
walletStore: WalletStoreModel,
|
walletStore: WalletStoreModel,
|
||||||
walletManager: WalletManager,
|
walletManager: WalletManager,
|
||||||
): CompletionResult<Unit> {
|
): CompletionResult<Unit> {
|
||||||
val rentProvider = walletManager as? RentProvider
|
val rentProvider = walletManager as? RentProvider ?: return CompletionResult.Success(Unit)
|
||||||
?: return CompletionResult.Success(Unit)
|
|
||||||
|
|
||||||
when (val result = rentProvider.minimalBalanceForRentExemption()) {
|
when (val result = rentProvider.minimalBalanceForRentExemption()) {
|
||||||
is Success -> {
|
is Success -> {
|
||||||
|
|
@ -318,7 +311,7 @@ internal class DefaultWalletAmountsRepository(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (error is BlockchainSdkError) {
|
if (error is BlockchainSdkError) {
|
||||||
walletStoresStorage.update { prevState ->
|
WalletStoresStorage.update { prevState ->
|
||||||
prevState.replaceWalletStore(
|
prevState.replaceWalletStore(
|
||||||
walletStoreToUpdate = walletStore,
|
walletStoreToUpdate = walletStore,
|
||||||
update = {
|
update = {
|
||||||
|
|
@ -350,7 +343,7 @@ internal class DefaultWalletAmountsRepository(
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
walletStoresStorage.update { prevState ->
|
WalletStoresStorage.update { prevState ->
|
||||||
prevState.replaceWalletStore(
|
prevState.replaceWalletStore(
|
||||||
walletStoreToUpdate = walletStore,
|
walletStoreToUpdate = walletStore,
|
||||||
update = {
|
update = {
|
||||||
|
|
@ -378,7 +371,7 @@ internal class DefaultWalletAmountsRepository(
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
walletStoresStorage.update { prevState ->
|
WalletStoresStorage.update { prevState ->
|
||||||
prevState.replaceWalletStore(
|
prevState.replaceWalletStore(
|
||||||
walletStoreToUpdate = walletStore,
|
walletStoreToUpdate = walletStore,
|
||||||
update = {
|
update = {
|
||||||
|
|
@ -402,7 +395,7 @@ internal class DefaultWalletAmountsRepository(
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
walletStoresStorage.update { prevState ->
|
WalletStoresStorage.update { prevState ->
|
||||||
prevState.replaceWalletStore(
|
prevState.replaceWalletStore(
|
||||||
walletStoreToUpdate = walletStore,
|
walletStoreToUpdate = walletStore,
|
||||||
update = {
|
update = {
|
||||||
|
|
@ -425,7 +418,7 @@ internal class DefaultWalletAmountsRepository(
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
walletStoresStorage.update { prevState ->
|
WalletStoresStorage.update { prevState ->
|
||||||
prevState.replaceWalletStores(
|
prevState.replaceWalletStores(
|
||||||
walletStoresToUpdate = walletStores,
|
walletStoresToUpdate = walletStores,
|
||||||
update = {
|
update = {
|
||||||
|
|
@ -450,7 +443,7 @@ internal class DefaultWalletAmountsRepository(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (rent != walletStore.walletRent) {
|
if (rent != walletStore.walletRent) {
|
||||||
walletStoresStorage.update { prevState ->
|
WalletStoresStorage.update { prevState ->
|
||||||
prevState.replaceWalletStore(
|
prevState.replaceWalletStore(
|
||||||
walletStoreToUpdate = walletStore,
|
walletStoreToUpdate = walletStore,
|
||||||
update = {
|
update = {
|
||||||
|
|
@ -465,14 +458,12 @@ internal class DefaultWalletAmountsRepository(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
walletManager: WalletManager,
|
walletManager: WalletManager,
|
||||||
) = withContext(Dispatchers.Default) {
|
) = withContext(Dispatchers.Default) {
|
||||||
walletManagersStorage.update { prevManagers ->
|
WalletManagerStorage.update { prevManagers ->
|
||||||
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty()
|
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty().toMutableList().apply {
|
||||||
.toMutableList()
|
replaceByOrAdd(walletManager) {
|
||||||
.apply {
|
it.wallet.blockchain == walletManager.wallet.blockchain
|
||||||
replaceByOrAdd(walletManager) {
|
|
||||||
it.wallet.blockchain == walletManager.wallet.blockchain
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prevManagers.apply {
|
prevManagers.apply {
|
||||||
set(userWalletId, newManagersForUserWallet)
|
set(userWalletId, newManagersForUserWallet)
|
||||||
|
|
@ -481,13 +472,8 @@ internal class DefaultWalletAmountsRepository(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getWalletStores(userWallets: List<UserWallet>): List<WalletStoreModel> {
|
private suspend fun getWalletStores(userWallets: List<UserWallet>): List<WalletStoreModel> {
|
||||||
return userWallets
|
return userWallets.map { it.walletId }.flatMap { userWalletId ->
|
||||||
.map { it.walletId }
|
WalletStoresStorage.getAll().firstOrNull()?.get(userWalletId).orEmpty()
|
||||||
.flatMap { userWalletId ->
|
}
|
||||||
walletStoresStorage.getAll()
|
|
||||||
.firstOrNull()
|
|
||||||
?.get(userWalletId)
|
|
||||||
.orEmpty()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.tangem.tap.features.demo
|
||||||
|
|
||||||
|
import com.tangem.datasource.demo.DemoModeDatasource
|
||||||
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DefaultDemoModeDatasource @Inject constructor(private val appStateHolder: AppStateHolder) : DemoModeDatasource {
|
||||||
|
|
||||||
|
override val isDemoModeActive: Boolean
|
||||||
|
get() = appStateHolder.scanResponse?.let { DemoHelper.isDemoCard(it) } == true
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.tangem.tap.features.demo
|
package com.tangem.tap.features.demo
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Amount
|
import com.tangem.blockchain.common.Amount
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.blockchain.common.TransactionData
|
import com.tangem.blockchain.common.TransactionData
|
||||||
import com.tangem.blockchain.common.TransactionSender
|
import com.tangem.blockchain.common.TransactionSender
|
||||||
import com.tangem.blockchain.common.TransactionSigner
|
import com.tangem.blockchain.common.TransactionSigner
|
||||||
|
|
@ -11,6 +10,7 @@ import com.tangem.blockchain.extensions.Result
|
||||||
import com.tangem.blockchain.extensions.SimpleResult
|
import com.tangem.blockchain.extensions.SimpleResult
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
import com.tangem.domain.common.ScanResponse
|
import com.tangem.domain.common.ScanResponse
|
||||||
|
import com.tangem.domain.common.demo.DemoConfig
|
||||||
import com.tangem.tap.common.extensions.dispatchNotification
|
import com.tangem.tap.common.extensions.dispatchNotification
|
||||||
import com.tangem.tap.common.redux.AppState
|
import com.tangem.tap.common.redux.AppState
|
||||||
import com.tangem.tap.features.details.redux.DetailsAction
|
import com.tangem.tap.features.details.redux.DetailsAction
|
||||||
|
|
@ -18,10 +18,8 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.wallet.BuildConfig
|
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
import org.rekotlin.Action
|
import org.rekotlin.Action
|
||||||
import java.math.BigDecimal
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
|
|
@ -84,412 +82,6 @@ object DemoHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
|
||||||
class DemoConfig {
|
|
||||||
|
|
||||||
val demoBlockchains = listOf(
|
|
||||||
Blockchain.Bitcoin,
|
|
||||||
Blockchain.Ethereum,
|
|
||||||
Blockchain.Dogecoin,
|
|
||||||
Blockchain.Solana,
|
|
||||||
)
|
|
||||||
|
|
||||||
val demoCardIds: List<String> by lazy {
|
|
||||||
val demoIds = getReleaseIds().toMutableList()
|
|
||||||
if (BuildConfig.DEBUG) demoIds.addAll(debugTestDemoCardIds)
|
|
||||||
|
|
||||||
return@lazy demoIds.distinct()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val walletBalances: Map<Blockchain, Amount> = mapOf(
|
|
||||||
Blockchain.Bitcoin to Amount(0.028.toBigDecimal(), Blockchain.Bitcoin),
|
|
||||||
Blockchain.Ethereum to Amount(0.2311.toBigDecimal(), Blockchain.Ethereum),
|
|
||||||
Blockchain.Dogecoin to Amount(1450.025.toBigDecimal(), Blockchain.Dogecoin),
|
|
||||||
Blockchain.Solana to Amount(13.246.toBigDecimal(), Blockchain.Solana),
|
|
||||||
)
|
|
||||||
|
|
||||||
fun isDemoCardId(cardId: String): Boolean = demoCardIds.contains(cardId)
|
|
||||||
|
|
||||||
fun isTestDemoCardId(cardId: String): Boolean = testDemoCardIds.contains(cardId)
|
|
||||||
|
|
||||||
fun getBalance(blockchain: Blockchain): Amount = walletBalances[blockchain]?.copy()
|
|
||||||
?: Amount(BigDecimal.ZERO, blockchain).copy()
|
|
||||||
|
|
||||||
private fun getReleaseIds(): List<String> {
|
|
||||||
return (releaseDemoCardIds + testDemoCardIds).distinct()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("ClassOrdering")
|
|
||||||
private val releaseDemoCardIds = mutableListOf(
|
|
||||||
// === Not from the Google Sheet table ===
|
|
||||||
"AC01000000041225",
|
|
||||||
"AC01000000041472",
|
|
||||||
"AB01000000046498",
|
|
||||||
"AB01000000049608",
|
|
||||||
"AB01000000049574",
|
|
||||||
"AB01000000046704",
|
|
||||||
"AB02000000051000",
|
|
||||||
"AB02000000050911",
|
|
||||||
|
|
||||||
// === Mvideo ===
|
|
||||||
// Wallet
|
|
||||||
"AC01000000045754",
|
|
||||||
"AC01000000041662",
|
|
||||||
"AC01000000041647",
|
|
||||||
"AC01000000041209",
|
|
||||||
"AC01000000042462",
|
|
||||||
"AC01000000041100",
|
|
||||||
"AC01000000041621",
|
|
||||||
"AC01000000045960",
|
|
||||||
"AC01000000041092",
|
|
||||||
"AC01000000041217",
|
|
||||||
"AC01000000013489",
|
|
||||||
"AC01000000028610",
|
|
||||||
"AC01000000028701",
|
|
||||||
"AC01000000028578",
|
|
||||||
"AC01000000027281",
|
|
||||||
"AC01000000027216",
|
|
||||||
"AC01000000028594",
|
|
||||||
"AC01000000028602",
|
|
||||||
"AC01000000028636",
|
|
||||||
"AC01000000013968",
|
|
||||||
"AC01000000027208",
|
|
||||||
"AC01000000013471",
|
|
||||||
"AC01000000028586",
|
|
||||||
"AC01000000013703",
|
|
||||||
"AC01000000028628",
|
|
||||||
"AC01000000028693",
|
|
||||||
"AC01000000028685",
|
|
||||||
"AC01000000013950",
|
|
||||||
"AC01000000013828",
|
|
||||||
"AC01000000013497",
|
|
||||||
"AC01000000013836",
|
|
||||||
"AC01000000013505",
|
|
||||||
"AC03000000046693",
|
|
||||||
"AC03000000046685",
|
|
||||||
"AC03000000046677",
|
|
||||||
"AC03000000046669",
|
|
||||||
"AC03000000046651",
|
|
||||||
"AC03000000046644",
|
|
||||||
"AC03000000046636",
|
|
||||||
"AC03000000046628",
|
|
||||||
"AC03000000046610",
|
|
||||||
"AC03000000046602",
|
|
||||||
"AC03000000046594",
|
|
||||||
"AC03000000046586",
|
|
||||||
"AC03000000046578",
|
|
||||||
"AC03000000046560",
|
|
||||||
"AC03000000046552",
|
|
||||||
"AC03000000046545",
|
|
||||||
"AC03000000046537",
|
|
||||||
"AC03000000046529",
|
|
||||||
"AC03000000046511",
|
|
||||||
"AC03000000046800",
|
|
||||||
"AC03000000046792",
|
|
||||||
"AC03000000046784",
|
|
||||||
"AC03000000046776",
|
|
||||||
"AC03000000046768",
|
|
||||||
"AC03000000046750",
|
|
||||||
"AC03000000046743",
|
|
||||||
"AC03000000046735",
|
|
||||||
"AC03000000046727",
|
|
||||||
"AC03000000046446",
|
|
||||||
"AC03000000046438",
|
|
||||||
"AC03000000046412",
|
|
||||||
"AC03000000046388",
|
|
||||||
"AC03000000046370",
|
|
||||||
"AC03000000046354",
|
|
||||||
"AC03000000046347",
|
|
||||||
"AC03000000046339",
|
|
||||||
"AC03000000046321",
|
|
||||||
"AC03000000046172",
|
|
||||||
"AC03000000046396",
|
|
||||||
"AC03000000046404",
|
|
||||||
"AC03000000046701",
|
|
||||||
"AC03000000046420",
|
|
||||||
"AC03000000046719",
|
|
||||||
"AC03000000046503",
|
|
||||||
"AC03000000046495",
|
|
||||||
"AC03000000046487",
|
|
||||||
"AC03000000046362",
|
|
||||||
"AC03000000046479",
|
|
||||||
"AC03000000046461",
|
|
||||||
"AC03000000046453",
|
|
||||||
|
|
||||||
// Note BTC
|
|
||||||
"AB01000000059608",
|
|
||||||
"AB01000000046647",
|
|
||||||
"AB01000000046571",
|
|
||||||
"AB01000000046746",
|
|
||||||
"AB01000000059574",
|
|
||||||
"AB01000000046753",
|
|
||||||
"AB01000000046605",
|
|
||||||
"AB01000000046761",
|
|
||||||
"AB01000000046720",
|
|
||||||
"AB01000000046530",
|
|
||||||
"AB01000000016475",
|
|
||||||
"AB01000000016483",
|
|
||||||
"AB01000000016491",
|
|
||||||
"AB01000000020709",
|
|
||||||
"AB01000000020717",
|
|
||||||
"AB01000000015550",
|
|
||||||
"AB01000000015394",
|
|
||||||
"AB01000000016079",
|
|
||||||
"AB01000000016087",
|
|
||||||
"AB01000000016095",
|
|
||||||
"AB01000000020915",
|
|
||||||
"AB01000000017184",
|
|
||||||
"AB01000000020907",
|
|
||||||
"AB01000000017192",
|
|
||||||
"AB01000000016210",
|
|
||||||
"AB01000000016111",
|
|
||||||
"AB01000000016103",
|
|
||||||
"AB01000000015766",
|
|
||||||
"AB01000000015774",
|
|
||||||
"AB01000000015782",
|
|
||||||
"AB01000000022598",
|
|
||||||
"AB01000000022580",
|
|
||||||
"AB01000000005688",
|
|
||||||
"AB07000000005696",
|
|
||||||
"AB07000000005902",
|
|
||||||
"AB07000000005910",
|
|
||||||
"AB07000000005928",
|
|
||||||
"AB07000000005936",
|
|
||||||
"AB07000000005944",
|
|
||||||
"AB07000000005993",
|
|
||||||
"AB07000000005985",
|
|
||||||
"AB07000000005977",
|
|
||||||
"AB07000000005969",
|
|
||||||
"AB07000000005951",
|
|
||||||
"AB07000000005605",
|
|
||||||
"AB07000000005803",
|
|
||||||
"AB07000000005811",
|
|
||||||
"AB07000000005829",
|
|
||||||
"AB07000000005837",
|
|
||||||
"AB07000000005845",
|
|
||||||
"AB07000000005852",
|
|
||||||
"AB07000000005860",
|
|
||||||
"AB07000000005878",
|
|
||||||
"AB07000000005886",
|
|
||||||
"AB07000000005894",
|
|
||||||
"AB07000000005704",
|
|
||||||
"AB07000000005712",
|
|
||||||
"AB07000000005720",
|
|
||||||
"AB07000000005738",
|
|
||||||
"AB07000000005746",
|
|
||||||
"AB07000000005514",
|
|
||||||
"AB07000000005522",
|
|
||||||
"AB07000000005563",
|
|
||||||
"AB07000000005571",
|
|
||||||
"AB07000000005589",
|
|
||||||
"AB07000000005597",
|
|
||||||
"AB07000000005613",
|
|
||||||
"AB07000000005621",
|
|
||||||
"AB07000000005639",
|
|
||||||
"AB07000000005647",
|
|
||||||
"AB07000000005654",
|
|
||||||
"AB07000000005662",
|
|
||||||
"AB07000000005670",
|
|
||||||
"AB07000000005530",
|
|
||||||
"AB07000000005548",
|
|
||||||
"AB07000000005555",
|
|
||||||
"AB07000000005753",
|
|
||||||
"AB07000000005761",
|
|
||||||
"AB07000000005779",
|
|
||||||
"AB07000000005787",
|
|
||||||
"AB07000000005795",
|
|
||||||
"AB07000000005506",
|
|
||||||
|
|
||||||
// Note ETH
|
|
||||||
"AB02000000051083",
|
|
||||||
"AB02000000051059",
|
|
||||||
"AB02000000051158",
|
|
||||||
"AB02000000050986",
|
|
||||||
"AB02000000051026",
|
|
||||||
"AB02000000050960",
|
|
||||||
"AB02000000051042",
|
|
||||||
"AB02000000051091",
|
|
||||||
"AB02000000051034",
|
|
||||||
"AB02000000051133",
|
|
||||||
"AB02000000019924",
|
|
||||||
"AB02000000019932",
|
|
||||||
"AB02000000022092",
|
|
||||||
"AB02000000022282",
|
|
||||||
"AB02000000023983",
|
|
||||||
"AB02000000023439",
|
|
||||||
"AB02000000020328",
|
|
||||||
"AB02000000020310",
|
|
||||||
"AB02000000021565",
|
|
||||||
"AB02000000022357",
|
|
||||||
"AB02000000023355",
|
|
||||||
"AB02000000022324",
|
|
||||||
"AB02000000022100",
|
|
||||||
"AB02000000019999",
|
|
||||||
"AB02000000020013",
|
|
||||||
"AB02000000020005",
|
|
||||||
"AB02000000020021",
|
|
||||||
"AB02000000020039",
|
|
||||||
"AB02000000020278",
|
|
||||||
"AB02000000020252",
|
|
||||||
"AB02000000018652",
|
|
||||||
"AB02000000018561",
|
|
||||||
"AB08000000009481",
|
|
||||||
"AB08000000009473",
|
|
||||||
"AB08000000009705",
|
|
||||||
"AB08000000009897",
|
|
||||||
"AB08000000009689",
|
|
||||||
"AB08000000009671",
|
|
||||||
"AB08000000009465",
|
|
||||||
"AB08000000009457",
|
|
||||||
"AB08000000009440",
|
|
||||||
"AB08000000009432",
|
|
||||||
"AB08000000009424",
|
|
||||||
"AB08000000009416",
|
|
||||||
"AB08000000009408",
|
|
||||||
"AB08000000009390",
|
|
||||||
"AB08000000009374",
|
|
||||||
"AB08000000009382",
|
|
||||||
"AB08000000009267",
|
|
||||||
"AB08000000009275",
|
|
||||||
"AB08000000009283",
|
|
||||||
"AB08000000009291",
|
|
||||||
"AB08000000009309",
|
|
||||||
"AB08000000009317",
|
|
||||||
"AB08000000009325",
|
|
||||||
"AB08000000009333",
|
|
||||||
"AB08000000009341",
|
|
||||||
"AB08000000009358",
|
|
||||||
"AB08000000009366",
|
|
||||||
"AB08000000009077",
|
|
||||||
"AB08000000009143",
|
|
||||||
"AB08000000009168",
|
|
||||||
"AB08000000009184",
|
|
||||||
"AB08000000009192",
|
|
||||||
"AB08000000009200",
|
|
||||||
"AB08000000009226",
|
|
||||||
"AB08000000009218",
|
|
||||||
"AB08000000009234",
|
|
||||||
"AB08000000009242",
|
|
||||||
"AB08000000008574",
|
|
||||||
"AB08000000009069",
|
|
||||||
"AB08000000008525",
|
|
||||||
"AB08000000009051",
|
|
||||||
"AB08000000009135",
|
|
||||||
"AB08000000009150",
|
|
||||||
"AB08000000009176",
|
|
||||||
"AB08000000009085",
|
|
||||||
"AB08000000009093",
|
|
||||||
"AB08000000009101",
|
|
||||||
"AB08000000009119",
|
|
||||||
"AB08000000009127",
|
|
||||||
"AB08000000009259",
|
|
||||||
|
|
||||||
// === Technopark ===
|
|
||||||
// Wallet
|
|
||||||
"AC01000000044120",
|
|
||||||
"AC01000000044997",
|
|
||||||
"AC01000000044989",
|
|
||||||
"AC01000000043494",
|
|
||||||
"AC01000000043486",
|
|
||||||
"AC01000000044187",
|
|
||||||
"AC01000000043148",
|
|
||||||
"AC01000000044013",
|
|
||||||
"AC01000000043973",
|
|
||||||
"AC01000000044815",
|
|
||||||
"AC01000000044807",
|
|
||||||
"AC01000000043809",
|
|
||||||
"AC01000000043833",
|
|
||||||
"AC01000000043460",
|
|
||||||
"AC01000000043064",
|
|
||||||
"AC01000000044138",
|
|
||||||
"AC01000000044500",
|
|
||||||
"AC01000000044492",
|
|
||||||
"AC01000000044260",
|
|
||||||
"AC01000000044278",
|
|
||||||
|
|
||||||
// Note BTC
|
|
||||||
"AB01000000049864",
|
|
||||||
"AB01000000053239",
|
|
||||||
"AB01000000053056",
|
|
||||||
"AB01000000054237",
|
|
||||||
"AB01000000054245",
|
|
||||||
"AB01000000054211",
|
|
||||||
"AB01000000054229",
|
|
||||||
"AB01000000053189",
|
|
||||||
"AB01000000054195",
|
|
||||||
"AB01000000050797",
|
|
||||||
"AB01000000053833",
|
|
||||||
"AB01000000052124",
|
|
||||||
"AB01000000051605",
|
|
||||||
"AB01000000052223",
|
|
||||||
"AB01000000052207",
|
|
||||||
"AB01000000052199",
|
|
||||||
"AB01000000047785",
|
|
||||||
"AB01000000047850",
|
|
||||||
"AB01000000047868",
|
|
||||||
"AB01000000048288",
|
|
||||||
|
|
||||||
// Note ETH
|
|
||||||
"AB02000000049715",
|
|
||||||
"AB02000000049848",
|
|
||||||
"AB02000000049814",
|
|
||||||
"AB02000000049863",
|
|
||||||
"AB02000000049871",
|
|
||||||
"AB02000000049855",
|
|
||||||
"AB02000000049285",
|
|
||||||
"AB02000000049277",
|
|
||||||
"AB02000000049558",
|
|
||||||
"AB02000000049889",
|
|
||||||
"AB02000000049988",
|
|
||||||
"AB02000000049707",
|
|
||||||
"AB02000000049699",
|
|
||||||
"AB02000000049897",
|
|
||||||
"AB02000000049905",
|
|
||||||
"AB02000000049913",
|
|
||||||
"AB02000000049251",
|
|
||||||
"AB02000000049533",
|
|
||||||
"AB02000000049541",
|
|
||||||
"AB02000000049830",
|
|
||||||
// === more cids ===
|
|
||||||
"AC03000000091418",
|
|
||||||
"AC03000000091400",
|
|
||||||
"AC03000000099007",
|
|
||||||
"AC03000000098991",
|
|
||||||
"AC03000000098942",
|
|
||||||
"AC03000000091715",
|
|
||||||
"AC03000000091301",
|
|
||||||
"AC03000000091343",
|
|
||||||
"AB01000000055705",
|
|
||||||
"AB01000000052918",
|
|
||||||
"AB01000000047710",
|
|
||||||
"AB01000000052306",
|
|
||||||
"AB01000000047645",
|
|
||||||
"AB01000000048957",
|
|
||||||
"AB01000000052900",
|
|
||||||
"AB01000000050391",
|
|
||||||
"AB01000000047363",
|
|
||||||
"AB02000000053998",
|
|
||||||
"AB02000000019809",
|
|
||||||
"AB02000000020872",
|
|
||||||
"AB02000000022027",
|
|
||||||
"AB02000000058955",
|
|
||||||
"AB02000000053253",
|
|
||||||
"AB02000000048063",
|
|
||||||
"AB02000000023736",
|
|
||||||
"AB02000000058187",
|
|
||||||
)
|
|
||||||
|
|
||||||
@Suppress("ClassOrdering")
|
|
||||||
private val testDemoCardIds = listOf(
|
|
||||||
"FB20000000000186", // Note ETH
|
|
||||||
"FB10000000000196", // Note BTC
|
|
||||||
"FB30000000000176", // Wallet
|
|
||||||
)
|
|
||||||
|
|
||||||
@Suppress("ClassOrdering")
|
|
||||||
private val debugTestDemoCardIds = listOf<String>()
|
|
||||||
}
|
|
||||||
|
|
||||||
class DemoTransactionSender(private val walletManager: WalletManager) : TransactionSender {
|
class DemoTransactionSender(private val walletManager: WalletManager) : TransactionSender {
|
||||||
|
|
||||||
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
|
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.demo
|
||||||
|
|
||||||
import com.tangem.common.extensions.guard
|
import com.tangem.common.extensions.guard
|
||||||
import com.tangem.domain.common.ScanResponse
|
import com.tangem.domain.common.ScanResponse
|
||||||
|
import com.tangem.domain.common.demo.DemoConfig
|
||||||
import com.tangem.domain.common.extensions.withMainContext
|
import com.tangem.domain.common.extensions.withMainContext
|
||||||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.tangem.tap.features.demo.di
|
||||||
|
|
||||||
|
import com.tangem.datasource.demo.DemoModeDatasource
|
||||||
|
import com.tangem.tap.features.demo.DefaultDemoModeDatasource
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal interface DemoModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindDemoModeHelper(demoModeHelper: DefaultDemoModeDatasource): DemoModeDatasource
|
||||||
|
}
|
||||||
|
|
@ -68,7 +68,7 @@ class DetailsMiddleware {
|
||||||
private fun handleAction(state: DetailsState, action: Action) {
|
private fun handleAction(state: DetailsState, action: Action) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action)
|
is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action)
|
||||||
is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action)
|
is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action, state)
|
||||||
is DetailsAction.AppSettings -> managePrivacyMiddleware.handle(state, action)
|
is DetailsAction.AppSettings -> managePrivacyMiddleware.handle(state, action)
|
||||||
is DetailsAction.ReCreateTwinsWallet -> {
|
is DetailsAction.ReCreateTwinsWallet -> {
|
||||||
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
|
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
|
||||||
|
|
@ -157,15 +157,15 @@ class DetailsMiddleware {
|
||||||
|
|
||||||
class ManageSecurityMiddleware {
|
class ManageSecurityMiddleware {
|
||||||
@Suppress("ComplexMethod")
|
@Suppress("ComplexMethod")
|
||||||
fun handle(action: DetailsAction.ManageSecurity) {
|
fun handle(action: DetailsAction.ManageSecurity, detailsState: DetailsState) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is DetailsAction.ManageSecurity.OpenSecurity -> {
|
is DetailsAction.ManageSecurity.OpenSecurity -> {
|
||||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity))
|
store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity))
|
||||||
}
|
}
|
||||||
is DetailsAction.ManageSecurity.SaveChanges -> {
|
is DetailsAction.ManageSecurity.SaveChanges -> {
|
||||||
val cardId = store.state.detailsState.scanResponse?.card?.cardId
|
val cardSettingsState = detailsState.cardSettingsState
|
||||||
val selectedOption =
|
val cardId = cardSettingsState?.card?.cardId
|
||||||
store.state.detailsState.cardSettingsState?.manageSecurityState?.selectedOption
|
val selectedOption = cardSettingsState?.manageSecurityState?.selectedOption
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val result = when (selectedOption) {
|
val result = when (selectedOption) {
|
||||||
SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId)
|
SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId)
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,8 @@ import com.tangem.domain.common.CardDTO
|
||||||
import com.tangem.domain.common.ScanResponse
|
import com.tangem.domain.common.ScanResponse
|
||||||
import com.tangem.tap.common.entities.Button
|
import com.tangem.tap.common.entities.Button
|
||||||
import com.tangem.tap.common.entities.FiatCurrency
|
import com.tangem.tap.common.entities.FiatCurrency
|
||||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
|
||||||
import com.tangem.tap.store
|
|
||||||
import org.rekotlin.StateType
|
import org.rekotlin.StateType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
|
|
||||||
data class DetailsState(
|
data class DetailsState(
|
||||||
val scanResponse: ScanResponse? = null,
|
val scanResponse: ScanResponse? = null,
|
||||||
|
|
@ -19,14 +16,7 @@ data class DetailsState(
|
||||||
val createBackupAllowed: Boolean = false,
|
val createBackupAllowed: Boolean = false,
|
||||||
val appCurrency: FiatCurrency = FiatCurrency.Default,
|
val appCurrency: FiatCurrency = FiatCurrency.Default,
|
||||||
val appSettingsState: AppSettingsState = AppSettingsState(),
|
val appSettingsState: AppSettingsState = AppSettingsState(),
|
||||||
) : StateType {
|
) : StateType
|
||||||
|
|
||||||
// if you do not delegate - the application crashes on startup,
|
|
||||||
// because twinCardsState has not been created yet
|
|
||||||
val twinCardsState: TwinCardsState by ReadOnlyProperty<Any, TwinCardsState> { _, _ ->
|
|
||||||
store.state.twinCardsState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class CardInfo(
|
data class CardInfo(
|
||||||
val cardId: String,
|
val cardId: String,
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.transition.TransitionInflater
|
import androidx.transition.TransitionInflater
|
||||||
|
|
@ -21,9 +19,6 @@ class DetailsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
||||||
|
|
||||||
private val detailsViewModel = DetailsViewModel(store)
|
private val detailsViewModel = DetailsViewModel(store)
|
||||||
|
|
||||||
private var detailsScreenState: MutableState<DetailsScreenState> =
|
|
||||||
mutableStateOf(detailsViewModel.updateState(store.state.detailsState))
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Analytics.send(Settings.ScreenOpened())
|
Analytics.send(Settings.ScreenOpened())
|
||||||
|
|
@ -42,7 +37,7 @@ class DetailsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
||||||
isTransitionGroup = true
|
isTransitionGroup = true
|
||||||
TangemTheme {
|
TangemTheme {
|
||||||
DetailsScreen(
|
DetailsScreen(
|
||||||
state = detailsScreenState.value,
|
state = detailsViewModel.detailsScreenState.value,
|
||||||
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
|
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -66,6 +61,6 @@ class DetailsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
||||||
|
|
||||||
override fun newState(state: DetailsState) {
|
override fun newState(state: DetailsState) {
|
||||||
if (activity == null || view == null) return
|
if (activity == null || view == null) return
|
||||||
detailsScreenState.value = detailsViewModel.updateState(state)
|
detailsViewModel.detailsScreenState.value = detailsViewModel.updateState(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,8 @@ package com.tangem.tap.features.details.ui.details
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
|
@ -16,8 +18,13 @@ import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.SnackbarHost
|
||||||
|
import androidx.compose.material.SnackbarHostState
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.colorResource
|
import androidx.compose.ui.res.colorResource
|
||||||
|
|
@ -27,10 +34,12 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.core.ui.components.SystemBarsEffect
|
import com.tangem.core.ui.components.SystemBarsEffect
|
||||||
import com.tangem.core.ui.res.TangemColorPalette
|
import com.tangem.core.ui.res.TangemColorPalette
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.tap.common.compose.TangemTypography
|
import com.tangem.tap.common.compose.TangemTypography
|
||||||
import com.tangem.tap.features.details.ui.common.ScreenTitle
|
import com.tangem.tap.features.details.ui.common.ScreenTitle
|
||||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DetailsScreen(state: DetailsScreenState, onBackClick: () -> Unit) {
|
fun DetailsScreen(state: DetailsScreenState, onBackClick: () -> Unit) {
|
||||||
|
|
@ -46,32 +55,35 @@ fun DetailsScreen(state: DetailsScreenState, onBackClick: () -> Unit) {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Content(state: DetailsScreenState) {
|
fun Content(state: DetailsScreenState) {
|
||||||
Column(
|
Box {
|
||||||
modifier = Modifier
|
Column(
|
||||||
.fillMaxSize()
|
modifier = Modifier
|
||||||
.verticalScroll(rememberScrollState()),
|
.fillMaxSize()
|
||||||
) {
|
.verticalScroll(rememberScrollState()),
|
||||||
ScreenTitle(titleRes = R.string.details_title, Modifier.padding(bottom = 52.dp))
|
) {
|
||||||
state.elements.map { element ->
|
ScreenTitle(titleRes = R.string.details_title, Modifier.padding(bottom = 52.dp))
|
||||||
if (element == SettingsElement.WalletConnect) {
|
state.elements.map { element ->
|
||||||
WalletConnectDetailsItem(onItemsClick = state.onItemsClick)
|
if (element == SettingsElement.WalletConnect) {
|
||||||
} else {
|
WalletConnectDetailsItem(onItemsClick = state.onItemsClick)
|
||||||
DetailsItem(
|
} else {
|
||||||
item = element,
|
DetailsItem(
|
||||||
appCurrency = state.appCurrency,
|
item = element,
|
||||||
onItemsClick = { state.onItemsClick(element) },
|
appCurrency = state.appCurrency,
|
||||||
)
|
onItemsClick = { state.onItemsClick(element) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
TangemSocialAccounts(state.tangemLinks, state.onSocialNetworkClick)
|
||||||
|
Spacer(modifier = Modifier.size(12.dp))
|
||||||
|
Text(
|
||||||
|
text = "${stringResource(id = state.appNameRes)} ${state.tangemVersion}",
|
||||||
|
style = TangemTypography.caption,
|
||||||
|
color = colorResource(id = R.color.text_tertiary),
|
||||||
|
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 40.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
ShowSnackbarIfNeeded(state.showErrorSnackbar.value)
|
||||||
TangemSocialAccounts(state.tangemLinks, state.onSocialNetworkClick)
|
|
||||||
Spacer(modifier = Modifier.size(12.dp))
|
|
||||||
Text(
|
|
||||||
text = "${stringResource(id = state.appNameRes)} ${state.tangemVersion}",
|
|
||||||
style = TangemTypography.caption,
|
|
||||||
color = colorResource(id = R.color.text_tertiary),
|
|
||||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 40.dp),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +177,36 @@ fun TangemSocialAccounts(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BoxScope.ShowSnackbarIfNeeded(snackbarErrorState: EventError) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
SnackbarHost(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.padding(vertical = TangemTheme.dimens.spacing16)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
hostState = snackbarHostState,
|
||||||
|
)
|
||||||
|
val errorTitle = when (snackbarErrorState) {
|
||||||
|
is EventError.DemoReferralNotAvailable -> stringResource(id = R.string.alert_demo_feature_disabled)
|
||||||
|
EventError.Empty -> ""
|
||||||
|
}
|
||||||
|
if (snackbarErrorState != EventError.Empty) {
|
||||||
|
SideEffect {
|
||||||
|
coroutineScope.launch {
|
||||||
|
snackbarHostState.showSnackbar(errorTitle)
|
||||||
|
}
|
||||||
|
when (snackbarErrorState) {
|
||||||
|
is EventError.DemoReferralNotAvailable -> snackbarErrorState.onErrorShow.invoke()
|
||||||
|
else -> {
|
||||||
|
/*no-op*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
private fun Preview() {
|
private fun Preview() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.tangem.tap.features.details.ui.details
|
package com.tangem.tap.features.details.ui.details
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
@ -11,6 +13,7 @@ data class DetailsScreenState(
|
||||||
val appCurrency: String,
|
val appCurrency: String,
|
||||||
val onItemsClick: (SettingsElement) -> Unit,
|
val onItemsClick: (SettingsElement) -> Unit,
|
||||||
val onSocialNetworkClick: (SocialNetworkLink) -> Unit,
|
val onSocialNetworkClick: (SocialNetworkLink) -> Unit,
|
||||||
|
val showErrorSnackbar: MutableState<EventError> = mutableStateOf(EventError.Empty),
|
||||||
) {
|
) {
|
||||||
val appNameRes: Int = R.string.tangem_app_name
|
val appNameRes: Int = R.string.tangem_app_name
|
||||||
}
|
}
|
||||||
|
|
@ -29,7 +32,8 @@ enum class SettingsElement(
|
||||||
AppSettings(R.drawable.ic_settings, R.string.app_settings_title),
|
AppSettings(R.drawable.ic_settings, R.string.app_settings_title),
|
||||||
LinkMoreCards(R.drawable.ic_more_cards, R.string.details_row_title_create_backup),
|
LinkMoreCards(R.drawable.ic_more_cards, R.string.details_row_title_create_backup),
|
||||||
TermsOfService(R.drawable.ic_text, R.string.disclaimer_title), // General Terms of Service of the App
|
TermsOfService(R.drawable.ic_text, R.string.disclaimer_title), // General Terms of Service of the App
|
||||||
PrivacyPolicy(R.drawable.ic_lock_24, R.string.details_row_privacy_policy);
|
PrivacyPolicy(R.drawable.ic_lock_24, R.string.details_row_privacy_policy),
|
||||||
|
TesterMenu(R.drawable.ic_alert_24, R.string.tester_menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
@ -38,6 +42,11 @@ data class SocialNetworkLink(
|
||||||
val url: String,
|
val url: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sealed class EventError {
|
||||||
|
object Empty : EventError()
|
||||||
|
data class DemoReferralNotAvailable(val onErrorShow: () -> Unit) : EventError()
|
||||||
|
}
|
||||||
|
|
||||||
sealed class SocialNetwork(val id: String, val iconRes: Int) {
|
sealed class SocialNetwork(val id: String, val iconRes: Int) {
|
||||||
object Telegram : SocialNetwork("Telegram", R.drawable.ic_telegram)
|
object Telegram : SocialNetwork("Telegram", R.drawable.ic_telegram)
|
||||||
object Twitter : SocialNetwork("Twitter", R.drawable.ic_twitter)
|
object Twitter : SocialNetwork("Twitter", R.drawable.ic_twitter)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.tangem.tap.features.details.ui.details
|
package com.tangem.tap.features.details.ui.details
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.tap.common.analytics.events.Settings
|
import com.tangem.tap.common.analytics.events.Settings
|
||||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
import com.tangem.tap.common.feedback.FeedbackEmail
|
||||||
|
|
@ -18,6 +20,9 @@ import org.rekotlin.Store
|
||||||
|
|
||||||
class DetailsViewModel(private val store: Store<AppState>) {
|
class DetailsViewModel(private val store: Store<AppState>) {
|
||||||
|
|
||||||
|
var detailsScreenState: MutableState<DetailsScreenState> = mutableStateOf(updateState(store.state.detailsState))
|
||||||
|
private set
|
||||||
|
|
||||||
@Suppress("ComplexMethod")
|
@Suppress("ComplexMethod")
|
||||||
fun updateState(state: DetailsState): DetailsScreenState {
|
fun updateState(state: DetailsState): DetailsScreenState {
|
||||||
val cardTypesResolver = state.scanResponse?.cardTypesResolver
|
val cardTypesResolver = state.scanResponse?.cardTypesResolver
|
||||||
|
|
@ -39,6 +44,7 @@ class DetailsViewModel(private val store: Store<AppState>) {
|
||||||
SettingsElement.AppSettings -> if (state.appSettingsState.isBiometricsAvailable) it else null
|
SettingsElement.AppSettings -> if (state.appSettingsState.isBiometricsAvailable) it else null
|
||||||
SettingsElement.AppCurrency -> if (cardTypesResolver?.isMultiwalletAllowed() != true) it else null
|
SettingsElement.AppCurrency -> if (cardTypesResolver?.isMultiwalletAllowed() != true) it else null
|
||||||
SettingsElement.ReferralProgram -> if (cardTypesResolver?.isTangemWallet() == true) it else null
|
SettingsElement.ReferralProgram -> if (cardTypesResolver?.isTangemWallet() == true) it else null
|
||||||
|
SettingsElement.TesterMenu -> if (BuildConfig.TESTER_MENU_ENABLED) it else null
|
||||||
else -> it
|
else -> it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,6 +102,9 @@ class DetailsViewModel(private val store: Store<AppState>) {
|
||||||
SettingsElement.ReferralProgram -> {
|
SettingsElement.ReferralProgram -> {
|
||||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram))
|
store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram))
|
||||||
}
|
}
|
||||||
|
SettingsElement.TesterMenu -> {
|
||||||
|
store.state.daggerGraphState.testerRouter?.startTesterScreen()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.features.onboarding.products.twins.redux
|
||||||
import com.tangem.Message
|
import com.tangem.Message
|
||||||
import com.tangem.blockchain.common.WalletManager
|
import com.tangem.blockchain.common.WalletManager
|
||||||
import com.tangem.common.extensions.VoidCallback
|
import com.tangem.common.extensions.VoidCallback
|
||||||
import com.tangem.datasource.utils.AssetReader
|
import com.tangem.datasource.asset.AssetReader
|
||||||
import com.tangem.domain.common.ScanResponse
|
import com.tangem.domain.common.ScanResponse
|
||||||
import com.tangem.tap.domain.TapError
|
import com.tangem.tap.domain.TapError
|
||||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@ import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.common.extensions.VoidCallback
|
import com.tangem.common.extensions.VoidCallback
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||||
import com.tangem.datasource.utils.AndroidAssetReader
|
import com.tangem.datasource.asset.AssetReader
|
||||||
import com.tangem.domain.common.ScanResponse
|
import com.tangem.domain.common.ScanResponse
|
||||||
import com.tangem.domain.common.TwinCardNumber
|
import com.tangem.domain.common.TwinCardNumber
|
||||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||||
import com.tangem.tap.common.analytics.events.Onboarding
|
import com.tangem.tap.common.analytics.events.Onboarding
|
||||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||||
|
|
@ -39,10 +39,16 @@ import com.tangem.tap.features.wallet.redux.Artwork
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
|
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
|
@AndroidEntryPoint
|
||||||
class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var assetReader: AssetReader
|
||||||
|
|
||||||
private val mainBinding by lazy { binding.vMain }
|
private val mainBinding by lazy { binding.vMain }
|
||||||
private var previousStep: TwinCardsStep = TwinCardsStep.None
|
private var previousStep: TwinCardsStep = TwinCardsStep.None
|
||||||
|
|
||||||
|
|
@ -253,8 +259,8 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
||||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
TwinCardsAction.Wallet.LaunchFirstStep(
|
TwinCardsAction.Wallet.LaunchFirstStep(
|
||||||
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
|
initialMessage = Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
|
||||||
AndroidAssetReader(requireContext()),
|
reader = assetReader,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.tangem.common.CardIdFormatter
|
import com.tangem.common.CardIdFormatter
|
||||||
import com.tangem.common.core.CardIdDisplayFormat
|
import com.tangem.common.core.CardIdDisplayFormat
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.PropertyCalculator
|
import com.tangem.sdk.ui.widget.leapfrogWidget.PropertyCalculator
|
||||||
import com.tangem.tap.common.analytics.events.Onboarding
|
import com.tangem.tap.common.analytics.events.Onboarding
|
||||||
import com.tangem.tap.common.extensions.hide
|
import com.tangem.tap.common.extensions.hide
|
||||||
import com.tangem.tap.common.extensions.inflate
|
import com.tangem.tap.common.extensions.inflate
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import android.animation.PropertyValuesHolder
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.core.animation.doOnEnd
|
import androidx.core.animation.doOnEnd
|
||||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView
|
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapView
|
||||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState
|
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapViewState
|
||||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||||
|
|
||||||
class WalletCardsWidget(
|
class WalletCardsWidget(
|
||||||
val leapfrogWidget: LeapfrogWidget,
|
val leapfrogWidget: LeapfrogWidget,
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,12 @@ sealed class SendAction : SendScreenAction {
|
||||||
val reduceAmount: BigDecimal,
|
val reduceAmount: BigDecimal,
|
||||||
) : Dialog()
|
) : Dialog()
|
||||||
|
|
||||||
|
data class KaspaWarningDialog(
|
||||||
|
val maxOutputs: Int,
|
||||||
|
val maxAmount: BigDecimal,
|
||||||
|
val onOk: () -> Unit,
|
||||||
|
) : Dialog()
|
||||||
|
|
||||||
sealed class SendTransactionFails : Dialog() {
|
sealed class SendTransactionFails : Dialog() {
|
||||||
data class CardSdkError(val error: TangemSdkError) : Dialog()
|
data class CardSdkError(val error: TangemSdkError) : Dialog()
|
||||||
data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError) : Dialog()
|
data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError) : Dialog()
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ internal class AddressPayIdMiddleware {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setAddressAndCheck(data: String, isUserInput: Boolean, dispatch: (Action) -> Unit) {
|
private fun setAddressAndCheck(data: String, isUserInput: Boolean, dispatch: (Action) -> Unit) {
|
||||||
val potentialPayId = data.toLowerCase()
|
val potentialPayId = data.lowercase()
|
||||||
if (isPayIdEnabled() && PayIdManager.isPayId(potentialPayId)) {
|
if (isPayIdEnabled() && PayIdManager.isPayId(potentialPayId)) {
|
||||||
dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput))
|
dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -113,10 +113,11 @@ internal class AddressPayIdMiddleware {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyAddress(address: String, wallet: Wallet, isUserInput: Boolean, dispatch: (Action) -> Unit) {
|
private fun verifyAddress(address: String, wallet: Wallet, isUserInput: Boolean, dispatch: (Action) -> Unit) {
|
||||||
val addressSchemeSplit = if (wallet.blockchain == Blockchain.BitcoinCash) {
|
val addressSchemeSplit = when (wallet.blockchain) {
|
||||||
listOf(address)
|
Blockchain.BitcoinCash,
|
||||||
} else {
|
Blockchain.Kaspa,
|
||||||
address.split(":")
|
-> listOf(address)
|
||||||
|
else -> address.split(":")
|
||||||
}
|
}
|
||||||
val noSchemeAddress = when (addressSchemeSplit.size) {
|
val noSchemeAddress = when (addressSchemeSplit.size) {
|
||||||
1 -> address // no scheme
|
1 -> address // no scheme
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,18 @@ private fun sendTransaction(
|
||||||
// from XLM, XRP, Polkadot
|
// from XLM, XRP, Polkadot
|
||||||
dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error))
|
dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error))
|
||||||
}
|
}
|
||||||
|
is BlockchainSdkError.Kaspa.UtxoAmountError -> {
|
||||||
|
dispatch(
|
||||||
|
SendAction.Dialog.KaspaWarningDialog(
|
||||||
|
maxOutputs = error.maxOutputs,
|
||||||
|
maxAmount = error.maxAmount,
|
||||||
|
onOk = {
|
||||||
|
dispatch(AmountAction.SetAmount(error.maxAmount, isUserInput = false))
|
||||||
|
dispatch(AmountActionUi.CheckAmountToSend)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
when {
|
when {
|
||||||
error.customMessage.contains(DemoTransactionSender.ID) -> {
|
error.customMessage.contains(DemoTransactionSender.ID) -> {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ private class SendReducer : SendInternalReducer {
|
||||||
sendState.copy(sendButtonState = IndeterminateProgressButton(action.state))
|
sendState.copy(sendButtonState = IndeterminateProgressButton(action.state))
|
||||||
}
|
}
|
||||||
is SendAction.Dialog.TezosWarningDialog -> sendState.copy(dialog = action)
|
is SendAction.Dialog.TezosWarningDialog -> sendState.copy(dialog = action)
|
||||||
|
is SendAction.Dialog.KaspaWarningDialog -> sendState.copy(dialog = action)
|
||||||
is SendAction.Dialog.SendTransactionFails.CardSdkError -> sendState.copy(dialog = action)
|
is SendAction.Dialog.SendTransactionFails.CardSdkError -> sendState.copy(dialog = action)
|
||||||
is SendAction.Dialog.SendTransactionFails.BlockchainSdkError -> sendState.copy(dialog = action)
|
is SendAction.Dialog.SendTransactionFails.BlockchainSdkError -> sendState.copy(dialog = action)
|
||||||
is SendAction.Dialog.RequestFeeError -> sendState.copy(dialog = action)
|
is SendAction.Dialog.RequestFeeError -> sendState.copy(dialog = action)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
import com.tangem.Message
|
import com.tangem.Message
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard
|
import com.tangem.sdk.extensions.hideSoftKeyboard
|
||||||
import com.tangem.tap.common.KeyboardObserver
|
import com.tangem.tap.common.KeyboardObserver
|
||||||
import com.tangem.tap.common.analytics.events.Token
|
import com.tangem.tap.common.analytics.events.Token
|
||||||
import com.tangem.tap.common.entities.FiatCurrency
|
import com.tangem.tap.common.entities.FiatCurrency
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.tangem.tap.features.send.ui.dialogs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||||
|
import com.tangem.tap.features.send.redux.SendAction
|
||||||
|
import com.tangem.tap.store
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
|
||||||
|
object KaspaWarningDialog {
|
||||||
|
fun create(context: Context, dialog: SendAction.Dialog.KaspaWarningDialog): AlertDialog {
|
||||||
|
return AlertDialog.Builder(context).apply {
|
||||||
|
setTitle(R.string.common_warning)
|
||||||
|
setMessage(
|
||||||
|
context.getString(
|
||||||
|
R.string.kaspa_withdrawal_message_warning,
|
||||||
|
dialog.maxOutputs,
|
||||||
|
dialog.maxAmount.toPlainString(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
setPositiveButton(R.string.common_ok) { _, _ ->
|
||||||
|
dialog.onOk()
|
||||||
|
}
|
||||||
|
setOnDismissListener {
|
||||||
|
store.dispatchDialogHide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchain.common.BlockchainSdkError
|
import com.tangem.blockchain.common.BlockchainSdkError
|
||||||
import com.tangem.common.module.ModuleMessageConverter
|
import com.tangem.common.module.ModuleMessageConverter
|
||||||
import com.tangem.tangem_sdk_new.extensions.localizedDescription
|
import com.tangem.sdk.extensions.localizedDescription
|
||||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||||
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
|
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import com.tangem.tap.features.send.redux.states.TransactionExtraError
|
||||||
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
|
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
|
||||||
import com.tangem.tap.features.send.ui.FeeUiHelper
|
import com.tangem.tap.features.send.ui.FeeUiHelper
|
||||||
import com.tangem.tap.features.send.ui.SendFragment
|
import com.tangem.tap.features.send.ui.SendFragment
|
||||||
|
import com.tangem.tap.features.send.ui.dialogs.KaspaWarningDialog
|
||||||
import com.tangem.tap.features.send.ui.dialogs.RequestFeeErrorDialog
|
import com.tangem.tap.features.send.ui.dialogs.RequestFeeErrorDialog
|
||||||
import com.tangem.tap.features.send.ui.dialogs.SendTransactionFailsDialog
|
import com.tangem.tap.features.send.ui.dialogs.SendTransactionFailsDialog
|
||||||
import com.tangem.tap.features.send.ui.dialogs.TezosWarningDialog
|
import com.tangem.tap.features.send.ui.dialogs.TezosWarningDialog
|
||||||
|
|
@ -124,6 +125,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
|
||||||
dialog?.show()
|
dialog?.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is SendAction.Dialog.KaspaWarningDialog -> {
|
||||||
|
if (dialog == null) {
|
||||||
|
dialog = KaspaWarningDialog.create(fg.requireContext(), state.dialog)
|
||||||
|
dialog?.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
is SendAction.Dialog.SendTransactionFails.CardSdkError -> {
|
is SendAction.Dialog.SendTransactionFails.CardSdkError -> {
|
||||||
if (dialog == null) {
|
if (dialog == null) {
|
||||||
dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog)
|
dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.tap.features.tokens.di
|
||||||
|
|
||||||
|
import com.tangem.tap.features.tokens.presentation.router.AddTokensRouter
|
||||||
|
import com.tangem.tap.features.tokens.presentation.router.DefaultAddTokensRouter
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.components.ViewModelComponent
|
||||||
|
import dagger.hilt.android.scopes.ViewModelScoped
|
||||||
|
|
||||||
|
/**
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
@InstallIn(ViewModelComponent::class)
|
||||||
|
internal object AddTokensRouterModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ViewModelScoped
|
||||||
|
fun provideAddTokensRouter(): AddTokensRouter = DefaultAddTokensRouter()
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.tangem.tap.features.tokens.ui
|
package com.tangem.tap.features.tokens.presentation
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
|
|
@ -31,6 +31,7 @@ import com.tangem.tap.mainScope
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
import com.tangem.wallet.databinding.FragmentAddTokensBinding
|
import com.tangem.wallet.databinding.FragmentAddTokensBinding
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
|
@ -40,7 +41,8 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.rekotlin.StoreSubscriber
|
import org.rekotlin.StoreSubscriber
|
||||||
|
|
||||||
class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubscriber<TokensState> {
|
@AndroidEntryPoint
|
||||||
|
internal class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubscriber<TokensState> {
|
||||||
|
|
||||||
private val binding: FragmentAddTokensBinding by viewBinding(FragmentAddTokensBinding::bind)
|
private val binding: FragmentAddTokensBinding by viewBinding(FragmentAddTokensBinding::bind)
|
||||||
private var tokensState: MutableState<TokensState> = mutableStateOf(store.state.tokensState)
|
private var tokensState: MutableState<TokensState> = mutableStateOf(store.state.tokensState)
|
||||||
|
|
@ -155,18 +157,18 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc
|
||||||
tokensState.value = state
|
tokensState.value = state
|
||||||
binding.toolbar.title = getString(toolbarTitle)
|
binding.toolbar.title = getString(toolbarTitle)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun SearchView.inputtedTextAsFlow(): Flow<String> = callbackFlow {
|
private fun SearchView.inputtedTextAsFlow(): Flow<String> = callbackFlow {
|
||||||
val watcher = setOnQueryTextListener(
|
val watcher = setOnQueryTextListener(
|
||||||
object : SearchView.OnQueryTextListener {
|
object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean = false
|
override fun onQueryTextSubmit(query: String?): Boolean = false
|
||||||
|
|
||||||
override fun onQueryTextChange(newText: String?): Boolean {
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
trySend(newText ?: "")
|
trySend(newText ?: "")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
awaitClose { watcher }
|
awaitClose { watcher }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.tangem.tap.features.tokens.presentation.router
|
||||||
|
|
||||||
|
/** Add tokens feature router */
|
||||||
|
internal interface AddTokensRouter {
|
||||||
|
|
||||||
|
/** Return to last screen */
|
||||||
|
fun popBackStack()
|
||||||
|
|
||||||
|
/** Open screen for adding custom token */
|
||||||
|
fun openAddCustomTokenScreen()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.tangem.tap.features.tokens.presentation.router
|
||||||
|
|
||||||
|
import com.tangem.core.analytics.Analytics
|
||||||
|
import com.tangem.tap.common.analytics.events.ManageTokens
|
||||||
|
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||||
|
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||||
|
import com.tangem.tap.store
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router implementation for add tokens feature
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
internal class DefaultAddTokensRouter : AddTokensRouter {
|
||||||
|
|
||||||
|
override fun popBackStack() {
|
||||||
|
store.dispatch(NavigationAction.PopBackTo())
|
||||||
|
store.dispatch(TokensAction.ResetState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openAddCustomTokenScreen() {
|
||||||
|
Analytics.send(ManageTokens.ButtonCustomToken())
|
||||||
|
store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.tangem.tap.features.tokens.presentation.states
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Network item state
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
sealed interface AddTokensNetworkItemState {
|
||||||
|
|
||||||
|
/** Network name */
|
||||||
|
val name: String
|
||||||
|
|
||||||
|
/** Network protocol name */
|
||||||
|
val protocolName: String
|
||||||
|
|
||||||
|
/** Network icon id from resources */
|
||||||
|
val iconResId: Int
|
||||||
|
|
||||||
|
/** Flag that determines if the network is the main network for the token */
|
||||||
|
val isMainNetwork: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Network item state that is available for read
|
||||||
|
*
|
||||||
|
* @property name network name
|
||||||
|
* @property iconResId network icon id from resources
|
||||||
|
* @property isMainNetwork flag that determines if the network is the main network for the token
|
||||||
|
*/
|
||||||
|
data class ReadAccess(
|
||||||
|
override val name: String,
|
||||||
|
override val protocolName: String,
|
||||||
|
override val iconResId: Int,
|
||||||
|
override val isMainNetwork: Boolean,
|
||||||
|
) : AddTokensNetworkItemState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Network item state that is available for read and edit
|
||||||
|
*
|
||||||
|
* @property name network name
|
||||||
|
* @property iconResId network icon id from resources
|
||||||
|
* @property isMainNetwork flag that determines if the network is the main network for the token
|
||||||
|
* @property isAdded flag that determines if the user has saved the token
|
||||||
|
* @property networkId network id
|
||||||
|
* @property onToggleClick lambda be invoked when switch is been toggled
|
||||||
|
*/
|
||||||
|
data class EditAccess(
|
||||||
|
override val name: String,
|
||||||
|
override val protocolName: String,
|
||||||
|
override val iconResId: Int,
|
||||||
|
override val isMainNetwork: Boolean,
|
||||||
|
val isAdded: Boolean,
|
||||||
|
val networkId: String,
|
||||||
|
val onToggleClick: (String) -> Unit,
|
||||||
|
) : AddTokensNetworkItemState
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.tangem.tap.features.tokens.presentation.states
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State holder for adding token screen
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
internal sealed interface AddTokensStateHolder {
|
||||||
|
|
||||||
|
/** Toolbar state */
|
||||||
|
val toolbarState: AddTokensToolbarState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util function that allow to make a copy
|
||||||
|
*
|
||||||
|
* @param toolbarState toolbar state
|
||||||
|
*/
|
||||||
|
fun copySealed(toolbarState: AddTokensToolbarState): AddTokensStateHolder {
|
||||||
|
return when (this) {
|
||||||
|
is ManageAccess -> copy(toolbarState = toolbarState)
|
||||||
|
is Loading -> copy(toolbarState = toolbarState)
|
||||||
|
is ReadAccess -> copy(toolbarState = toolbarState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loading state
|
||||||
|
*
|
||||||
|
* @property toolbarState toolbar state
|
||||||
|
*/
|
||||||
|
data class Loading(override val toolbarState: AddTokensToolbarState) : AddTokensStateHolder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State screen that is available only for read
|
||||||
|
*
|
||||||
|
* @property toolbarState toolbar state
|
||||||
|
* @property tokens tokens list
|
||||||
|
*/
|
||||||
|
data class ReadAccess(
|
||||||
|
override val toolbarState: AddTokensToolbarState,
|
||||||
|
override val tokens: List<TokenItemModel>,
|
||||||
|
) : AddTokensStateHolder, TokensListVisibility
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State screen that is available for read and edit
|
||||||
|
*
|
||||||
|
* @property toolbarState toolbar state
|
||||||
|
* @property tokens tokens list
|
||||||
|
* @property onSaveButtonClick callback to be invoked when SaveButton is being clicked
|
||||||
|
*/
|
||||||
|
data class ManageAccess(
|
||||||
|
override val toolbarState: AddTokensToolbarState,
|
||||||
|
override val tokens: List<TokenItemModel>,
|
||||||
|
val onSaveButtonClick: () -> Unit,
|
||||||
|
) : AddTokensStateHolder, TokensListVisibility
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.tangem.tap.features.tokens.presentation.states
|
||||||
|
|
||||||
|
/** Toolbar state */
|
||||||
|
sealed interface AddTokensToolbarState {
|
||||||
|
|
||||||
|
/** Callback to be invoked when BackButton is being clicked */
|
||||||
|
val onBackButtonClick: () -> Unit
|
||||||
|
|
||||||
|
/** Callback to be invoked when SearchButton is being clicked */
|
||||||
|
val onSearchButtonClick: () -> Unit
|
||||||
|
|
||||||
|
/** Toolbar state as title */
|
||||||
|
sealed interface Title : AddTokensToolbarState {
|
||||||
|
|
||||||
|
/** Toolbar title id from resources */
|
||||||
|
val titleResId: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title state that is available only for read
|
||||||
|
*
|
||||||
|
* @property titleResId toolbar title id from resources
|
||||||
|
* @property onBackButtonClick callback to be invoked when BackButton is being clicked
|
||||||
|
* @property onSearchButtonClick callback to be invoked when SearchButton is being clicked
|
||||||
|
*/
|
||||||
|
data class ReadAccess(
|
||||||
|
override val titleResId: Int,
|
||||||
|
override val onBackButtonClick: () -> Unit,
|
||||||
|
override val onSearchButtonClick: () -> Unit,
|
||||||
|
) : Title
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title state that is available for read and edit
|
||||||
|
*
|
||||||
|
* @property titleResId toolbar title id from resources
|
||||||
|
* @property onBackButtonClick callback to be invoked when BackButton is being clicked
|
||||||
|
* @property onSearchButtonClick callback to be invoked when SearchButton is being clicked
|
||||||
|
* @property onAddCustomTokenClick callback to be invoked when AddCustomTokenButton is being clicked
|
||||||
|
*/
|
||||||
|
data class EditAccess(
|
||||||
|
override val titleResId: Int,
|
||||||
|
override val onBackButtonClick: () -> Unit,
|
||||||
|
override val onSearchButtonClick: () -> Unit,
|
||||||
|
val onAddCustomTokenClick: () -> Unit,
|
||||||
|
) : Title
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toolbar state as input field
|
||||||
|
*
|
||||||
|
* @property onBackButtonClick callback to be invoked when BackButton is being clicked
|
||||||
|
* @property onSearchButtonClick callback to be invoked when SearchButton is being clicked
|
||||||
|
* @property value input value
|
||||||
|
* @property onValueChange lambda to be invoked when search value is being changed
|
||||||
|
* @property onCleanButtonClick callback to be invoked when CleanButton is being clicked
|
||||||
|
*/
|
||||||
|
data class SearchInputField(
|
||||||
|
override val onBackButtonClick: () -> Unit,
|
||||||
|
override val onSearchButtonClick: () -> Unit,
|
||||||
|
val value: String,
|
||||||
|
val onValueChange: (String) -> Unit,
|
||||||
|
val onCleanButtonClick: () -> Unit,
|
||||||
|
) : AddTokensToolbarState
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.tangem.tap.features.tokens.presentation.states
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token model
|
||||||
|
*
|
||||||
|
* @property name token name
|
||||||
|
* @property iconUrl token icon url
|
||||||
|
* @property networks list of networks
|
||||||
|
*/
|
||||||
|
data class TokenItemModel(
|
||||||
|
val name: String,
|
||||||
|
val iconUrl: String,
|
||||||
|
val networks: List<AddTokensNetworkItemState>,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.tap.features.tokens.presentation.states
|
||||||
|
|
||||||
|
/** Marker interface for states that have a list of tokens */
|
||||||
|
sealed interface TokensListVisibility {
|
||||||
|
|
||||||
|
/** Tokens list */
|
||||||
|
val tokens: List<TokenItemModel>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
package com.tangem.tap.features.tokens.presentation.viewmodels
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.tangem.tap.common.redux.AppState
|
||||||
|
import com.tangem.tap.features.tokens.presentation.router.AddTokensRouter
|
||||||
|
import com.tangem.tap.features.tokens.presentation.states.AddTokensStateHolder
|
||||||
|
import com.tangem.tap.features.tokens.presentation.states.AddTokensToolbarState
|
||||||
|
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||||
|
import com.tangem.tap.features.tokens.redux.TokensState
|
||||||
|
import com.tangem.tap.store
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import org.rekotlin.StoreSubscriber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel for add tokens screen
|
||||||
|
*
|
||||||
|
* @property router feature router
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@HiltViewModel
|
||||||
|
internal class AddTokensViewModel @Inject constructor(
|
||||||
|
private val router: AddTokensRouter,
|
||||||
|
) : ViewModel(), DefaultLifecycleObserver, StoreSubscriber<TokensState> {
|
||||||
|
|
||||||
|
var uiState by mutableStateOf(getInitialUiState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
loadContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
|
store.unsubscribe(subscriber = this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
|
store.dispatch(TokensAction.ResetState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newState(state: TokensState) {
|
||||||
|
uiState = uiState.copySealed(
|
||||||
|
toolbarState = getToolbarState(hasEditAccess = state.allowToAdd),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadContent() {
|
||||||
|
store.subscribe(subscriber = this) { state ->
|
||||||
|
state
|
||||||
|
.skipRepeats { old, new -> old.tokensState == new.tokensState }
|
||||||
|
.select(AppState::tokensState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInitialUiState(): AddTokensStateHolder {
|
||||||
|
return AddTokensStateHolder.Loading(toolbarState = getToolbarState(store.state.tokensState.allowToAdd))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getToolbarState(hasEditAccess: Boolean): AddTokensToolbarState {
|
||||||
|
return if (hasEditAccess) {
|
||||||
|
AddTokensToolbarState.Title.EditAccess(
|
||||||
|
titleResId = R.string.main_manage_tokens,
|
||||||
|
onBackButtonClick = router::popBackStack,
|
||||||
|
onSearchButtonClick = ::onSearchButtonClick,
|
||||||
|
onAddCustomTokenClick = router::openAddCustomTokenScreen,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AddTokensToolbarState.Title.ReadAccess(
|
||||||
|
titleResId = R.string.search_tokens_title,
|
||||||
|
onBackButtonClick = router::popBackStack,
|
||||||
|
onSearchButtonClick = ::onSearchButtonClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSearchButtonClick() {
|
||||||
|
uiState = when (val state = uiState.toolbarState) {
|
||||||
|
is AddTokensToolbarState.Title -> {
|
||||||
|
uiState.copySealed(
|
||||||
|
toolbarState = AddTokensToolbarState.SearchInputField(
|
||||||
|
onBackButtonClick = uiState.toolbarState.onBackButtonClick,
|
||||||
|
onSearchButtonClick = ::onSearchButtonClick,
|
||||||
|
value = "",
|
||||||
|
onValueChange = ::onValueChange,
|
||||||
|
onCleanButtonClick = { onValueChange(newValue = "") },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is AddTokensToolbarState.SearchInputField -> {
|
||||||
|
store.dispatch(TokensAction.SetSearchInput(state.value))
|
||||||
|
uiState.copySealed(
|
||||||
|
getToolbarState(store.state.tokensState.allowToAdd),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onValueChange(newValue: String) {
|
||||||
|
val state = requireNotNull(
|
||||||
|
value = uiState.toolbarState as? AddTokensToolbarState.SearchInputField,
|
||||||
|
lazyMessage = { "Impossible to change value in another state" },
|
||||||
|
)
|
||||||
|
|
||||||
|
uiState = uiState.copySealed(toolbarState = state.copy(value = newValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,6 @@ import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||||
import com.tangem.domain.redux.domainStore
|
import com.tangem.domain.redux.domainStore
|
||||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||||
import com.tangem.tap.assetReader
|
|
||||||
import com.tangem.tap.common.analytics.events.ManageTokens
|
import com.tangem.tap.common.analytics.events.ManageTokens
|
||||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||||
|
|
@ -32,6 +31,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||||
import com.tangem.tap.domain.TapError
|
import com.tangem.tap.domain.TapError
|
||||||
import com.tangem.tap.domain.tokens.LoadAvailableCoinsService
|
import com.tangem.tap.domain.tokens.LoadAvailableCoinsService
|
||||||
import com.tangem.tap.features.wallet.models.Currency
|
import com.tangem.tap.features.wallet.models.Currency
|
||||||
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.tap.scope
|
import com.tangem.tap.scope
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.tap.tangemSdkManager
|
import com.tangem.tap.tangemSdkManager
|
||||||
|
|
@ -85,7 +85,7 @@ class TokensMiddleware {
|
||||||
val loadCoinsService = LoadAvailableCoinsService(
|
val loadCoinsService = LoadAvailableCoinsService(
|
||||||
tangemTechApi = store.state.domainNetworks.tangemTechService.api,
|
tangemTechApi = store.state.domainNetworks.tangemTechService.api,
|
||||||
dispatchers = AppCoroutineDispatcherProvider(),
|
dispatchers = AppCoroutineDispatcherProvider(),
|
||||||
assetReader = assetReader,
|
assetReader = store.state.daggerGraphState.get(DaggerGraphState::assetReader),
|
||||||
)
|
)
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|
@ -264,14 +264,10 @@ class TokensMiddleware {
|
||||||
val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
||||||
if (toDerive.isEmpty()) return null
|
if (toDerive.isEmpty()) return null
|
||||||
|
|
||||||
return DerivationData(
|
return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
|
||||||
derivations = mapKeyOfWalletPublicKey to toDerive,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DerivationData(
|
private class DerivationData(val derivations: Pair<ByteArrayKey, List<DerivationPath>>)
|
||||||
val derivations: Pair<ByteArrayKey, List<DerivationPath>>,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun submitAdd(scanResponse: ScanResponse, currencyList: List<Currency>) {
|
private fun submitAdd(scanResponse: ScanResponse, currencyList: List<Currency>) {
|
||||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchain.common.TransactionData
|
import com.tangem.blockchain.common.TransactionData
|
||||||
import com.tangem.common.extensions.isZero
|
import com.tangem.common.extensions.isZero
|
||||||
import com.tangem.domain.common.extensions.toNetworkId
|
import com.tangem.domain.common.extensions.toNetworkId
|
||||||
|
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||||
import com.tangem.feature.swap.domain.SwapInteractor
|
import com.tangem.feature.swap.domain.SwapInteractor
|
||||||
import com.tangem.tap.features.wallet.models.Currency
|
import com.tangem.tap.features.wallet.models.Currency
|
||||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||||
|
|
@ -36,22 +37,26 @@ data class WalletData(
|
||||||
return exchangeManager.availableForSell(currency)
|
return exchangeManager.availableForSell(currency)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isAvailableToSwap(swapInteractor: SwapInteractor, isExchangeFeatureOn: Boolean): Boolean {
|
fun isAvailableToSwap(
|
||||||
|
swapFeatureToggleManager: SwapFeatureToggleManager,
|
||||||
|
swapInteractor: SwapInteractor,
|
||||||
|
): Boolean {
|
||||||
|
if (currency.blockchain.id == Blockchain.Optimism.id && !swapFeatureToggleManager.isOptimismSwapEnabled) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return swapInteractor.isAvailableToSwap(currency.blockchain.toNetworkId()) &&
|
return swapInteractor.isAvailableToSwap(currency.blockchain.toNetworkId()) &&
|
||||||
currency.blockchain.id != Blockchain.Optimism.id && // disable optimism blockchain for now
|
|
||||||
isExchangeFeatureOn &&
|
|
||||||
!currency.isCustomCurrency(null)
|
!currency.isCustomCurrency(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAvailableActions(
|
fun getAvailableActions(
|
||||||
swapInteractor: SwapInteractor,
|
swapInteractor: SwapInteractor,
|
||||||
exchangeManager: CurrencyExchangeManager,
|
exchangeManager: CurrencyExchangeManager,
|
||||||
isExchangeFeatureOn: Boolean,
|
swapFeatureToggleManager: SwapFeatureToggleManager,
|
||||||
): Set<CurrencyAction> {
|
): Set<CurrencyAction> {
|
||||||
return setOfNotNull(
|
return setOfNotNull(
|
||||||
if (isAvailableToBuy(exchangeManager)) CurrencyAction.Buy else null,
|
if (isAvailableToBuy(exchangeManager)) CurrencyAction.Buy else null,
|
||||||
if (isAvailableToSell(exchangeManager)) CurrencyAction.Sell else null,
|
if (isAvailableToSell(exchangeManager)) CurrencyAction.Sell else null,
|
||||||
if (isAvailableToSwap(swapInteractor, isExchangeFeatureOn)) CurrencyAction.Swap else null,
|
if (isAvailableToSwap(swapFeatureToggleManager, swapInteractor)) CurrencyAction.Swap else null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import com.tangem.blockchain.common.AmountType
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
import com.tangem.common.extensions.guard
|
import com.tangem.common.extensions.guard
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||||
import com.tangem.tap.common.analytics.events.Basic
|
import com.tangem.tap.common.analytics.events.Basic
|
||||||
import com.tangem.tap.common.analytics.events.MainScreen
|
import com.tangem.tap.common.analytics.events.MainScreen
|
||||||
|
|
@ -34,17 +35,17 @@ import com.tangem.tap.features.wallet.redux.WalletData
|
||||||
import com.tangem.tap.features.wallet.redux.WalletState
|
import com.tangem.tap.features.wallet.redux.WalletState
|
||||||
import com.tangem.tap.features.wallet.redux.WalletStore
|
import com.tangem.tap.features.wallet.redux.WalletStore
|
||||||
import com.tangem.tap.features.wallet.redux.reducers.findSelectedCurrency
|
import com.tangem.tap.features.wallet.redux.reducers.findSelectedCurrency
|
||||||
import com.tangem.tap.network.NetworkConnectivity
|
|
||||||
import com.tangem.tap.network.NetworkStateChanged
|
|
||||||
import com.tangem.tap.preferencesStorage
|
import com.tangem.tap.preferencesStorage
|
||||||
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.tap.scope
|
import com.tangem.tap.scope
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.tap.tangemSdkManager
|
import com.tangem.tap.tangemSdkManager
|
||||||
import com.tangem.tap.totalFiatBalanceCalculator
|
import com.tangem.tap.totalFiatBalanceCalculator
|
||||||
import com.tangem.tap.userWalletsListManager
|
import com.tangem.tap.userWalletsListManager
|
||||||
import com.tangem.tap.userWalletsListManagerSafe
|
import com.tangem.utils.coroutines.ifActive
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.rekotlin.Action
|
import org.rekotlin.Action
|
||||||
|
|
@ -68,6 +69,15 @@ class WalletMiddleware {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val networkConnectionManager: NetworkConnectionManager
|
||||||
|
get() = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||||
|
|
||||||
|
private var updateWalletStoresJob: Job? = null
|
||||||
|
set(value) {
|
||||||
|
field?.cancel()
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
val walletMiddleware: Middleware<AppState> = { _, state ->
|
val walletMiddleware: Middleware<AppState> = { _, state ->
|
||||||
{ next ->
|
{ next ->
|
||||||
{ action ->
|
{ action ->
|
||||||
|
|
@ -131,16 +141,6 @@ class WalletMiddleware {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is NetworkStateChanged -> {
|
|
||||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline)
|
|
||||||
if (!action.isOnline) return
|
|
||||||
|
|
||||||
val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync.guard {
|
|
||||||
Timber.e("Unable to proceed with changed network state, no user wallet selected")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
scope.launch { globalState.tapWalletManager.loadData(selectedUserWallet, refresh = true) }
|
|
||||||
}
|
|
||||||
is WalletAction.CopyAddress -> {
|
is WalletAction.CopyAddress -> {
|
||||||
Analytics.send(Token.Receive.ButtonCopyAddress())
|
Analytics.send(Token.Receive.ButtonCopyAddress())
|
||||||
action.context.copyToClipboard(action.address)
|
action.context.copyToClipboard(action.address)
|
||||||
|
|
@ -155,7 +155,7 @@ class WalletMiddleware {
|
||||||
store.dispatchOpenUrl(action.exploreUrl)
|
store.dispatchOpenUrl(action.exploreUrl)
|
||||||
}
|
}
|
||||||
is WalletAction.Send -> {
|
is WalletAction.Send -> {
|
||||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
if (!networkConnectionManager.isOnline) {
|
||||||
store.dispatchErrorNotification(TapError.NoInternetConnection)
|
store.dispatchErrorNotification(TapError.NoInternetConnection)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -184,11 +184,14 @@ class WalletMiddleware {
|
||||||
}
|
}
|
||||||
is WalletAction.UserWalletChanged -> Unit
|
is WalletAction.UserWalletChanged -> Unit
|
||||||
is WalletAction.WalletStoresChanged -> {
|
is WalletAction.WalletStoresChanged -> {
|
||||||
store.state.globalState.topUpController?.walletStoresChanged(action.walletStores)
|
// Cancel update job when new wallet stores received
|
||||||
updateWalletStores(action.walletStores, walletState)
|
updateWalletStoresJob = scope.launch(Dispatchers.Default) {
|
||||||
fetchTotalFiatBalance(action.walletStores)
|
ifActive { updateWalletStores(action.walletStores, walletState) }
|
||||||
findMissedDerivations(action.walletStores)
|
ifActive { fetchTotalFiatBalance(action.walletStores) }
|
||||||
tryToShowAppRatingWarning(action.walletStores)
|
ifActive { findMissedDerivations(action.walletStores) }
|
||||||
|
ifActive { tryToShowAppRatingWarning(action.walletStores) }
|
||||||
|
ifActive { store.state.globalState.topUpController?.walletStoresChanged(action.walletStores) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is WalletAction.TotalFiatBalanceChanged -> Unit
|
is WalletAction.TotalFiatBalanceChanged -> Unit
|
||||||
is WalletAction.PopBackToInitialScreen -> {
|
is WalletAction.PopBackToInitialScreen -> {
|
||||||
|
|
@ -205,55 +208,47 @@ class WalletMiddleware {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateWalletStores(walletsStores: List<WalletStoreModel>, state: WalletState) {
|
private fun updateWalletStores(walletsStores: List<WalletStoreModel>, state: WalletState) {
|
||||||
scope.launch(Dispatchers.Default) {
|
val reduxWalletStores = walletsStores.mapToReduxModels()
|
||||||
val reduxWalletStores = walletsStores.mapToReduxModels()
|
if (!state.isMultiwalletAllowed) {
|
||||||
if (!state.isMultiwalletAllowed) {
|
findSelectedCurrency(
|
||||||
findSelectedCurrency(
|
walletsStores = reduxWalletStores,
|
||||||
walletsStores = reduxWalletStores,
|
currentSelectedCurrency = null,
|
||||||
currentSelectedCurrency = null,
|
isMultiWalletAllowed = false,
|
||||||
isMultiWalletAllowed = false,
|
)?.let {
|
||||||
)?.let {
|
store.dispatchOnMain(WalletAction.MultiWallet.SetSingleWalletCurrency(it))
|
||||||
store.dispatchOnMain(WalletAction.MultiWallet.SetSingleWalletCurrency(it))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
store.dispatchOnMain(
|
|
||||||
WalletAction.WalletStoresChanged.UpdateWalletStores(
|
|
||||||
reduxWalletStores = reduxWalletStores,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
store.dispatchOnMain(
|
||||||
|
WalletAction.WalletStoresChanged.UpdateWalletStores(
|
||||||
|
reduxWalletStores = reduxWalletStores,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchTotalFiatBalance(walletStores: List<WalletStoreModel>) {
|
private suspend fun fetchTotalFiatBalance(walletStores: List<WalletStoreModel>) {
|
||||||
scope.launch(Dispatchers.Default) {
|
val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull(walletStores)?.mapToReduxModel()
|
||||||
val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull(walletStores)?.mapToReduxModel()
|
|
||||||
|
|
||||||
if (totalFiatBalance != null) {
|
if (totalFiatBalance != null) {
|
||||||
store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance))
|
store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findMissedDerivations(wallStores: List<WalletStoreModel>) {
|
private fun findMissedDerivations(wallStores: List<WalletStoreModel>) {
|
||||||
scope.launch(Dispatchers.Default) {
|
val missedDerivations = wallStores
|
||||||
val missedDerivations = wallStores
|
.filter { store ->
|
||||||
.filter { store ->
|
store.walletsData.any { it.status is WalletDataModel.MissedDerivation }
|
||||||
store.walletsData.any { it.status is WalletDataModel.MissedDerivation }
|
}
|
||||||
}
|
.map(WalletStoreModel::blockchainNetwork)
|
||||||
.map(WalletStoreModel::blockchainNetwork)
|
|
||||||
|
|
||||||
store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(missedDerivations))
|
store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(missedDerivations))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryToShowAppRatingWarning(walletStores: List<WalletStoreModel>) {
|
private fun tryToShowAppRatingWarning(walletStores: List<WalletStoreModel>) {
|
||||||
scope.launch(Dispatchers.Default) {
|
warningsMiddleware.tryToShowAppRatingWarning(
|
||||||
warningsMiddleware.tryToShowAppRatingWarning(
|
hasNonZeroWallets = walletStores
|
||||||
hasNonZeroWallets = walletStores
|
.flatMap { it.walletsData }
|
||||||
.flatMap { it.walletsData }
|
.any { it.status.amount.isGreaterThan(BigDecimal.ZERO) },
|
||||||
.any { it.status.amount.isGreaterThan(BigDecimal.ZERO) },
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSaveWalletIfNeeded() {
|
private fun showSaveWalletIfNeeded() {
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,26 @@ package com.tangem.tap.features.wallet.redux.middlewares
|
||||||
|
|
||||||
import com.tangem.blockchain.common.BlockchainSdkError
|
import com.tangem.blockchain.common.BlockchainSdkError
|
||||||
import com.tangem.blockchain.common.SignatureCountValidator
|
import com.tangem.blockchain.common.SignatureCountValidator
|
||||||
import com.tangem.blockchain.common.Wallet
|
|
||||||
import com.tangem.blockchain.extensions.SimpleResult
|
import com.tangem.blockchain.extensions.SimpleResult
|
||||||
import com.tangem.common.card.FirmwareVersion
|
import com.tangem.common.card.FirmwareVersion
|
||||||
import com.tangem.domain.common.CardDTO
|
import com.tangem.domain.common.CardDTO
|
||||||
import com.tangem.domain.common.ScanResponse
|
import com.tangem.domain.common.ScanResponse
|
||||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||||
import com.tangem.tap.common.extensions.isGreaterThan
|
|
||||||
import com.tangem.tap.common.redux.global.GlobalState
|
import com.tangem.tap.common.redux.global.GlobalState
|
||||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||||
import com.tangem.tap.domain.extensions.hasSignedHashes
|
import com.tangem.tap.domain.extensions.hasSignedHashes
|
||||||
import com.tangem.tap.features.demo.isDemoCard
|
import com.tangem.tap.features.demo.isDemoCard
|
||||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||||
import com.tangem.tap.network.NetworkConnectivity
|
|
||||||
import com.tangem.tap.preferencesStorage
|
import com.tangem.tap.preferencesStorage
|
||||||
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.tap.scope
|
import com.tangem.tap.scope
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.wallet.R
|
import com.tangem.wallet.R
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.math.BigDecimal
|
|
||||||
|
|
||||||
class WarningsMiddleware {
|
class WarningsMiddleware {
|
||||||
fun handle(action: WalletAction.Warnings, globalState: GlobalState?) {
|
fun handle(action: WalletAction.Warnings, globalState: GlobalState?) {
|
||||||
|
|
@ -75,13 +72,6 @@ class WarningsMiddleware {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tryToShowAppRatingWarning(wallet: Wallet) {
|
|
||||||
val nonZeroWalletsCount = wallet.amounts.filter {
|
|
||||||
it.value.value?.isGreaterThan(BigDecimal.ZERO) ?: false
|
|
||||||
}.size
|
|
||||||
tryToShowAppRatingWarning(hasNonZeroWallets = nonZeroWalletsCount > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showCardWarningsIfNeeded(globalState: GlobalState?) {
|
private fun showCardWarningsIfNeeded(globalState: GlobalState?) {
|
||||||
globalState?.scanResponse?.let { scanResponse ->
|
globalState?.scanResponse?.let { scanResponse ->
|
||||||
val card = scanResponse.card
|
val card = scanResponse.card
|
||||||
|
|
@ -147,7 +137,9 @@ class WarningsMiddleware {
|
||||||
|
|
||||||
private fun checkHashesCountOnline() {
|
private fun checkHashesCountOnline() {
|
||||||
if (store.state.walletState.hashesCountVerified != false) return
|
if (store.state.walletState.hashesCountVerified != false) return
|
||||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return
|
|
||||||
|
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||||
|
if (!networkConnectionManager.isOnline) return
|
||||||
|
|
||||||
val scanResponse = store.state.globalState.scanResponse
|
val scanResponse = store.state.globalState.scanResponse
|
||||||
val card = scanResponse?.card
|
val card = scanResponse?.card
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,9 @@ import com.tangem.common.extensions.guard
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||||
import com.tangem.domain.common.extensions.withMainContext
|
import com.tangem.domain.common.extensions.withMainContext
|
||||||
|
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||||
import com.tangem.feature.swap.domain.SwapInteractor
|
import com.tangem.feature.swap.domain.SwapInteractor
|
||||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
import com.tangem.sdk.extensions.dpToPx
|
||||||
import com.tangem.tap.common.SnackbarHandler
|
import com.tangem.tap.common.SnackbarHandler
|
||||||
import com.tangem.tap.common.TestActions
|
import com.tangem.tap.common.TestActions
|
||||||
import com.tangem.tap.common.analytics.events.DetailsScreen
|
import com.tangem.tap.common.analytics.events.DetailsScreen
|
||||||
|
|
@ -75,6 +76,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var swapInteractor: SwapInteractor
|
lateinit var swapInteractor: SwapInteractor
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var swapFeatureToggleManager: SwapFeatureToggleManager
|
||||||
|
|
||||||
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
||||||
private lateinit var warningMessagesAdapter: WalletDetailWarningMessagesAdapter
|
private lateinit var warningMessagesAdapter: WalletDetailWarningMessagesAdapter
|
||||||
|
|
||||||
|
|
@ -290,7 +294,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
||||||
WalletAction.DialogAction.ChooseTradeActionDialog(
|
WalletAction.DialogAction.ChooseTradeActionDialog(
|
||||||
buyAllowed = selectedWallet.isAvailableToBuy(exchangeManager),
|
buyAllowed = selectedWallet.isAvailableToBuy(exchangeManager),
|
||||||
sellAllowed = selectedWallet.isAvailableToSell(exchangeManager),
|
sellAllowed = selectedWallet.isAvailableToSell(exchangeManager),
|
||||||
swapAllowed = selectedWallet.isAvailableToSwap(swapInteractor, isExchangeServiceFeatureOn),
|
swapAllowed = selectedWallet.isAvailableToSwap(swapFeatureToggleManager, swapInteractor),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +302,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
||||||
val actions = selectedWallet.getAvailableActions(
|
val actions = selectedWallet.getAvailableActions(
|
||||||
swapInteractor = swapInteractor,
|
swapInteractor = swapInteractor,
|
||||||
exchangeManager = exchangeManager,
|
exchangeManager = exchangeManager,
|
||||||
isExchangeFeatureOn = isExchangeServiceFeatureOn,
|
swapFeatureToggleManager = swapFeatureToggleManager,
|
||||||
)
|
)
|
||||||
binding.rowButtons.updateButtonsVisibility(
|
binding.rowButtons.updateButtonsVisibility(
|
||||||
actions = actions,
|
actions = actions,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.tangem.tap.features.wallet.ui
|
package com.tangem.tap.features.wallet.ui
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
|
|
@ -10,7 +9,6 @@ import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.transition.TransitionInflater
|
import androidx.transition.TransitionInflater
|
||||||
|
|
@ -21,6 +19,7 @@ import com.badoo.mvicore.modelWatcher
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||||
import com.tangem.core.ui.utils.OneTouchClickListener
|
import com.tangem.core.ui.utils.OneTouchClickListener
|
||||||
|
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||||
import com.tangem.feature.swap.domain.SwapInteractor
|
import com.tangem.feature.swap.domain.SwapInteractor
|
||||||
import com.tangem.tap.MainActivity
|
import com.tangem.tap.MainActivity
|
||||||
import com.tangem.tap.common.analytics.events.Portfolio
|
import com.tangem.tap.common.analytics.events.Portfolio
|
||||||
|
|
@ -56,6 +55,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var swapInteractor: SwapInteractor
|
lateinit var swapInteractor: SwapInteractor
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var swapFeatureToggleManager: SwapFeatureToggleManager
|
||||||
|
|
||||||
private lateinit var warningsAdapter: WarningMessagesAdapter
|
private lateinit var warningsAdapter: WarningMessagesAdapter
|
||||||
|
|
||||||
private val binding: FragmentWalletBinding by viewBinding(FragmentWalletBinding::bind)
|
private val binding: FragmentWalletBinding by viewBinding(FragmentWalletBinding::bind)
|
||||||
|
|
@ -67,22 +69,16 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
||||||
private val totalBalanceWatcher = modelWatcher {
|
private val totalBalanceWatcher = modelWatcher {
|
||||||
(WalletState::totalBalance) { totalBalance ->
|
(WalletState::totalBalance) { totalBalance ->
|
||||||
totalBalance?.state?.let {
|
totalBalance?.state?.let {
|
||||||
|
viewModel.onBalanceLoaded(totalBalance)
|
||||||
store.state.globalState.topUpController?.totalBalanceStateChanged(it)
|
store.state.globalState.topUpController?.totalBalanceStateChanged(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
super.onAttach(context)
|
|
||||||
lifecycle.addObserver(viewModel)
|
|
||||||
activity?.lifecycleScope?.launchWhenCreated {
|
|
||||||
viewModel.launch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
|
activity?.lifecycle?.addObserver(viewModel)
|
||||||
|
|
||||||
activity?.onBackPressedDispatcher?.addCallback(
|
activity?.onBackPressedDispatcher?.addCallback(
|
||||||
this,
|
this,
|
||||||
|
|
@ -179,6 +175,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
||||||
totalBalanceWatcher.invoke(state)
|
totalBalanceWatcher.invoke(state)
|
||||||
|
|
||||||
walletView.swapInteractor = swapInteractor
|
walletView.swapInteractor = swapInteractor
|
||||||
|
walletView.swapFeatureToggleManager = swapFeatureToggleManager
|
||||||
|
|
||||||
walletView.onNewState(state)
|
walletView.onNewState(state)
|
||||||
|
|
||||||
|
|
@ -255,10 +252,10 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.details_menu -> {
|
R.id.details_menu -> {
|
||||||
store.dispatch(GlobalAction.UpdateFeedbackInfo(store.state.walletState.walletManagers))
|
store.dispatch(GlobalAction.UpdateFeedbackInfo(store.state.walletState.walletManagers))
|
||||||
store.state.globalState.scanResponse?.let { scanNoteResponse ->
|
store.state.globalState.scanResponse?.let { scanResponse ->
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
DetailsAction.PrepareScreen(
|
DetailsAction.PrepareScreen(
|
||||||
scanResponse = scanNoteResponse,
|
scanResponse = scanResponse,
|
||||||
wallets = store.state.walletState.walletManagers.map { it.wallet },
|
wallets = store.state.walletState.walletManagers.map { it.wallet },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,34 +4,45 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||||
import com.tangem.tap.common.analytics.events.Basic
|
import com.tangem.tap.common.analytics.events.Basic
|
||||||
import com.tangem.tap.common.analytics.events.MainScreen
|
import com.tangem.tap.common.analytics.events.MainScreen
|
||||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||||
|
import com.tangem.tap.features.wallet.models.TotalBalance
|
||||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||||
|
import com.tangem.tap.features.wallet.ui.analytics.WalletAnalyticsEventsMapper
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.tap.walletStoresManager
|
import com.tangem.tap.walletStoresManager
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.rekotlin.StoreSubscriber
|
import org.rekotlin.StoreSubscriber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
// TODO: Kill me, please
|
// TODO: Kill me, please
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
internal class WalletViewModel : ViewModel(), StoreSubscriber<UserWalletsListManager?>, DefaultLifecycleObserver {
|
@HiltViewModel
|
||||||
|
internal class WalletViewModel @Inject constructor(
|
||||||
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
|
) : ViewModel(), StoreSubscriber<UserWalletsListManager?>, DefaultLifecycleObserver {
|
||||||
private var observeWalletStoresUpdatesJob: Job? = null
|
private var observeWalletStoresUpdatesJob: Job? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field?.cancel()
|
field?.cancel()
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val walletAnalyticsEventsMapper = WalletAnalyticsEventsMapper()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
subscribeToUserWalletsListManagerUpdates()
|
subscribeToUserWalletsListManagerUpdates()
|
||||||
}
|
}
|
||||||
|
|
@ -48,12 +59,13 @@ internal class WalletViewModel : ViewModel(), StoreSubscriber<UserWalletsListMan
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(owner: LifecycleOwner) {
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
|
launch()
|
||||||
val scanResponse = store.state.globalState.scanResponse
|
val scanResponse = store.state.globalState.scanResponse
|
||||||
if (scanResponse != null) {
|
if (scanResponse != null) {
|
||||||
val currency = ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)
|
val currency = ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)
|
||||||
val signInType = store.state.signInState.type
|
val signInType = store.state.signInState.type
|
||||||
if (currency != null && signInType != null) {
|
if (currency != null && signInType != null) {
|
||||||
Analytics.send(
|
analyticsEventHandler.send(
|
||||||
Basic.SignedIn(
|
Basic.SignedIn(
|
||||||
currency = currency,
|
currency = currency,
|
||||||
batch = scanResponse.card.batchId,
|
batch = scanResponse.card.batchId,
|
||||||
|
|
@ -65,10 +77,10 @@ internal class WalletViewModel : ViewModel(), StoreSubscriber<UserWalletsListMan
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart(owner: LifecycleOwner) {
|
override fun onStart(owner: LifecycleOwner) {
|
||||||
Analytics.send(MainScreen.ScreenOpened())
|
analyticsEventHandler.send(MainScreen.ScreenOpened())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launch() {
|
private fun launch() {
|
||||||
val manager = store.state.globalState.userWalletsListManager
|
val manager = store.state.globalState.userWalletsListManager
|
||||||
if (manager != null) {
|
if (manager != null) {
|
||||||
bootstrapSelectedWalletStoresChanges(manager)
|
bootstrapSelectedWalletStoresChanges(manager)
|
||||||
|
|
@ -76,12 +88,28 @@ internal class WalletViewModel : ViewModel(), StoreSubscriber<UserWalletsListMan
|
||||||
bootstrapShowSaveWalletIfNeeded()
|
bootstrapShowSaveWalletIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onBalanceLoaded(totalBalance: TotalBalance?) {
|
||||||
|
if (totalBalance != null) {
|
||||||
|
walletAnalyticsEventsMapper.convert(totalBalance)?.let { balanceParam ->
|
||||||
|
analyticsEventHandler.send(
|
||||||
|
Basic.BalanceLoaded(
|
||||||
|
balance = balanceParam,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) {
|
private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) {
|
||||||
observeWalletStoresUpdatesJob = manager.selectedUserWallet
|
observeWalletStoresUpdatesJob = manager.selectedUserWallet
|
||||||
.map { it.walletId }
|
.map { it.walletId }
|
||||||
.flatMapLatest { selectedUserWalletId ->
|
.flatMapLatest { selectedUserWalletId ->
|
||||||
walletStoresManager.get(selectedUserWalletId)
|
walletStoresManager.get(selectedUserWalletId)
|
||||||
}
|
}
|
||||||
|
.debounce { walletStores ->
|
||||||
|
if (walletStores.isNotEmpty()) WALLET_STORES_DEBOUNCE_TIMEOUT else 0
|
||||||
|
}
|
||||||
.onEach { walletStores ->
|
.onEach { walletStores ->
|
||||||
store.dispatch(WalletAction.WalletStoresChanged(walletStores))
|
store.dispatch(WalletAction.WalletStoresChanged(walletStores))
|
||||||
}
|
}
|
||||||
|
|
@ -104,4 +132,8 @@ internal class WalletViewModel : ViewModel(), StoreSubscriber<UserWalletsListMan
|
||||||
.select { it.globalState.userWalletsListManager }
|
.select { it.globalState.userWalletsListManager }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val WALLET_STORES_DEBOUNCE_TIMEOUT = 100L
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.tangem.tap.features.wallet.ui.analytics
|
||||||
|
|
||||||
|
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||||
|
import com.tangem.tap.common.extensions.isGreaterThan
|
||||||
|
import com.tangem.tap.features.wallet.models.TotalBalance
|
||||||
|
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
class WalletAnalyticsEventsMapper : Converter<TotalBalance, AnalyticsParam.CardBalanceState?> {
|
||||||
|
|
||||||
|
override fun convert(value: TotalBalance): AnalyticsParam.CardBalanceState? {
|
||||||
|
return when (value.state) {
|
||||||
|
ProgressState.Done -> if (value.fiatAmount?.isGreaterThan(BigDecimal.ZERO) == true) {
|
||||||
|
AnalyticsParam.CardBalanceState.Full
|
||||||
|
} else {
|
||||||
|
AnalyticsParam.CardBalanceState.Empty
|
||||||
|
}
|
||||||
|
ProgressState.Error -> {
|
||||||
|
if (value.fiatAmount == null) {
|
||||||
|
// if fiatAmount is null while ProgressState.Error it means error occurs when loading blockchain
|
||||||
|
AnalyticsParam.CardBalanceState.BlockchainError
|
||||||
|
} else {
|
||||||
|
// if fiatAmount is not null while ProgressState.Error it means couldn't load custom token amount
|
||||||
|
AnalyticsParam.CardBalanceState.CustomToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ import androidx.constraintlayout.utils.widget.ImageFilterView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.tangem.blockchain.common.DerivationStyle
|
import com.tangem.blockchain.common.DerivationStyle
|
||||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
import com.tangem.sdk.extensions.dpToPx
|
||||||
import com.tangem.tap.features.wallet.models.Currency
|
import com.tangem.tap.features.wallet.models.Currency
|
||||||
import com.tangem.wallet.databinding.ViewCurrencyIconBinding
|
import com.tangem.wallet.databinding.ViewCurrencyIconBinding
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,7 @@ class SingleWalletView : WalletView() {
|
||||||
isExchangeServiceFeatureEnabled: Boolean,
|
isExchangeServiceFeatureEnabled: Boolean,
|
||||||
) {
|
) {
|
||||||
val swapInteractor = this.swapInteractor ?: return
|
val swapInteractor = this.swapInteractor ?: return
|
||||||
|
val swapFeatureToggleManager = this.swapFeatureToggleManager ?: return
|
||||||
|
|
||||||
val exchangeManager = store.state.globalState.exchangeManager
|
val exchangeManager = store.state.globalState.exchangeManager
|
||||||
binding?.rowButtons?.apply {
|
binding?.rowButtons?.apply {
|
||||||
|
|
@ -158,7 +159,7 @@ class SingleWalletView : WalletView() {
|
||||||
val actions = walletData.getAvailableActions(
|
val actions = walletData.getAvailableActions(
|
||||||
swapInteractor = swapInteractor,
|
swapInteractor = swapInteractor,
|
||||||
exchangeManager = exchangeManager,
|
exchangeManager = exchangeManager,
|
||||||
isExchangeFeatureOn = isExchangeServiceFeatureEnabled,
|
swapFeatureToggleManager = swapFeatureToggleManager,
|
||||||
)
|
)
|
||||||
binding?.rowButtons?.updateButtonsVisibility(
|
binding?.rowButtons?.updateButtonsVisibility(
|
||||||
actions = actions,
|
actions = actions,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.tap.features.wallet.ui.wallet
|
package com.tangem.tap.features.wallet.ui.wallet
|
||||||
|
|
||||||
|
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||||
import com.tangem.feature.swap.domain.SwapInteractor
|
import com.tangem.feature.swap.domain.SwapInteractor
|
||||||
import com.tangem.tap.features.wallet.redux.WalletState
|
import com.tangem.tap.features.wallet.redux.WalletState
|
||||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||||
|
|
@ -8,6 +9,7 @@ import com.tangem.wallet.databinding.FragmentWalletBinding
|
||||||
abstract class WalletView {
|
abstract class WalletView {
|
||||||
|
|
||||||
var swapInteractor: SwapInteractor? = null
|
var swapInteractor: SwapInteractor? = null
|
||||||
|
var swapFeatureToggleManager: SwapFeatureToggleManager? = null
|
||||||
|
|
||||||
protected var fragment: WalletFragment? = null
|
protected var fragment: WalletFragment? = null
|
||||||
protected var binding: FragmentWalletBinding? = null
|
protected var binding: FragmentWalletBinding? = null
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ internal sealed interface WalletSelectorAction : Action {
|
||||||
|
|
||||||
data class SelectWallet(
|
data class SelectWallet(
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
|
val sendAnalyticsEvent: Boolean = false,
|
||||||
) : WalletSelectorAction
|
) : WalletSelectorAction
|
||||||
|
|
||||||
data class RenameWallet(
|
data class RenameWallet(
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ internal class WalletSelectorMiddleware {
|
||||||
addWallet()
|
addWallet()
|
||||||
}
|
}
|
||||||
is WalletSelectorAction.SelectWallet -> {
|
is WalletSelectorAction.SelectWallet -> {
|
||||||
selectWallet(action.userWalletId)
|
selectWallet(action.userWalletId, action.sendAnalyticsEvent)
|
||||||
}
|
}
|
||||||
is WalletSelectorAction.RemoveWallets -> {
|
is WalletSelectorAction.RemoveWallets -> {
|
||||||
deleteWallets(action.userWalletsIds, state)
|
deleteWallets(action.userWalletsIds, state)
|
||||||
|
|
@ -189,7 +189,7 @@ internal class WalletSelectorMiddleware {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun selectWallet(userWalletId: UserWalletId) {
|
private fun selectWallet(userWalletId: UserWalletId, sendAnalyticsEvent: Boolean) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
userWalletsListManager.get(userWalletId)
|
userWalletsListManager.get(userWalletId)
|
||||||
.flatMap { userWallet ->
|
.flatMap { userWallet ->
|
||||||
|
|
@ -212,7 +212,10 @@ internal class WalletSelectorMiddleware {
|
||||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
|
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
|
||||||
if (selectedUserWallet != null) {
|
if (selectedUserWallet != null) {
|
||||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||||
store.onUserWalletSelected(selectedUserWallet)
|
store.onUserWalletSelected(
|
||||||
|
userWallet = selectedUserWallet,
|
||||||
|
sendAnalyticsEvent = true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -336,7 +339,10 @@ internal class WalletSelectorMiddleware {
|
||||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
|
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
|
||||||
}
|
}
|
||||||
currentSelectedWalletId != selectedUserWallet.walletId -> {
|
currentSelectedWalletId != selectedUserWallet.walletId -> {
|
||||||
store.onUserWalletSelected(selectedUserWallet)
|
store.onUserWalletSelected(
|
||||||
|
userWallet = selectedUserWallet,
|
||||||
|
sendAnalyticsEvent = true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,9 @@ import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogCo
|
||||||
import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent
|
import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent
|
||||||
import com.tangem.tap.features.walletSelector.ui.model.DialogModel
|
import com.tangem.tap.features.walletSelector.ui.model.DialogModel
|
||||||
import com.tangem.tap.features.walletSelector.ui.model.WarningModel
|
import com.tangem.tap.features.walletSelector.ui.model.WarningModel
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<WalletSelectorScreenState>() {
|
internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<WalletSelectorScreenState>() {
|
||||||
private val viewModel by viewModels<WalletSelectorViewModel>()
|
private val viewModel by viewModels<WalletSelectorViewModel>()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,12 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
||||||
cancelWalletEditing(userWalletId)
|
cancelWalletEditing(userWalletId)
|
||||||
}
|
}
|
||||||
selectedUserWalletId != userWalletId -> {
|
selectedUserWalletId != userWalletId -> {
|
||||||
store.dispatch(WalletSelectorAction.SelectWallet(userWalletId))
|
store.dispatch(
|
||||||
|
WalletSelectorAction.SelectWallet(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
sendAnalyticsEvent = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
package com.tangem.tap.network
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.net.NetworkCapabilities
|
|
||||||
import android.os.Build
|
|
||||||
import org.rekotlin.Action
|
|
||||||
import org.rekotlin.Store
|
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
|
|
||||||
/**
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
@Deprecated("Use com.tangem.datasource.connection.NetworkConnectionManager")
|
|
||||||
class NetworkConnectivity(
|
|
||||||
private val store: Store<*>,
|
|
||||||
context: Context,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val wContext: WeakReference<Context> = WeakReference(context)
|
|
||||||
|
|
||||||
private val receiver = object : BroadcastReceiver() {
|
|
||||||
override fun onReceive(c: Context?, intent: Intent?) {
|
|
||||||
store.dispatch(NetworkStateChanged(isOnlineOrConnecting()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
val intentFilter = IntentFilter()
|
|
||||||
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
|
|
||||||
wContext.get()?.registerReceiver(receiver, intentFilter)
|
|
||||||
store.dispatch(NetworkStateChanged(isOnlineOrConnecting()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isOnlineOrConnecting(): Boolean {
|
|
||||||
val connectivityManager = getConnectivityManager() ?: return false
|
|
||||||
|
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
|
||||||
capabilities != null &&
|
|
||||||
(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
|
|
||||||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
|
|
||||||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) ||
|
|
||||||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET))
|
|
||||||
} else {
|
|
||||||
val networkInfo = connectivityManager.activeNetworkInfo
|
|
||||||
networkInfo != null && networkInfo.isConnectedOrConnecting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getConnectivityManager(): ConnectivityManager? {
|
|
||||||
return wContext.get()?.applicationContext
|
|
||||||
?.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private lateinit var instance: NetworkConnectivity
|
|
||||||
|
|
||||||
fun createInstance(store: Store<*>, context: Context): NetworkConnectivity {
|
|
||||||
instance = NetworkConnectivity(store, context)
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getInstance(): NetworkConnectivity = instance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class NetworkStateChanged(val isOnline: Boolean) : Action
|
|
||||||
|
|
@ -4,6 +4,8 @@ import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchain.common.Token
|
import com.tangem.blockchain.common.Token
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
import com.tangem.common.card.EllipticCurve
|
import com.tangem.common.card.EllipticCurve
|
||||||
|
import com.tangem.common.core.TangemError
|
||||||
|
import com.tangem.common.core.TangemSdkError
|
||||||
import com.tangem.common.extensions.ByteArrayKey
|
import com.tangem.common.extensions.ByteArrayKey
|
||||||
import com.tangem.common.extensions.toMapKey
|
import com.tangem.common.extensions.toMapKey
|
||||||
import com.tangem.common.hdWallet.DerivationPath
|
import com.tangem.common.hdWallet.DerivationPath
|
||||||
|
|
@ -13,6 +15,7 @@ import com.tangem.domain.common.extensions.fromNetworkId
|
||||||
import com.tangem.lib.crypto.DerivationManager
|
import com.tangem.lib.crypto.DerivationManager
|
||||||
import com.tangem.lib.crypto.models.Currency
|
import com.tangem.lib.crypto.models.Currency
|
||||||
import com.tangem.lib.crypto.models.Currency.NonNativeToken
|
import com.tangem.lib.crypto.models.Currency.NonNativeToken
|
||||||
|
import com.tangem.lib.crypto.models.errors.UserCancelledException
|
||||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||||
|
|
@ -49,9 +52,10 @@ class DerivationManagerImpl(
|
||||||
if (scanResponse != null) {
|
if (scanResponse != null) {
|
||||||
deriveMissingBlockchains(
|
deriveMissingBlockchains(
|
||||||
scanResponse = scanResponse,
|
scanResponse = scanResponse,
|
||||||
listOf(appCurrency),
|
currencyList = listOf(appCurrency),
|
||||||
|
onSuccess = { continuation.resumeWith(Result.success(true)) },
|
||||||
) {
|
) {
|
||||||
continuation.resumeWith(Result.success(true))
|
continuation.resumeWith(Result.failure(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -84,6 +88,7 @@ class DerivationManagerImpl(
|
||||||
scanResponse: ScanResponse,
|
scanResponse: ScanResponse,
|
||||||
currencyList: List<com.tangem.tap.features.wallet.models.Currency>,
|
currencyList: List<com.tangem.tap.features.wallet.models.Currency>,
|
||||||
onSuccess: (ScanResponse) -> Unit,
|
onSuccess: (ScanResponse) -> Unit,
|
||||||
|
onFailure: (Exception) -> Unit,
|
||||||
) {
|
) {
|
||||||
val derivationDataList = listOfNotNull(
|
val derivationDataList = listOfNotNull(
|
||||||
getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList),
|
getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList),
|
||||||
|
|
@ -135,6 +140,7 @@ class DerivationManagerImpl(
|
||||||
"Error derivation",
|
"Error derivation",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
onFailure.invoke(handleTangemError(result.error))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
error("result result is null")
|
error("result result is null")
|
||||||
|
|
@ -176,6 +182,19 @@ class DerivationManagerImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple error handler
|
||||||
|
* for now specifically handle only UserCancelled
|
||||||
|
*
|
||||||
|
* @param error [TangemError]
|
||||||
|
*/
|
||||||
|
private fun handleTangemError(error: TangemError): Exception {
|
||||||
|
if (error is TangemSdkError.UserCancelled) {
|
||||||
|
return UserCancelledException()
|
||||||
|
}
|
||||||
|
return IllegalStateException(error.customMessage)
|
||||||
|
}
|
||||||
|
|
||||||
private class DerivationData(
|
private class DerivationData(
|
||||||
val derivations: Pair<ByteArrayKey, List<DerivationPath>>,
|
val derivations: Pair<ByteArrayKey, List<DerivationPath>>,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import com.tangem.Message
|
import com.tangem.Message
|
||||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||||
|
import com.tangem.blockchain.blockchains.optimism.OptimismWalletManager
|
||||||
import com.tangem.blockchain.common.Amount
|
import com.tangem.blockchain.common.Amount
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchain.common.BlockchainSdkError
|
import com.tangem.blockchain.common.BlockchainSdkError
|
||||||
|
|
@ -155,56 +156,31 @@ class TransactionManagerImpl(
|
||||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||||
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
||||||
if (walletManager is EthereumWalletManager) {
|
if (walletManager is EthereumWalletManager) {
|
||||||
val gasLimit = getGasLimit(
|
if (walletManager is OptimismWalletManager) {
|
||||||
evmWalletManager = walletManager,
|
return getFeeForOptimismBlockchain(
|
||||||
|
walletManager = walletManager,
|
||||||
|
amount = createAmount(amountToSend, currencyToSend, blockchain),
|
||||||
|
destinationAddress = destinationAddress,
|
||||||
|
data = data,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return getFeeForEthereumBlockchain(
|
||||||
|
walletManager = walletManager,
|
||||||
blockchain = blockchain,
|
blockchain = blockchain,
|
||||||
amount = amountToSend,
|
amountToSend = amountToSend,
|
||||||
currency = currencyToSend,
|
currency = currencyToSend,
|
||||||
destinationAddress = destinationAddress,
|
destinationAddress = destinationAddress,
|
||||||
data = data,
|
data = data,
|
||||||
).let {
|
increaseBy = increaseBy,
|
||||||
if (increaseBy != null && increaseBy != 0) {
|
)
|
||||||
it.multiply(increaseBy.toBigInteger()).divide(BigInteger("100"))
|
|
||||||
} else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return when (val gasPrice = walletManager.getGasPrice()) {
|
|
||||||
is Result.Success -> {
|
|
||||||
val fee = gasLimit.multiply(gasPrice.data).toBigDecimal(
|
|
||||||
scale = blockchain.decimals(),
|
|
||||||
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
|
||||||
)
|
|
||||||
ProxyFee(
|
|
||||||
gasLimit = gasLimit,
|
|
||||||
fee = ProxyAmount(
|
|
||||||
currencySymbol = blockchain.currency,
|
|
||||||
value = fee,
|
|
||||||
decimals = blockchain.decimals(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is Result.Failure -> {
|
|
||||||
error(gasPrice.error.message ?: gasPrice.error.customMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
val fee = (walletManager as? TransactionSender)?.getFee(
|
return getFeeForBlockchain(
|
||||||
amount = createAmount(amountToSend, currencyToSend, blockchain),
|
walletManager = walletManager,
|
||||||
destination = destinationAddress,
|
amountToSend = amountToSend,
|
||||||
) ?: error("Cannot cast to TransactionSender")
|
currency = currencyToSend,
|
||||||
when (fee) {
|
blockchain = blockchain,
|
||||||
is Result.Success -> {
|
destinationAddress = destinationAddress,
|
||||||
// for not EVM blockchains set gasLimit ZERO for now
|
)
|
||||||
return ProxyFee(
|
|
||||||
gasLimit = BigInteger.ZERO,
|
|
||||||
fee = convertToProxyAmount(fee.data.firstOrNull() ?: error("no fee found")),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is Result.Failure -> {
|
|
||||||
error(fee.error.message ?: fee.error.customMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,6 +193,100 @@ class TransactionManagerImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun getFeeForBlockchain(
|
||||||
|
walletManager: WalletManager,
|
||||||
|
amountToSend: BigDecimal,
|
||||||
|
currency: Currency,
|
||||||
|
blockchain: Blockchain,
|
||||||
|
destinationAddress: String,
|
||||||
|
): ProxyFee {
|
||||||
|
val fee = (walletManager as? TransactionSender)?.getFee(
|
||||||
|
amount = createAmount(amountToSend, currency, blockchain),
|
||||||
|
destination = destinationAddress,
|
||||||
|
) ?: error("Cannot cast to TransactionSender")
|
||||||
|
return when (fee) {
|
||||||
|
is Result.Success -> {
|
||||||
|
// for not EVM blockchains set gasLimit ZERO for now
|
||||||
|
ProxyFee(
|
||||||
|
gasLimit = BigInteger.ZERO,
|
||||||
|
fee = convertToProxyAmount(fee.data.firstOrNull() ?: error("no fee found")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Result.Failure -> {
|
||||||
|
error(fee.error.message ?: fee.error.customMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
|
private suspend fun getFeeForEthereumBlockchain(
|
||||||
|
walletManager: EthereumWalletManager,
|
||||||
|
blockchain: Blockchain,
|
||||||
|
amountToSend: BigDecimal,
|
||||||
|
currency: Currency,
|
||||||
|
destinationAddress: String,
|
||||||
|
data: String?,
|
||||||
|
increaseBy: Int?,
|
||||||
|
): ProxyFee {
|
||||||
|
val gasLimit = getGasLimit(
|
||||||
|
evmWalletManager = walletManager,
|
||||||
|
blockchain = blockchain,
|
||||||
|
amount = amountToSend,
|
||||||
|
currency = currency,
|
||||||
|
destinationAddress = destinationAddress,
|
||||||
|
data = data,
|
||||||
|
).let {
|
||||||
|
if (increaseBy != null && increaseBy != 0) {
|
||||||
|
it.multiply(increaseBy.toBigInteger()).divide(BigInteger("100"))
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return when (val gasPrice = walletManager.getGasPrice()) {
|
||||||
|
is Result.Success -> {
|
||||||
|
val fee = gasLimit.multiply(gasPrice.data).toBigDecimal(
|
||||||
|
scale = blockchain.decimals(),
|
||||||
|
mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
|
||||||
|
)
|
||||||
|
ProxyFee(
|
||||||
|
gasLimit = gasLimit,
|
||||||
|
fee = ProxyAmount(
|
||||||
|
currencySymbol = blockchain.currency,
|
||||||
|
value = fee,
|
||||||
|
decimals = blockchain.decimals(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Result.Failure -> {
|
||||||
|
error(gasPrice.error.message ?: gasPrice.error.customMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getFeeForOptimismBlockchain(
|
||||||
|
walletManager: OptimismWalletManager,
|
||||||
|
amount: Amount,
|
||||||
|
destinationAddress: String,
|
||||||
|
data: String?,
|
||||||
|
): ProxyFee {
|
||||||
|
val fee = if (data.isNullOrEmpty()) {
|
||||||
|
walletManager.getFee(amount, destinationAddress)
|
||||||
|
} else {
|
||||||
|
walletManager.getFee(amount, destinationAddress, data)
|
||||||
|
}
|
||||||
|
when (fee) {
|
||||||
|
is Result.Success -> {
|
||||||
|
return ProxyFee(
|
||||||
|
gasLimit = walletManager.gasLimit ?: BigInteger.ZERO,
|
||||||
|
fee = convertToProxyAmount(fee.data.lastOrNull() ?: error("no fee found")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Result.Failure -> {
|
||||||
|
error(fee.error.message ?: fee.error.customMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
private suspend fun getGasLimit(
|
private suspend fun getGasLimit(
|
||||||
evmWalletManager: EthereumWalletManager,
|
evmWalletManager: EthereumWalletManager,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.AmountType
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchain.common.Token
|
import com.tangem.blockchain.common.Token
|
||||||
import com.tangem.blockchain.common.WalletManager
|
import com.tangem.blockchain.common.WalletManager
|
||||||
|
import com.tangem.common.doOnFailure
|
||||||
import com.tangem.common.extensions.guard
|
import com.tangem.common.extensions.guard
|
||||||
import com.tangem.domain.common.extensions.fromNetworkId
|
import com.tangem.domain.common.extensions.fromNetworkId
|
||||||
import com.tangem.domain.common.extensions.toCoinId
|
import com.tangem.domain.common.extensions.toCoinId
|
||||||
|
|
@ -20,6 +21,8 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||||
import com.tangem.tap.userWalletsListManager
|
import com.tangem.tap.userWalletsListManager
|
||||||
import com.tangem.tap.walletCurrenciesManager
|
import com.tangem.tap.walletCurrenciesManager
|
||||||
|
import com.tangem.tap.walletStoresManager
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import com.tangem.tap.features.wallet.models.Currency as WalletCurrency
|
import com.tangem.tap.features.wallet.models.Currency as WalletCurrency
|
||||||
|
|
@ -117,6 +120,28 @@ class UserWalletManagerImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun hideAllTokens() {
|
||||||
|
val userWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||||
|
Timber.e("No user wallets selected")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val currencies = walletStoresManager.get(userWallet.walletId)
|
||||||
|
.firstOrNull()
|
||||||
|
?.flatMap { walletStore ->
|
||||||
|
walletStore.walletsData.map { it.currency }
|
||||||
|
}
|
||||||
|
.guard {
|
||||||
|
Timber.d("No currencies found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
walletCurrenciesManager.removeCurrencies(userWallet, currenciesToRemove = currencies)
|
||||||
|
.doOnFailure { e ->
|
||||||
|
Timber.e(e, "Unable to delete all currencies")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getWalletAddress(networkId: String, derivationPath: String?): String {
|
override fun getWalletAddress(networkId: String, derivationPath: String?): String {
|
||||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||||
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.tangem.tap.proxy.redux
|
||||||
|
|
||||||
|
import com.tangem.datasource.asset.AssetReader
|
||||||
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
|
import com.tangem.features.tester.api.TesterRouter
|
||||||
|
import org.rekotlin.Action
|
||||||
|
|
||||||
|
sealed interface DaggerGraphAction : Action {
|
||||||
|
|
||||||
|
data class SetApplicationDependencies(
|
||||||
|
val assetReader: AssetReader,
|
||||||
|
val networkConnectionManager: NetworkConnectionManager,
|
||||||
|
) : DaggerGraphAction
|
||||||
|
|
||||||
|
data class SetActivityDependencies(val testerRouter: TesterRouter) : DaggerGraphAction
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.tangem.tap.proxy.redux
|
||||||
|
|
||||||
|
import com.tangem.tap.common.redux.AppState
|
||||||
|
import org.rekotlin.Middleware
|
||||||
|
|
||||||
|
object DaggerGraphMiddleware {
|
||||||
|
val daggerGraphMiddleware: Middleware<AppState> = { _, _ ->
|
||||||
|
{ next ->
|
||||||
|
{ action -> next(action) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.tangem.tap.proxy.redux
|
||||||
|
|
||||||
|
import com.tangem.tap.common.redux.AppState
|
||||||
|
import org.rekotlin.Action
|
||||||
|
|
||||||
|
object DaggerGraphReducer {
|
||||||
|
fun reduce(action: Action, state: AppState): DaggerGraphState {
|
||||||
|
if (action !is DaggerGraphAction) return state.daggerGraphState
|
||||||
|
|
||||||
|
return internalReduce(action, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun internalReduce(action: DaggerGraphAction, state: AppState): DaggerGraphState {
|
||||||
|
return when (action) {
|
||||||
|
is DaggerGraphAction.SetApplicationDependencies -> state.daggerGraphState.copy(
|
||||||
|
assetReader = action.assetReader,
|
||||||
|
networkConnectionManager = action.networkConnectionManager,
|
||||||
|
)
|
||||||
|
is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy(
|
||||||
|
testerRouter = action.testerRouter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.tangem.tap.proxy.redux
|
||||||
|
|
||||||
|
import com.tangem.datasource.asset.AssetReader
|
||||||
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
|
import com.tangem.features.tester.api.TesterRouter
|
||||||
|
import org.rekotlin.StateType
|
||||||
|
|
||||||
|
data class DaggerGraphState(
|
||||||
|
val assetReader: AssetReader? = null,
|
||||||
|
val testerRouter: TesterRouter? = null,
|
||||||
|
val networkConnectionManager: NetworkConnectionManager? = null,
|
||||||
|
) : StateType {
|
||||||
|
|
||||||
|
inline fun <reified T> get(getDependency: DaggerGraphState.() -> T?): T {
|
||||||
|
return requireNotNull(getDependency()) {
|
||||||
|
"${T::class.simpleName} isn't initialized "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
app/src/main/res/drawable/ic_kaspa_no_color.xml
Normal file
12
app/src/main/res/drawable/ic_kaspa_no_color.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="22dp"
|
||||||
|
android:height="22dp"
|
||||||
|
android:viewportWidth="22"
|
||||||
|
android:viewportHeight="22">
|
||||||
|
<path
|
||||||
|
android:fillColor="#EBEBEB"
|
||||||
|
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#C9C9C9"
|
||||||
|
android:pathData="M13.659,5.35L11.564,5.663L12.174,9.721L7.787,6.34L6.5,8.022L10.343,10.975L6.5,13.944L7.787,15.626L12.174,12.245L11.564,16.303L13.659,16.616L14.5,10.975L13.659,5.35Z" />
|
||||||
|
</vector>
|
||||||
6
app/src/main/res/values/strings.xml
Normal file
6
app/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="tangem_app_name" translatable="false">Tangem</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue