Updated on 2026-08-14
This commit is contained in:
commit
0625a92045
199 changed files with 3829 additions and 2255 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -32,4 +32,3 @@ local.properties
|
|||
**/fastlane/Preview.html
|
||||
**/fastlane/screenshots
|
||||
**/fastlane/test_output
|
||||
/buildSrc/build/
|
||||
|
|
|
|||
|
|
@ -1,117 +1,20 @@
|
|||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
kotlin("plugin.serialization")
|
||||
id("com.google.gms.google-services")
|
||||
id("com.google.firebase.crashlytics")
|
||||
id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId = AppConfig.packageName
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
|
||||
versionCode = if (project.hasProperty("versionCode")) {
|
||||
(project.property("versionCode") as String).toInt()
|
||||
} else {
|
||||
AppConfig.versionCode
|
||||
}
|
||||
|
||||
versionName = if (project.hasProperty("versionName")) {
|
||||
project.property("versionName") as String
|
||||
} else {
|
||||
AppConfig.versionName
|
||||
}
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isDebuggable = false
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
|
||||
BuildConfigFieldFactory(
|
||||
fields = listOf(
|
||||
Field.Environment("prod"),
|
||||
Field.TestActionEnabled(false),
|
||||
Field.LogEnabled(false),
|
||||
),
|
||||
builder = ::buildConfigField,
|
||||
).create()
|
||||
}
|
||||
|
||||
debug {
|
||||
isDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
applicationIdSuffix = ".dev"
|
||||
|
||||
configure<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")
|
||||
alias(deps.plugins.android.application)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.google.services)
|
||||
alias(deps.plugins.hilt.android)
|
||||
alias(deps.plugins.firebase.crashlytics)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
|
||||
implementation(files("libs/walletconnect-1.5.6.aar"))
|
||||
implementation(project(":domain"))
|
||||
implementation(project(":common"))
|
||||
implementation(project(":core:analytics"))
|
||||
implementation(project(":core:featuretoggles"))
|
||||
implementation(project(":core:res"))
|
||||
implementation(project(":core:ui"))
|
||||
implementation(project(":core:datasource"))
|
||||
|
|
@ -123,91 +26,93 @@ dependencies {
|
|||
implementation(project(":features:referral:presentation"))
|
||||
implementation(project(":features:referral:domain"))
|
||||
implementation(project(":features:referral:data"))
|
||||
implementation(project(":features:swap:api"))
|
||||
implementation(project(":features:swap:presentation"))
|
||||
implementation(project(":features:swap:domain"))
|
||||
implementation(project(":features:swap:data"))
|
||||
implementation(project(":features:tester:api"))
|
||||
implementation(project(":features:tester:impl"))
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(AndroidX.coreKtx)
|
||||
implementation(AndroidX.appCompat)
|
||||
implementation(AndroidX.fragmentKtx)
|
||||
implementation(AndroidX.constraintLayout)
|
||||
implementation(AndroidX.browser)
|
||||
implementation(AndroidX.lifecycleRuntimeKtx)
|
||||
implementation(AndroidX.lifecycleCommonJava8)
|
||||
implementation(AndroidX.lifecycleViewModelKtx)
|
||||
implementation(AndroidX.lifecycleLiveDataKtx)
|
||||
implementation(AndroidX.activityCompose)
|
||||
implementation(deps.androidx.core.ktx)
|
||||
implementation(deps.androidx.appCompat)
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
implementation(deps.androidx.constraintLayout)
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.androidx.browser)
|
||||
implementation(deps.lifecycle.runtime.ktx)
|
||||
implementation(deps.lifecycle.common.java8)
|
||||
implementation(deps.lifecycle.viewModel.ktx)
|
||||
|
||||
/** Compose libraries */
|
||||
implementation(Compose.material)
|
||||
implementation(Compose.animation)
|
||||
implementation(Compose.foundation)
|
||||
implementation(Compose.ui)
|
||||
implementation(Compose.uiTooling)
|
||||
implementation(Compose.coil)
|
||||
implementation(deps.compose.material)
|
||||
implementation(deps.compose.animation)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.shimmer)
|
||||
|
||||
/** Firebase libraries */
|
||||
implementation(platform(Firebase.bom))
|
||||
implementation(Firebase.firebaseAnalytics)
|
||||
implementation(Firebase.firebaseCrashlytics)
|
||||
implementation(platform(deps.firebase.bom))
|
||||
implementation(deps.firebase.analytics)
|
||||
implementation(deps.firebase.crashlytics)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(Tangem.blockchain) {
|
||||
implementation(deps.tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
implementation(Tangem.cardCore)
|
||||
implementation(Tangem.cardAndroid) {
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.card.android) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(Library.materialComponent)
|
||||
implementation(Library.googlePlayCore)
|
||||
implementation(Library.googlePlayCoreKtx)
|
||||
coreLibraryDesugaring(Library.desugarJdkLibs)
|
||||
implementation(Library.timber)
|
||||
implementation(Library.reKotlin)
|
||||
implementation(Library.zxingQrCore)
|
||||
implementation(Library.zxingQrBarcodeScanner)
|
||||
implementation(Library.otaliastudiosCameraView)
|
||||
implementation(Library.coil)
|
||||
implementation(Library.appsflyer)
|
||||
implementation(Library.amplitude)
|
||||
implementation(Library.kotsonGsonExt)
|
||||
implementation(deps.material)
|
||||
implementation(deps.googlePlay.core)
|
||||
implementation(deps.googlePlay.core.ktx)
|
||||
implementation(deps.googlePlay.services.wallet)
|
||||
coreLibraryDesugaring(deps.desugar)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.reKotlin)
|
||||
implementation(deps.zxing.qrCore)
|
||||
implementation(deps.zxing.qrBarcodeScanner)
|
||||
implementation(deps.otaliastudiosCameraView)
|
||||
implementation(deps.coil)
|
||||
implementation(deps.appsflyer)
|
||||
implementation(deps.amplitude)
|
||||
implementation(deps.kotsonGson)
|
||||
//TODO: refactoring: remove it when all network services moved to the datasource module
|
||||
implementation(Library.retrofit)
|
||||
implementation(Library.retrofitMoshiConverter)
|
||||
implementation(Library.moshi)
|
||||
implementation(Library.moshiKotlin)
|
||||
implementation(Library.okHttp)
|
||||
implementation(Library.okHttpLogging)
|
||||
implementation(Library.zendeskChat)
|
||||
implementation(Library.zendeskMessaging)
|
||||
implementation(Library.spongecastleCryptoCore)
|
||||
implementation(Library.lottie)
|
||||
implementation(Library.shopifyBuySdk) {
|
||||
implementation(deps.retrofit)
|
||||
implementation(deps.retrofit.moshi)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.okHttp)
|
||||
implementation(deps.okHttp.logging)
|
||||
implementation(deps.zendesk.chat)
|
||||
implementation(deps.zendesk.messaging)
|
||||
implementation(deps.spongecastle.core)
|
||||
implementation(deps.lottie)
|
||||
implementation(deps.shopify.buy) {
|
||||
exclude(group = "com.shopify.graphql.support")
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
implementation(Library.accompanistAppCompatTheme)
|
||||
implementation(Library.accompanistSystemUiController)
|
||||
implementation(Library.accompanistWebView)
|
||||
implementation(Library.xmlShimmer)
|
||||
implementation(Library.viewBindingDelegate)
|
||||
implementation(Library.armadillo)
|
||||
implementation(Library.googlePlayServicesWallet)
|
||||
implementation(Library.composeShimmer)
|
||||
implementation(Library.mviCoreWatcher)
|
||||
implementation(Library.kotlinSerialization)
|
||||
implementation(deps.compose.accompanist.appCompatTheme)
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.accompanist.webView)
|
||||
implementation(deps.xmlShimmer)
|
||||
implementation(deps.viewBindingDelegate)
|
||||
implementation(deps.armadillo)
|
||||
implementation(deps.mviCore.watcher)
|
||||
implementation(deps.kotlin.serialization)
|
||||
|
||||
/** Testing libraries */
|
||||
testImplementation(Test.junit)
|
||||
testImplementation(Test.truth)
|
||||
androidTestImplementation(Test.junitAndroidExt)
|
||||
androidTestImplementation(Test.espresso)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.truth)
|
||||
androidTestImplementation(deps.test.junit.android)
|
||||
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 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="tangem_app_name">DebugTangem</string>
|
||||
<string name="tangem_app_name" translatable="false">DebugTangem</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-ru/strings.xml
vendored
Normal file
6
app/src/external/res/values-ru/strings.xml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="tangem_app_name" translatable="false">ExternalTangem</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">ExternalTangem</string>
|
||||
|
||||
</resources>
|
||||
6
app/src/internal/res/values-ru/strings.xml
Normal file
6
app/src/internal/res/values-ru/strings.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="tangem_app_name" translatable="false">InternalTangem</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">InternalTangem</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
|
||||
|
|
@ -10,9 +10,10 @@ import androidx.core.view.WindowInsetsControllerCompat
|
|||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tangem_sdk_new.extensions.initWithBiometrics
|
||||
import com.tangem.sdk.extensions.init
|
||||
import com.tangem.sdk.extensions.initWithBiometrics
|
||||
import com.tangem.tap.common.ActivityResultCallbackHolder
|
||||
import com.tangem.tap.common.DialogManager
|
||||
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.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ActivityMainBinding
|
||||
|
|
@ -71,6 +73,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
@Inject
|
||||
lateinit var appStateHolder: AppStateHolder
|
||||
|
||||
/** Router for opening tester menu */
|
||||
@Inject
|
||||
lateinit var testerRouter: TesterRouter
|
||||
|
||||
private var snackbar: Snackbar? = null
|
||||
private val dialogManager = DialogManager()
|
||||
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
|
||||
|
|
@ -97,6 +103,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
GooglePayService(createPaymentsClient(this), this),
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatch(DaggerGraphAction.SetActivityDependencies(testerRouter))
|
||||
}
|
||||
|
||||
private fun initUserWalletsListManager() {
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ import com.tangem.blockchain.common.BlockchainSdkConfig
|
|||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.config.FeaturesLocalLoader
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.utils.AndroidAssetReader
|
||||
import com.tangem.datasource.utils.AssetReader
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.domain.DomainLayer
|
||||
import com.tangem.domain.common.LogConfig
|
||||
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.di.provideDefaultImplementation
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
|
@ -65,7 +67,6 @@ lateinit var activityResultCaller: ActivityResultCaller
|
|||
lateinit var preferencesStorage: PreferencesStorage
|
||||
lateinit var walletConnectRepository: WalletConnectRepository
|
||||
lateinit var shopService: TangemShopService
|
||||
lateinit var assetReader: AssetReader
|
||||
lateinit var userTokensRepository: UserTokensRepository
|
||||
|
||||
private val walletStoresRepository by lazy { WalletStoresRepository.provideDefaultImplementation() }
|
||||
|
|
@ -116,6 +117,15 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
@Inject
|
||||
lateinit var configManager: ConfigManager
|
||||
|
||||
@Inject
|
||||
lateinit var assetReader: AssetReader
|
||||
|
||||
@Inject
|
||||
lateinit var featureTogglesManager: FeatureTogglesManager
|
||||
|
||||
@Inject
|
||||
lateinit var networkConnectionManager: NetworkConnectionManager
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
|
@ -136,11 +146,9 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
|
||||
|
||||
DomainLayer.init()
|
||||
NetworkConnectivity.createInstance(store, this)
|
||||
preferencesStorage = PreferencesStorage(this)
|
||||
walletConnectRepository = WalletConnectRepository(this)
|
||||
|
||||
assetReader = AndroidAssetReader(this)
|
||||
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
|
||||
initConfigManager(configLoader, ::initWithConfigDependency)
|
||||
initWarningMessagesManager()
|
||||
|
|
@ -154,10 +162,23 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
userTokensRepository = UserTokensRepository.init(
|
||||
context = this,
|
||||
tangemTechService = store.state.domainNetworks.tangemTechService,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
)
|
||||
appStateHolder.mainStore = store
|
||||
appStateHolder.userTokensRepository = userTokensRepository
|
||||
appStateHolder.walletStoresManager = walletStoresManager
|
||||
|
||||
store.dispatch(
|
||||
action = DaggerGraphAction.SetApplicationDependencies(
|
||||
assetReader = assetReader,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
),
|
||||
)
|
||||
|
||||
scope.launch {
|
||||
featureTogglesManager.init()
|
||||
}
|
||||
|
||||
initTopUpController()
|
||||
}
|
||||
|
||||
|
|
@ -217,6 +238,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
) {
|
||||
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply {
|
||||
appVersion = try {
|
||||
// TODO don't use deprecated method
|
||||
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
pInfo.versionName
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ sealed class AnalyticsParam {
|
|||
sealed class CardBalanceState(val value: String) {
|
||||
object Empty : CardBalanceState("Empty")
|
||||
object Full : CardBalanceState("Full")
|
||||
object CustomToken : CardBalanceState("Custom token")
|
||||
object BlockchainError : CardBalanceState("Blockchain error")
|
||||
companion object
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +81,7 @@ sealed class AnalyticsParam {
|
|||
|
||||
companion object Key {
|
||||
const val Source = "Source"
|
||||
const val Balance = "Balance"
|
||||
const val Batch = "Batch"
|
||||
const val ProductType = "Product Type"
|
||||
const val Firmware = "Firmware"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,13 @@ sealed class Basic(
|
|||
error: Throwable? = null,
|
||||
) : AnalyticsEvent("Basic", event, params, error) {
|
||||
|
||||
class BalanceLoaded(balance: AnalyticsParam.CardBalanceState) : Basic(
|
||||
event = "Balance Loaded",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Balance to balance.value,
|
||||
),
|
||||
)
|
||||
|
||||
class CardWasScanned(
|
||||
source: AnalyticsParam.ScannedFrom,
|
||||
) : Basic(
|
||||
|
|
@ -51,4 +58,6 @@ sealed class Basic(
|
|||
event = "Scan",
|
||||
error = error,
|
||||
)
|
||||
|
||||
class WalletOpened : Basic(event = "Wallet Opened")
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ sealed class MainScreen(
|
|||
) : AnalyticsEvent("Main Screen", event, params) {
|
||||
|
||||
class ScreenOpened : MainScreen("Screen opened")
|
||||
|
||||
class ButtonScanCard : MainScreen("Button - Scan Card")
|
||||
class ButtonMyWallets : MainScreen("Button - My Wallets")
|
||||
class ButtonBuy : MainScreen("Button - Buy")
|
||||
|
|
|
|||
|
|
@ -71,16 +71,16 @@ sealed class Onboarding(
|
|||
class ButtonSetPinCode : Onboarding("Onboarding", "Button - Set PIN Code")
|
||||
class CardConnectionScreenOpened : Onboarding("Onboarding", "Card Connection Screen Opened")
|
||||
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 KYCStarted : Onboarding("Onboarding", "KYC started")
|
||||
class KYCInProgress : Onboarding("Onboarding", "KYC in progress")
|
||||
class KYCRejected : Onboarding("Onboarding", "KYC rejected")
|
||||
class KYCStarted : Onboarding("Onboarding", "KYC Started")
|
||||
class KYCInProgress : Onboarding("Onboarding", "KYC In Progress")
|
||||
class KYCRejected : Onboarding("Onboarding", "KYC Rejected")
|
||||
|
||||
class ClaimScreenOpened : Onboarding("Onboarding", "Claim screen opened")
|
||||
class ClaimScreenOpened : Onboarding("Onboarding", "Claim Screen Opened")
|
||||
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")
|
||||
|
||||
|
|
|
|||
|
|
@ -60,16 +60,16 @@ sealed class Settings(
|
|||
params: Map<String, String> = mapOf(),
|
||||
) : 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",
|
||||
params = mapOf("State" to state.value),
|
||||
)
|
||||
|
||||
class SaveAccessCodeSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings(
|
||||
class SaveAccessCodeSwitcherChanged(state: AnalyticsParam.OnOffState) : AppSettings(
|
||||
event = "Save Access Code Switcher Changed",
|
||||
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(
|
||||
category = "Token",
|
||||
event = "Token bought",
|
||||
event = "Token Bought",
|
||||
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.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
||||
import com.tangem.tangem_sdk_new.extensions.pxToDp
|
||||
import com.tangem.sdk.extensions.dpToPx
|
||||
import com.tangem.sdk.extensions.pxToDp
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
|
|||
Blockchain.Kusama -> R.drawable.ic_kusama_no_color
|
||||
Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism_no_color
|
||||
Blockchain.Dash -> R.drawable.ic_dash_no_color
|
||||
Blockchain.Kaspa -> R.drawable.ic_kaspa_no_color
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,12 @@ fun Store<*>.dispatchNotification(resId: Int) {
|
|||
dispatchOnMain(GlobalAction.ShowNotification(resId))
|
||||
}
|
||||
|
||||
suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) {
|
||||
state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh)
|
||||
suspend fun Store<AppState>.onUserWalletSelected(
|
||||
userWallet: UserWallet,
|
||||
refresh: Boolean = false,
|
||||
sendAnalyticsEvent: Boolean = false,
|
||||
) {
|
||||
state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh, sendAnalyticsEvent)
|
||||
}
|
||||
|
||||
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.redux.AddressData
|
||||
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.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.delay
|
||||
import timber.log.Timber
|
||||
|
|
@ -47,7 +47,8 @@ suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
|
|||
} catch (exception: Exception) {
|
||||
Timber.e(exception)
|
||||
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
||||
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||
if (!networkConnectionManager.isOnline) {
|
||||
Result.Failure(TapError.NoInternetConnection)
|
||||
} else {
|
||||
val blockchain = wallet.blockchain
|
||||
|
|
@ -87,8 +88,6 @@ fun WalletManager?.getAddressData(): AddressData? {
|
|||
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.getTokenTxHistory(token: Token): List<TransactionData> = wallet.getTokenTxHistory(token)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import android.widget.FrameLayout
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidgetState
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidgetState
|
||||
import com.tangem.tap.domain.twins.TwinsCardWidget
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.TestLeapfrogFragmentBinding
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.common.recyclerView
|
|||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tangem_sdk_new.extensions.dpToPx
|
||||
import com.tangem.sdk.extensions.dpToPx
|
||||
|
||||
class SpaceItemDecoration(
|
||||
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.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
import org.rekotlin.Action
|
||||
|
||||
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),
|
||||
sprinklrState = SprinklrReducer.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.welcome.redux.WelcomeMiddleware
|
||||
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 org.rekotlin.Middleware
|
||||
import org.rekotlin.StateType
|
||||
|
|
@ -66,7 +68,8 @@ data class AppState(
|
|||
val saveWalletState: SaveWalletState = SaveWalletState(),
|
||||
val walletSelectorState: WalletSelectorState = WalletSelectorState(),
|
||||
val sprinklrState: SprinklrState = SprinklrState(),
|
||||
val signInState: SignInState = SignInState()
|
||||
val signInState: SignInState = SignInState(),
|
||||
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
|
||||
) : StateType {
|
||||
|
||||
val domainState: DomainState
|
||||
|
|
@ -108,7 +111,8 @@ data class AppState(
|
|||
LockUserWalletsTimerMiddleware().middleware,
|
||||
AccessCodeRequestPolicyMiddleware().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.extensions.withMainContext
|
||||
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.setContext
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -36,8 +37,11 @@ class TapWalletManager {
|
|||
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)
|
||||
if (sendAnalyticsEvent) {
|
||||
Analytics.send(Basic.WalletOpened())
|
||||
}
|
||||
val scanResponse = userWallet.scanResponse
|
||||
val card = scanResponse.card
|
||||
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.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.datasource.utils.AssetReader
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ class LoadAvailableCoinsService(
|
|||
return withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
tangemTechApi.getCoins(
|
||||
networkIds = supportedBlockchains.toSet().map(Blockchain::toNetworkId).joinToString(","),
|
||||
networkIds = supportedBlockchains.toSet().joinToString(",", transform = Blockchain::toNetworkId),
|
||||
active = true,
|
||||
searchText = searchInput,
|
||||
offset = offset,
|
||||
|
|
@ -74,8 +74,8 @@ class LoadAvailableCoinsService(
|
|||
}
|
||||
}
|
||||
|
||||
fun getTestnetCoins(): List<Currency> {
|
||||
val json = assetReader.readAssetAsString(FILE_NAME_TESTNET_COINS)
|
||||
private fun getTestnetCoins(): List<Currency> {
|
||||
val json = assetReader.readJson(FILE_NAME_TESTNET_COINS)
|
||||
return currenciesAdapter.fromJson(json)!!.coins
|
||||
.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.TangemTechService
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.tap.common.AndroidFileReader
|
||||
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.toBlockchainNetworks
|
||||
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.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -25,33 +25,31 @@ class UserTokensRepository(
|
|||
private val storageService: UserTokensStorageService,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
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) {
|
||||
val userId = getUserWalletId(card) ?: return@withContext emptyList()
|
||||
val userWalletId = getUserWalletId(card) ?: return@withContext emptyList()
|
||||
|
||||
if (DemoHelper.isDemoCardId(card.cardId)) {
|
||||
return@withContext loadTokensOffline(card, userId).ifEmpty(::loadDemoCurrencies)
|
||||
return@withContext loadTokensOffline(userWalletId = userWalletId).ifEmpty(::loadDemoCurrencies)
|
||||
}
|
||||
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
||||
return@withContext loadTokensOffline(card, userId)
|
||||
}
|
||||
if (!networkConnectionManager.isOnline) return@withContext loadTokensOffline(userWalletId = userWalletId)
|
||||
|
||||
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) {
|
||||
val userId = getUserWalletId(card) ?: return@withContext
|
||||
val userWalletId = getUserWalletId(card) ?: return@withContext
|
||||
val userTokens = tokens.toUserTokensResponse()
|
||||
remoteSaveUserTokens(userId, userTokens)
|
||||
storageService.saveUserTokens(userId, userTokens)
|
||||
remoteSaveUserTokens(userWalletId = userWalletId, userTokens = userTokens)
|
||||
storageService.saveUserTokens(userWalletId = userWalletId, tokens = userTokens)
|
||||
}
|
||||
|
||||
suspend fun loadBlockchainsToDerive(card: CardDTO): List<BlockchainNetwork> = withContext(dispatchers.io) {
|
||||
val userId = getUserWalletId(card) ?: return@withContext emptyList()
|
||||
val blockchainNetworks = loadTokensOffline(card = card, userId = userId).toBlockchainNetworks()
|
||||
val userWalletId = getUserWalletId(card) ?: return@withContext emptyList()
|
||||
val blockchainNetworks = loadTokensOffline(userWalletId = userWalletId).toBlockchainNetworks()
|
||||
|
||||
if (DemoHelper.isDemoCardId(card.cardId)) {
|
||||
return@withContext blockchainNetworks.ifEmpty(loadDemoCurrencies()::toBlockchainNetworks)
|
||||
|
|
@ -60,8 +58,8 @@ class UserTokensRepository(
|
|||
return@withContext blockchainNetworks
|
||||
}
|
||||
|
||||
private suspend fun loadTokensOffline(card: CardDTO, userId: String): List<Currency> {
|
||||
return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card)
|
||||
private fun loadTokensOffline(userWalletId: String): List<Currency> {
|
||||
return storageService.getUserTokens(userWalletId = userWalletId) ?: emptyList()
|
||||
}
|
||||
|
||||
private fun loadDemoCurrencies(): List<Currency> {
|
||||
|
|
@ -76,54 +74,49 @@ class UserTokensRepository(
|
|||
.flatMap(BlockchainNetwork::toCurrencies)
|
||||
}
|
||||
|
||||
private fun List<Currency>.toUserTokensResponse() = UserTokensResponse(
|
||||
tokens = CurrencyConverter.convertList(this),
|
||||
group = GROUP_DEFAULT_VALUE,
|
||||
sort = SORT_DEFAULT_VALUE,
|
||||
)
|
||||
private fun List<Currency>.toUserTokensResponse(): UserTokensResponse {
|
||||
return UserTokensResponse(
|
||||
tokens = CurrencyConverter.convertList(input = this),
|
||||
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 {
|
||||
error is TangemSdkError.NetworkError && error.customMessage.contains(NOT_FOUND_HTTP_CODE) ->
|
||||
storageService.getUserTokens(card).also {
|
||||
remoteSaveUserTokens(userId = userId, userTokens = it.toUserTokensResponse())
|
||||
}
|
||||
error is TangemSdkError.NetworkError && error.customMessage.contains(NOT_FOUND_HTTP_CODE) -> {
|
||||
storageService
|
||||
.getUserTokens(userWalletId)
|
||||
?.also { remoteSaveUserTokens(userWalletId = userWalletId, userTokens = it.toUserTokensResponse()) }
|
||||
?: emptyList()
|
||||
}
|
||||
else -> {
|
||||
val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card)
|
||||
tokens.distinct()
|
||||
storageService.getUserTokens(userWalletId)?.distinct() ?: emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun remoteGetUserTokens(card: CardDTO, userId: String): List<Currency> {
|
||||
runCatching {
|
||||
tangemTechApi.getUserTokens(userId)
|
||||
}.onSuccess { response ->
|
||||
return response.tokens
|
||||
.mapNotNull(Currency.Companion::fromTokenResponse).also {
|
||||
storageService.saveUserTokens(userId, it.toUserTokensResponse())
|
||||
}
|
||||
.distinct()
|
||||
}.onFailure {
|
||||
return handleGetUserTokensFailure(card = card, userId = userId, error = it)
|
||||
}
|
||||
error("Unreachable code because runCatching must return result")
|
||||
private suspend fun remoteGetUserTokens(userWalletId: String): List<Currency> {
|
||||
return runCatching { tangemTechApi.getUserTokens(userWalletId) }
|
||||
.fold(
|
||||
onSuccess = { response ->
|
||||
response.tokens
|
||||
.mapNotNull(Currency.Companion::fromTokenResponse)
|
||||
.also { storageService.saveUserTokens(userWalletId, it.toUserTokensResponse()) }
|
||||
.distinct()
|
||||
},
|
||||
onFailure = { handleGetUserTokensFailure(userWalletId = userWalletId, error = it) },
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
// if the /user-tokens endpoint disabled
|
||||
runCatching {
|
||||
tangemTechApi.saveUserTokens(userId, userTokens)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
runCatching { tangemTechApi.saveUserTokens(userWalletId, userTokens) }
|
||||
.onFailure { Timber.e(it) }
|
||||
}
|
||||
|
||||
private fun getUserWalletId(card: CardDTO): String? {
|
||||
return UserWalletIdBuilder.card(card).build()
|
||||
?.stringValue
|
||||
}
|
||||
private fun getUserWalletId(card: CardDTO): String? = UserWalletIdBuilder.card(card).build()?.stringValue
|
||||
|
||||
companion object {
|
||||
private const val GROUP_DEFAULT_VALUE = "none"
|
||||
|
|
@ -131,24 +124,16 @@ class UserTokensRepository(
|
|||
private const val NOT_FOUND_HTTP_CODE = "404"
|
||||
|
||||
// TODO("After adding DI") get dependencies by DI
|
||||
fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository {
|
||||
val fileReader = AndroidFileReader(context)
|
||||
val dispatchers = AppCoroutineDispatcherProvider()
|
||||
|
||||
val oldUserTokensRepository = OldUserTokensRepository(
|
||||
fileReader = fileReader,
|
||||
tangemTechApi = tangemTechService.api,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
val storageService = UserTokensStorageService(
|
||||
oldUserTokensRepository = oldUserTokensRepository,
|
||||
fileReader = fileReader,
|
||||
)
|
||||
|
||||
fun init(
|
||||
context: Context,
|
||||
tangemTechService: TangemTechService,
|
||||
networkConnectionManager: NetworkConnectionManager,
|
||||
): UserTokensRepository {
|
||||
return UserTokensRepository(
|
||||
storageService = storageService,
|
||||
storageService = UserTokensStorageService(fileReader = AndroidFileReader(context)),
|
||||
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.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.tap.common.FileReader
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.toCurrencies
|
||||
|
||||
class UserTokensStorageService(
|
||||
private val oldUserTokensRepository: OldUserTokensRepository,
|
||||
private val fileReader: FileReader,
|
||||
) {
|
||||
class UserTokensStorageService(private val fileReader: FileReader) {
|
||||
private val userTokensAdapter: JsonAdapter<UserTokensResponse> =
|
||||
MoshiConverter.networkMoshi.adapter(UserTokensResponse::class.java)
|
||||
|
||||
fun getUserTokens(userId: String): List<Currency>? {
|
||||
fun getUserTokens(userWalletId: String): List<Currency>? {
|
||||
return try {
|
||||
val json = fileReader.readFile(getFileNameForUserTokens(userId))
|
||||
val json = fileReader.readFile(getFileNameForUserTokens(userWalletId))
|
||||
userTokensAdapter.fromJson(json)?.tokens?.mapNotNull { Currency.fromTokenResponse(it) }
|
||||
} catch (exception: Exception) {
|
||||
Log.error { exception.stackTraceToString() }
|
||||
|
|
@ -26,16 +21,9 @@ class UserTokensStorageService(
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
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) {
|
||||
fun saveUserTokens(userWalletId: String, tokens: UserTokensResponse) {
|
||||
val json = userTokensAdapter.toJson(tokens)
|
||||
fileReader.rewriteFile(json, getFileNameForUserTokens(userId))
|
||||
fileReader.rewriteFile(json, getFileNameForUserTokens(userWalletId))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ import com.tangem.common.KeyPair
|
|||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.datasource.utils.AssetReader
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
||||
class TwinCardsManager(
|
||||
|
|
@ -89,7 +89,7 @@ class TwinCardsManager(
|
|||
}
|
||||
|
||||
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)!!
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import android.view.View
|
|||
import androidx.core.animation.doOnEnd
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapView
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapViewState
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import com.squareup.moshi.Moshi
|
|||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import com.tangem.common.json.TangemSdkAdapter
|
||||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.tangem_sdk_new.storage.AndroidSecureStorage
|
||||
import com.tangem.tangem_sdk_new.storage.createEncryptedSharedPreferences
|
||||
import com.tangem.sdk.storage.AndroidSecureStorage
|
||||
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
||||
import com.tangem.tap.domain.TangemSdkManager
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
|
||||
|
|
|
|||
|
|
@ -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.filterByCoin
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
|
|
@ -62,8 +63,6 @@ internal class DefaultWalletAmountsRepository(
|
|||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : WalletAmountsRepository {
|
||||
private val walletStoresStorage = WalletStoresStorage
|
||||
private val walletManagersStorage = WalletManagerStorage
|
||||
|
||||
override suspend fun updateAmountsForUserWallets(
|
||||
userWallets: List<UserWallet>,
|
||||
|
|
@ -76,8 +75,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
awaitAll(
|
||||
async { fetchAmountsForUserWallets(userWallets) },
|
||||
async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) },
|
||||
)
|
||||
.fold()
|
||||
).fold()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,8 +102,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
awaitAll(
|
||||
async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) },
|
||||
async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) },
|
||||
)
|
||||
.fold()
|
||||
).fold()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -123,41 +120,41 @@ internal class DefaultWalletAmountsRepository(
|
|||
walletStores: List<WalletStoreModel>?,
|
||||
fiatCurrency: FiatCurrency,
|
||||
): CompletionResult<Unit> {
|
||||
// FIXME: Use NetworkConnectionManager when it is added to DI
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
||||
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||
if (!networkConnectionManager.isOnline) {
|
||||
return CompletionResult.Failure(WalletStoresError.NoInternetConnection)
|
||||
}
|
||||
|
||||
val walletStoresInternal = walletStores ?: getWalletStores(userWallets)
|
||||
val currencies = walletStoresInternal
|
||||
.asSequence()
|
||||
.flatMap { it.walletsData }
|
||||
.map { it.currency }
|
||||
val currencies = walletStoresInternal.asSequence().flatMap { it.walletsData }.map { it.currency }
|
||||
|
||||
val coinsIds = currencies.mapNotNull { it.coinId }.distinct().toList()
|
||||
|
||||
return withContext(dispatchers.io) {
|
||||
runCatching { tangemTechApi.getRates(fiatCurrency.code.lowercase(), coinsIds.joinToString(",")) }
|
||||
.onSuccess {
|
||||
updateWalletStoresWithFiatRates(walletStores = walletStoresInternal, fiatRates = it.rates)
|
||||
return@withContext CompletionResult.Success(Unit)
|
||||
}
|
||||
.onFailure {
|
||||
val error = WalletStoresError.FetchFiatRatesError(
|
||||
currencies = currencies.map(Currency::currencySymbol).toList(),
|
||||
cause = it,
|
||||
)
|
||||
runCatching {
|
||||
tangemTechApi.getRates(
|
||||
fiatCurrency.code.lowercase(),
|
||||
coinsIds.joinToString(","),
|
||||
)
|
||||
}.onSuccess {
|
||||
updateWalletStoresWithFiatRates(walletStores = walletStoresInternal, fiatRates = it.rates)
|
||||
return@withContext CompletionResult.Success(Unit)
|
||||
}.onFailure {
|
||||
val error = WalletStoresError.FetchFiatRatesError(
|
||||
currencies = currencies.map(Currency::currencySymbol).toList(),
|
||||
cause = it,
|
||||
)
|
||||
|
||||
Timber.e(
|
||||
error,
|
||||
"""
|
||||
Timber.e(
|
||||
error,
|
||||
"""
|
||||
Unable to fetch fiat rates
|
||||
|- Coins ids: $coinsIds
|
||||
""".trimIndent(),
|
||||
)
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
return@withContext CompletionResult.Failure(error)
|
||||
}
|
||||
return@withContext CompletionResult.Failure(error)
|
||||
}
|
||||
|
||||
error("Unreachable code because runCatching must return result")
|
||||
}
|
||||
|
|
@ -166,9 +163,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
private suspend fun fetchAmountsForUserWallets(
|
||||
userWallets: List<UserWallet>,
|
||||
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
|
||||
userWallets.map { async { fetchAmountsForUserWallet(it) } }
|
||||
.awaitAll()
|
||||
.fold()
|
||||
userWallets.map { async { fetchAmountsForUserWallet(it) } }.awaitAll().fold()
|
||||
}
|
||||
|
||||
private suspend fun fetchAmountsForUserWallet(
|
||||
|
|
@ -186,8 +181,8 @@ internal class DefaultWalletAmountsRepository(
|
|||
scanResponse: ScanResponse,
|
||||
walletStores: List<WalletStoreModel>,
|
||||
): CompletionResult<Unit> = coroutineScope {
|
||||
// FIXME: Use NetworkConnectionManager when it is added to DI
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
||||
val networkConnectionManager = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||
if (!networkConnectionManager.isOnline) {
|
||||
walletStores.forEach {
|
||||
updateWalletStoreWithUnreachable(it)
|
||||
}
|
||||
|
|
@ -200,9 +195,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
val walletManager = walletStore.walletManager
|
||||
fetchAmountsForWalletStore(userWalletId, scanResponse, walletStore, walletManager)
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
.fold()
|
||||
}.awaitAll().fold()
|
||||
}
|
||||
|
||||
private suspend fun fetchAmountsForWalletStore(
|
||||
|
|
@ -223,24 +216,25 @@ internal class DefaultWalletAmountsRepository(
|
|||
updateWalletStoreWithUnreachable(walletStore)
|
||||
}
|
||||
else -> {
|
||||
updateWalletManager(scanResponse, walletManager)
|
||||
.map { updateWalletManagerInStorage(userWalletId, walletManager) }
|
||||
.flatMap {
|
||||
updateWalletStoreWithAmounts(
|
||||
walletStore = walletStore,
|
||||
updatedWallet = walletManager.wallet,
|
||||
// FIXME: move DemoHelper to Demo core module maybe
|
||||
isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId),
|
||||
)
|
||||
}
|
||||
.flatMap { fetchWalletStoreRentIfNeeded(walletStore, walletManager) }
|
||||
.flatMapOnFailure { error ->
|
||||
updateWalletStoreWithError(
|
||||
walletStore = walletStore,
|
||||
wallet = walletManager.wallet,
|
||||
error = error,
|
||||
)
|
||||
}
|
||||
updateWalletManager(scanResponse, walletManager).map {
|
||||
updateWalletManagerInStorage(
|
||||
userWalletId,
|
||||
walletManager,
|
||||
)
|
||||
}.flatMap {
|
||||
updateWalletStoreWithAmounts(
|
||||
walletStore = walletStore,
|
||||
updatedWallet = walletManager.wallet,
|
||||
// FIXME: move DemoHelper to Demo core module maybe
|
||||
isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId),
|
||||
)
|
||||
}.flatMap { fetchWalletStoreRentIfNeeded(walletStore, walletManager) }.flatMapOnFailure { error ->
|
||||
updateWalletStoreWithError(
|
||||
walletStore = walletStore,
|
||||
wallet = walletManager.wallet,
|
||||
error = error,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -267,8 +261,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
walletStore: WalletStoreModel,
|
||||
walletManager: WalletManager,
|
||||
): CompletionResult<Unit> {
|
||||
val rentProvider = walletManager as? RentProvider
|
||||
?: return CompletionResult.Success(Unit)
|
||||
val rentProvider = walletManager as? RentProvider ?: return CompletionResult.Success(Unit)
|
||||
|
||||
when (val result = rentProvider.minimalBalanceForRentExemption()) {
|
||||
is Success -> {
|
||||
|
|
@ -318,7 +311,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
)
|
||||
|
||||
if (error is BlockchainSdkError) {
|
||||
walletStoresStorage.update { prevState ->
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStore(
|
||||
walletStoreToUpdate = walletStore,
|
||||
update = {
|
||||
|
|
@ -350,7 +343,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
""".trimIndent(),
|
||||
)
|
||||
|
||||
walletStoresStorage.update { prevState ->
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStore(
|
||||
walletStoreToUpdate = walletStore,
|
||||
update = {
|
||||
|
|
@ -378,7 +371,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
""".trimIndent(),
|
||||
)
|
||||
|
||||
walletStoresStorage.update { prevState ->
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStore(
|
||||
walletStoreToUpdate = walletStore,
|
||||
update = {
|
||||
|
|
@ -402,7 +395,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
""".trimIndent(),
|
||||
)
|
||||
|
||||
walletStoresStorage.update { prevState ->
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStore(
|
||||
walletStoreToUpdate = walletStore,
|
||||
update = {
|
||||
|
|
@ -425,7 +418,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
""".trimIndent(),
|
||||
)
|
||||
|
||||
walletStoresStorage.update { prevState ->
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStores(
|
||||
walletStoresToUpdate = walletStores,
|
||||
update = {
|
||||
|
|
@ -450,7 +443,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
)
|
||||
|
||||
if (rent != walletStore.walletRent) {
|
||||
walletStoresStorage.update { prevState ->
|
||||
WalletStoresStorage.update { prevState ->
|
||||
prevState.replaceWalletStore(
|
||||
walletStoreToUpdate = walletStore,
|
||||
update = {
|
||||
|
|
@ -465,14 +458,12 @@ internal class DefaultWalletAmountsRepository(
|
|||
userWalletId: UserWalletId,
|
||||
walletManager: WalletManager,
|
||||
) = withContext(Dispatchers.Default) {
|
||||
walletManagersStorage.update { prevManagers ->
|
||||
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty()
|
||||
.toMutableList()
|
||||
.apply {
|
||||
replaceByOrAdd(walletManager) {
|
||||
it.wallet.blockchain == walletManager.wallet.blockchain
|
||||
}
|
||||
WalletManagerStorage.update { prevManagers ->
|
||||
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty().toMutableList().apply {
|
||||
replaceByOrAdd(walletManager) {
|
||||
it.wallet.blockchain == walletManager.wallet.blockchain
|
||||
}
|
||||
}
|
||||
|
||||
prevManagers.apply {
|
||||
set(userWalletId, newManagersForUserWallet)
|
||||
|
|
@ -481,13 +472,8 @@ internal class DefaultWalletAmountsRepository(
|
|||
}
|
||||
|
||||
private suspend fun getWalletStores(userWallets: List<UserWallet>): List<WalletStoreModel> {
|
||||
return userWallets
|
||||
.map { it.walletId }
|
||||
.flatMap { userWalletId ->
|
||||
walletStoresStorage.getAll()
|
||||
.firstOrNull()
|
||||
?.get(userWalletId)
|
||||
.orEmpty()
|
||||
}
|
||||
return userWallets.map { it.walletId }.flatMap { userWalletId ->
|
||||
WalletStoresStorage.getAll().firstOrNull()?.get(userWalletId).orEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.demo
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionSender
|
||||
import com.tangem.blockchain.common.TransactionSigner
|
||||
|
|
@ -11,6 +10,7 @@ import com.tangem.blockchain.extensions.Result
|
|||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.demo.DemoConfig
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
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.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[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 {
|
||||
|
||||
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.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.demo.DemoConfig
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class DetailsMiddleware {
|
|||
private fun handleAction(state: DetailsState, action: Action) {
|
||||
when (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.ReCreateTwinsWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
|
||||
|
|
@ -157,15 +157,15 @@ class DetailsMiddleware {
|
|||
|
||||
class ManageSecurityMiddleware {
|
||||
@Suppress("ComplexMethod")
|
||||
fun handle(action: DetailsAction.ManageSecurity) {
|
||||
fun handle(action: DetailsAction.ManageSecurity, detailsState: DetailsState) {
|
||||
when (action) {
|
||||
is DetailsAction.ManageSecurity.OpenSecurity -> {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity))
|
||||
}
|
||||
is DetailsAction.ManageSecurity.SaveChanges -> {
|
||||
val cardId = store.state.detailsState.scanResponse?.card?.cardId
|
||||
val selectedOption =
|
||||
store.state.detailsState.cardSettingsState?.manageSecurityState?.selectedOption
|
||||
val cardSettingsState = detailsState.cardSettingsState
|
||||
val cardId = cardSettingsState?.card?.cardId
|
||||
val selectedOption = cardSettingsState?.manageSecurityState?.selectedOption
|
||||
scope.launch {
|
||||
val result = when (selectedOption) {
|
||||
SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId)
|
||||
|
|
|
|||
|
|
@ -5,11 +5,8 @@ import com.tangem.domain.common.CardDTO
|
|||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.entities.Button
|
||||
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 java.util.*
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
data class DetailsState(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
|
|
@ -19,14 +16,7 @@ data class DetailsState(
|
|||
val createBackupAllowed: Boolean = false,
|
||||
val appCurrency: FiatCurrency = FiatCurrency.Default,
|
||||
val appSettingsState: AppSettingsState = AppSettingsState(),
|
||||
) : 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
|
||||
}
|
||||
}
|
||||
) : StateType
|
||||
|
||||
data class CardInfo(
|
||||
val cardId: String,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
|
|
@ -21,9 +19,6 @@ class DetailsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
|||
|
||||
private val detailsViewModel = DetailsViewModel(store)
|
||||
|
||||
private var detailsScreenState: MutableState<DetailsScreenState> =
|
||||
mutableStateOf(detailsViewModel.updateState(store.state.detailsState))
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Analytics.send(Settings.ScreenOpened())
|
||||
|
|
@ -42,7 +37,7 @@ class DetailsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
|||
isTransitionGroup = true
|
||||
TangemTheme {
|
||||
DetailsScreen(
|
||||
state = detailsScreenState.value,
|
||||
state = detailsViewModel.detailsScreenState.value,
|
||||
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||
)
|
||||
}
|
||||
|
|
@ -66,6 +61,6 @@ class DetailsFragment : Fragment(), StoreSubscriber<DetailsState> {
|
|||
|
||||
override fun newState(state: DetailsState) {
|
||||
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.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.Row
|
||||
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.verticalScroll
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.material.Text
|
||||
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.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
|
|
@ -27,10 +34,12 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
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.features.details.ui.common.ScreenTitle
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun DetailsScreen(state: DetailsScreenState, onBackClick: () -> Unit) {
|
||||
|
|
@ -46,32 +55,35 @@ fun DetailsScreen(state: DetailsScreenState, onBackClick: () -> Unit) {
|
|||
|
||||
@Composable
|
||||
fun Content(state: DetailsScreenState) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
ScreenTitle(titleRes = R.string.details_title, Modifier.padding(bottom = 52.dp))
|
||||
state.elements.map { element ->
|
||||
if (element == SettingsElement.WalletConnect) {
|
||||
WalletConnectDetailsItem(onItemsClick = state.onItemsClick)
|
||||
} else {
|
||||
DetailsItem(
|
||||
item = element,
|
||||
appCurrency = state.appCurrency,
|
||||
onItemsClick = { state.onItemsClick(element) },
|
||||
)
|
||||
Box {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
ScreenTitle(titleRes = R.string.details_title, Modifier.padding(bottom = 52.dp))
|
||||
state.elements.map { element ->
|
||||
if (element == SettingsElement.WalletConnect) {
|
||||
WalletConnectDetailsItem(onItemsClick = state.onItemsClick)
|
||||
} else {
|
||||
DetailsItem(
|
||||
item = 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))
|
||||
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),
|
||||
)
|
||||
ShowSnackbarIfNeeded(state.showErrorSnackbar.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
@Preview
|
||||
private fun Preview() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.features.details.ui.details
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Immutable
|
||||
|
|
@ -11,6 +13,7 @@ data class DetailsScreenState(
|
|||
val appCurrency: String,
|
||||
val onItemsClick: (SettingsElement) -> Unit,
|
||||
val onSocialNetworkClick: (SocialNetworkLink) -> Unit,
|
||||
val showErrorSnackbar: MutableState<EventError> = mutableStateOf(EventError.Empty),
|
||||
) {
|
||||
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),
|
||||
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
|
||||
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
|
||||
|
|
@ -38,6 +42,11 @@ data class SocialNetworkLink(
|
|||
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) {
|
||||
object Telegram : SocialNetwork("Telegram", R.drawable.ic_telegram)
|
||||
object Twitter : SocialNetwork("Twitter", R.drawable.ic_twitter)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
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.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
||||
|
|
@ -8,6 +10,7 @@ import com.tangem.tap.common.redux.AppState
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||
|
|
@ -18,6 +21,9 @@ import org.rekotlin.Store
|
|||
|
||||
class DetailsViewModel(private val store: Store<AppState>) {
|
||||
|
||||
var detailsScreenState: MutableState<DetailsScreenState> = mutableStateOf(updateState(store.state.detailsState))
|
||||
private set
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
fun updateState(state: DetailsState): DetailsScreenState {
|
||||
val cardTypesResolver = state.scanResponse?.cardTypesResolver
|
||||
|
|
@ -39,6 +45,7 @@ class DetailsViewModel(private val store: Store<AppState>) {
|
|||
SettingsElement.AppSettings -> if (state.appSettingsState.isBiometricsAvailable) it else null
|
||||
SettingsElement.AppCurrency -> if (cardTypesResolver?.isMultiwalletAllowed() != 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
|
||||
}
|
||||
}
|
||||
|
|
@ -94,7 +101,18 @@ class DetailsViewModel(private val store: Store<AppState>) {
|
|||
// TODO: To be available later
|
||||
}
|
||||
SettingsElement.ReferralProgram -> {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram))
|
||||
if (store.state.detailsState.scanResponse?.let { DemoHelper.isDemoCard(it) } == true) {
|
||||
detailsScreenState.value.showErrorSnackbar.value = EventError.DemoReferralNotAvailable(
|
||||
onErrorShow = {
|
||||
detailsScreenState.value.showErrorSnackbar.value = EventError.Empty
|
||||
},
|
||||
)
|
||||
} else {
|
||||
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.blockchain.common.WalletManager
|
||||
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.tap.domain.TapError
|
||||
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.core.analytics.Analytics
|
||||
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.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.extensions.beginDelayedTransition
|
||||
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.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LargeClass")
|
||||
@AndroidEntryPoint
|
||||
class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
||||
|
||||
@Inject
|
||||
lateinit var assetReader: AssetReader
|
||||
|
||||
private val mainBinding by lazy { binding.vMain }
|
||||
private var previousStep: TwinCardsStep = TwinCardsStep.None
|
||||
|
||||
|
|
@ -253,8 +259,8 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
|||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchFirstStep(
|
||||
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
|
||||
AndroidAssetReader(requireContext()),
|
||||
initialMessage = Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
|
||||
reader = assetReader,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import com.google.android.material.tabs.TabLayoutMediator
|
|||
import com.tangem.common.CardIdFormatter
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.PropertyCalculator
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.PropertyCalculator
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.inflate
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import android.animation.PropertyValuesHolder
|
|||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.core.animation.doOnEnd
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapView
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapViewState
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
|
||||
class WalletCardsWidget(
|
||||
val leapfrogWidget: LeapfrogWidget,
|
||||
|
|
|
|||
|
|
@ -155,6 +155,12 @@ sealed class SendAction : SendScreenAction {
|
|||
val reduceAmount: BigDecimal,
|
||||
) : Dialog()
|
||||
|
||||
data class KaspaWarningDialog(
|
||||
val maxOutputs: Int,
|
||||
val maxAmount: BigDecimal,
|
||||
val onOk: () -> Unit,
|
||||
) : Dialog()
|
||||
|
||||
sealed class SendTransactionFails : Dialog() {
|
||||
data class CardSdkError(val error: TangemSdkError) : 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) {
|
||||
val potentialPayId = data.toLowerCase()
|
||||
val potentialPayId = data.lowercase()
|
||||
if (isPayIdEnabled() && PayIdManager.isPayId(potentialPayId)) {
|
||||
dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput))
|
||||
} else {
|
||||
|
|
@ -113,10 +113,11 @@ internal class AddressPayIdMiddleware {
|
|||
}
|
||||
|
||||
private fun verifyAddress(address: String, wallet: Wallet, isUserInput: Boolean, dispatch: (Action) -> Unit) {
|
||||
val addressSchemeSplit = if (wallet.blockchain == Blockchain.BitcoinCash) {
|
||||
listOf(address)
|
||||
} else {
|
||||
address.split(":")
|
||||
val addressSchemeSplit = when (wallet.blockchain) {
|
||||
Blockchain.BitcoinCash,
|
||||
Blockchain.Kaspa,
|
||||
-> listOf(address)
|
||||
else -> address.split(":")
|
||||
}
|
||||
val noSchemeAddress = when (addressSchemeSplit.size) {
|
||||
1 -> address // no scheme
|
||||
|
|
|
|||
|
|
@ -269,6 +269,18 @@ private fun sendTransaction(
|
|||
// from XLM, XRP, Polkadot
|
||||
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 -> {
|
||||
when {
|
||||
error.customMessage.contains(DemoTransactionSender.ID) -> {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ private class SendReducer : SendInternalReducer {
|
|||
sendState.copy(sendButtonState = IndeterminateProgressButton(action.state))
|
||||
}
|
||||
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.BlockchainSdkError -> 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.tangem.Message
|
||||
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.analytics.events.Token
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.features.send.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
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) { _, _ ->
|
||||
store.dispatch(SendAction.Dialog.Hide)
|
||||
dialog.onOk()
|
||||
}
|
||||
}
|
||||
.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import androidx.appcompat.app.AlertDialog
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
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.feedback.SendTransactionFailedEmail
|
||||
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.ui.FeeUiHelper
|
||||
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.SendTransactionFailsDialog
|
||||
import com.tangem.tap.features.send.ui.dialogs.TezosWarningDialog
|
||||
|
|
@ -124,6 +125,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
|
|||
dialog?.show()
|
||||
}
|
||||
}
|
||||
is SendAction.Dialog.KaspaWarningDialog -> {
|
||||
if (dialog == null) {
|
||||
dialog = KaspaWarningDialog.create(fg.requireContext(), state.dialog)
|
||||
dialog?.show()
|
||||
}
|
||||
}
|
||||
is SendAction.Dialog.SendTransactionFails.CardSdkError -> {
|
||||
if (dialog == null) {
|
||||
dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
|||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
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.extensions.dispatchDebugErrorNotification
|
||||
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.tokens.LoadAvailableCoinsService
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
|
@ -85,7 +85,7 @@ class TokensMiddleware {
|
|||
val loadCoinsService = LoadAvailableCoinsService(
|
||||
tangemTechApi = store.state.domainNetworks.tangemTechService.api,
|
||||
dispatchers = AppCoroutineDispatcherProvider(),
|
||||
assetReader = assetReader,
|
||||
assetReader = store.state.daggerGraphState.get(DaggerGraphState::assetReader),
|
||||
)
|
||||
|
||||
scope.launch {
|
||||
|
|
@ -264,14 +264,10 @@ class TokensMiddleware {
|
|||
val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
|
||||
if (toDerive.isEmpty()) return null
|
||||
|
||||
return DerivationData(
|
||||
derivations = mapKeyOfWalletPublicKey to toDerive,
|
||||
)
|
||||
return DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
|
||||
}
|
||||
|
||||
private class DerivationData(
|
||||
val derivations: Pair<ByteArrayKey, List<DerivationPath>>,
|
||||
)
|
||||
private class DerivationData(val derivations: Pair<ByteArrayKey, List<DerivationPath>>)
|
||||
|
||||
private fun submitAdd(scanResponse: ScanResponse, currencyList: List<Currency>) {
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
|
|
@ -36,22 +37,26 @@ data class WalletData(
|
|||
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()) &&
|
||||
currency.blockchain.id != Blockchain.Optimism.id && // disable optimism blockchain for now
|
||||
isExchangeFeatureOn &&
|
||||
!currency.isCustomCurrency(null)
|
||||
}
|
||||
|
||||
fun getAvailableActions(
|
||||
swapInteractor: SwapInteractor,
|
||||
exchangeManager: CurrencyExchangeManager,
|
||||
isExchangeFeatureOn: Boolean,
|
||||
swapFeatureToggleManager: SwapFeatureToggleManager,
|
||||
): Set<CurrencyAction> {
|
||||
return setOfNotNull(
|
||||
if (isAvailableToBuy(exchangeManager)) CurrencyAction.Buy 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.extensions.guard
|
||||
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.Basic
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
|
|
@ -34,15 +35,14 @@ import com.tangem.tap.features.wallet.redux.WalletData
|
|||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.WalletStore
|
||||
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.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.totalFiatBalanceCalculator
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.userWalletsListManagerSafe
|
||||
import com.tangem.tap.walletStoresManager
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -68,6 +68,9 @@ class WalletMiddleware {
|
|||
)
|
||||
}
|
||||
|
||||
private val networkConnectionManager: NetworkConnectionManager
|
||||
get() = store.state.daggerGraphState.get(DaggerGraphState::networkConnectionManager)
|
||||
|
||||
val walletMiddleware: Middleware<AppState> = { _, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
|
|
@ -131,16 +134,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 -> {
|
||||
Analytics.send(Token.Receive.ButtonCopyAddress())
|
||||
action.context.copyToClipboard(action.address)
|
||||
|
|
@ -155,7 +148,7 @@ class WalletMiddleware {
|
|||
store.dispatchOpenUrl(action.exploreUrl)
|
||||
}
|
||||
is WalletAction.Send -> {
|
||||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
|
||||
if (!networkConnectionManager.isOnline) {
|
||||
store.dispatchErrorNotification(TapError.NoInternetConnection)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,29 +2,26 @@ package com.tangem.tap.features.wallet.redux.middlewares
|
|||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.SignatureCountValidator
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
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.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.domain.extensions.hasSignedHashes
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
class WarningsMiddleware {
|
||||
fun handle(action: WalletAction.Warnings, globalState: GlobalState?) {
|
||||
|
|
@ -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?) {
|
||||
globalState?.scanResponse?.let { scanResponse ->
|
||||
val card = scanResponse.card
|
||||
|
|
@ -147,7 +137,9 @@ class WarningsMiddleware {
|
|||
|
||||
private fun checkHashesCountOnline() {
|
||||
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 card = scanResponse?.card
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ import com.tangem.common.extensions.guard
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
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.TestActions
|
||||
import com.tangem.tap.common.analytics.events.DetailsScreen
|
||||
|
|
@ -75,6 +76,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
@Inject
|
||||
lateinit var swapInteractor: SwapInteractor
|
||||
|
||||
@Inject
|
||||
lateinit var swapFeatureToggleManager: SwapFeatureToggleManager
|
||||
|
||||
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
|
||||
private lateinit var warningMessagesAdapter: WalletDetailWarningMessagesAdapter
|
||||
|
||||
|
|
@ -290,7 +294,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
WalletAction.DialogAction.ChooseTradeActionDialog(
|
||||
buyAllowed = selectedWallet.isAvailableToBuy(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(
|
||||
swapInteractor = swapInteractor,
|
||||
exchangeManager = exchangeManager,
|
||||
isExchangeFeatureOn = isExchangeServiceFeatureOn,
|
||||
swapFeatureToggleManager = swapFeatureToggleManager,
|
||||
)
|
||||
binding.rowButtons.updateButtonsVisibility(
|
||||
actions = actions,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.badoo.mvicore.modelWatcher
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||
import com.tangem.core.ui.utils.OneTouchClickListener
|
||||
import com.tangem.feature.swap.api.SwapFeatureToggleManager
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.tap.MainActivity
|
||||
import com.tangem.tap.common.analytics.events.Portfolio
|
||||
|
|
@ -56,6 +57,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
@Inject
|
||||
lateinit var swapInteractor: SwapInteractor
|
||||
|
||||
@Inject
|
||||
lateinit var swapFeatureToggleManager: SwapFeatureToggleManager
|
||||
|
||||
private lateinit var warningsAdapter: WarningMessagesAdapter
|
||||
|
||||
private val binding: FragmentWalletBinding by viewBinding(FragmentWalletBinding::bind)
|
||||
|
|
@ -67,6 +71,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
private val totalBalanceWatcher = modelWatcher {
|
||||
(WalletState::totalBalance) { totalBalance ->
|
||||
totalBalance?.state?.let {
|
||||
viewModel.onBalanceLoaded(totalBalance)
|
||||
store.state.globalState.topUpController?.totalBalanceStateChanged(it)
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +80,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
lifecycle.addObserver(viewModel)
|
||||
activity?.lifecycleScope?.launchWhenCreated {
|
||||
lifecycleScope.launchWhenCreated {
|
||||
viewModel.launch()
|
||||
}
|
||||
}
|
||||
|
|
@ -179,6 +184,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
totalBalanceWatcher.invoke(state)
|
||||
|
||||
walletView.swapInteractor = swapInteractor
|
||||
walletView.swapFeatureToggleManager = swapFeatureToggleManager
|
||||
|
||||
walletView.onNewState(state)
|
||||
|
||||
|
|
@ -255,10 +261,10 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
return when (item.itemId) {
|
||||
R.id.details_menu -> {
|
||||
store.dispatch(GlobalAction.UpdateFeedbackInfo(store.state.walletState.walletManagers))
|
||||
store.state.globalState.scanResponse?.let { scanNoteResponse ->
|
||||
store.state.globalState.scanResponse?.let { scanResponse ->
|
||||
store.dispatch(
|
||||
DetailsAction.PrepareScreen(
|
||||
scanResponse = scanNoteResponse,
|
||||
scanResponse = scanResponse,
|
||||
wallets = store.state.walletState.walletManagers.map { it.wallet },
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,15 +4,18 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
|||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModel
|
||||
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.events.Basic
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
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.ui.analytics.WalletAnalyticsEventsMapper
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.walletStoresManager
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -22,16 +25,22 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Kill me, please
|
||||
@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
|
||||
set(value) {
|
||||
field?.cancel()
|
||||
field = value
|
||||
}
|
||||
|
||||
private val walletAnalyticsEventsMapper = WalletAnalyticsEventsMapper()
|
||||
|
||||
init {
|
||||
subscribeToUserWalletsListManagerUpdates()
|
||||
}
|
||||
|
|
@ -53,7 +62,7 @@ internal class WalletViewModel : ViewModel(), StoreSubscriber<UserWalletsListMan
|
|||
val currency = ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)
|
||||
val signInType = store.state.signInState.type
|
||||
if (currency != null && signInType != null) {
|
||||
Analytics.send(
|
||||
analyticsEventHandler.send(
|
||||
Basic.SignedIn(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
|
|
@ -65,7 +74,7 @@ internal class WalletViewModel : ViewModel(), StoreSubscriber<UserWalletsListMan
|
|||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
Analytics.send(MainScreen.ScreenOpened())
|
||||
analyticsEventHandler.send(MainScreen.ScreenOpened())
|
||||
}
|
||||
|
||||
fun launch() {
|
||||
|
|
@ -76,6 +85,18 @@ internal class WalletViewModel : ViewModel(), StoreSubscriber<UserWalletsListMan
|
|||
bootstrapShowSaveWalletIfNeeded()
|
||||
}
|
||||
|
||||
fun onBalanceLoaded(totalBalance: TotalBalance?) {
|
||||
if (totalBalance != null) {
|
||||
walletAnalyticsEventsMapper.convert(totalBalance)?.let { balanceParam ->
|
||||
analyticsEventHandler.send(
|
||||
Basic.BalanceLoaded(
|
||||
balance = balanceParam,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) {
|
||||
observeWalletStoresUpdatesJob = manager.selectedUserWallet
|
||||
.map { it.walletId }
|
||||
|
|
|
|||
|
|
@ -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.core.view.isVisible
|
||||
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.wallet.databinding.ViewCurrencyIconBinding
|
||||
import kotlin.math.roundToInt
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ class SingleWalletView : WalletView() {
|
|||
isExchangeServiceFeatureEnabled: Boolean,
|
||||
) {
|
||||
val swapInteractor = this.swapInteractor ?: return
|
||||
val swapFeatureToggleManager = this.swapFeatureToggleManager ?: return
|
||||
|
||||
val exchangeManager = store.state.globalState.exchangeManager
|
||||
binding?.rowButtons?.apply {
|
||||
|
|
@ -158,7 +159,7 @@ class SingleWalletView : WalletView() {
|
|||
val actions = walletData.getAvailableActions(
|
||||
swapInteractor = swapInteractor,
|
||||
exchangeManager = exchangeManager,
|
||||
isExchangeFeatureOn = isExchangeServiceFeatureEnabled,
|
||||
swapFeatureToggleManager = swapFeatureToggleManager,
|
||||
)
|
||||
binding?.rowButtons?.updateButtonsVisibility(
|
||||
actions = actions,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
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.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.ui.WalletFragment
|
||||
|
|
@ -8,6 +9,7 @@ import com.tangem.wallet.databinding.FragmentWalletBinding
|
|||
abstract class WalletView {
|
||||
|
||||
var swapInteractor: SwapInteractor? = null
|
||||
var swapFeatureToggleManager: SwapFeatureToggleManager? = null
|
||||
|
||||
protected var fragment: WalletFragment? = null
|
||||
protected var binding: FragmentWalletBinding? = null
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ internal sealed interface WalletSelectorAction : Action {
|
|||
|
||||
data class SelectWallet(
|
||||
val userWalletId: UserWalletId,
|
||||
val sendAnalyticsEvent: Boolean = false,
|
||||
) : WalletSelectorAction
|
||||
|
||||
data class RenameWallet(
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ internal class WalletSelectorMiddleware {
|
|||
addWallet()
|
||||
}
|
||||
is WalletSelectorAction.SelectWallet -> {
|
||||
selectWallet(action.userWalletId)
|
||||
selectWallet(action.userWalletId, action.sendAnalyticsEvent)
|
||||
}
|
||||
is WalletSelectorAction.RemoveWallets -> {
|
||||
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 {
|
||||
userWalletsListManager.get(userWalletId)
|
||||
.flatMap { userWallet ->
|
||||
|
|
@ -212,7 +212,10 @@ internal class WalletSelectorMiddleware {
|
|||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
|
||||
if (selectedUserWallet != null) {
|
||||
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))
|
||||
}
|
||||
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.model.DialogModel
|
||||
import com.tangem.tap.features.walletSelector.ui.model.WarningModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<WalletSelectorScreenState>() {
|
||||
private val viewModel by viewModels<WalletSelectorViewModel>()
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,12 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
cancelWalletEditing(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.common.CompletionResult
|
||||
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.toMapKey
|
||||
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.models.Currency
|
||||
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.tap.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
|
|
@ -49,9 +52,10 @@ class DerivationManagerImpl(
|
|||
if (scanResponse != null) {
|
||||
deriveMissingBlockchains(
|
||||
scanResponse = scanResponse,
|
||||
listOf(appCurrency),
|
||||
currencyList = listOf(appCurrency),
|
||||
onSuccess = { continuation.resumeWith(Result.success(true)) },
|
||||
) {
|
||||
continuation.resumeWith(Result.success(true))
|
||||
continuation.resumeWith(Result.failure(it))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -84,6 +88,7 @@ class DerivationManagerImpl(
|
|||
scanResponse: ScanResponse,
|
||||
currencyList: List<com.tangem.tap.features.wallet.models.Currency>,
|
||||
onSuccess: (ScanResponse) -> Unit,
|
||||
onFailure: (Exception) -> Unit,
|
||||
) {
|
||||
val derivationDataList = listOfNotNull(
|
||||
getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList),
|
||||
|
|
@ -135,6 +140,7 @@ class DerivationManagerImpl(
|
|||
"Error derivation",
|
||||
),
|
||||
)
|
||||
onFailure.invoke(handleTangemError(result.error))
|
||||
}
|
||||
else -> {
|
||||
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(
|
||||
val derivations: Pair<ByteArrayKey, List<DerivationPath>>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|||
import com.tangem.Message
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
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.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
|
|
@ -155,56 +156,31 @@ class TransactionManagerImpl(
|
|||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
||||
if (walletManager is EthereumWalletManager) {
|
||||
val gasLimit = getGasLimit(
|
||||
evmWalletManager = walletManager,
|
||||
if (walletManager is OptimismWalletManager) {
|
||||
return getFeeForOptimismBlockchain(
|
||||
walletManager = walletManager,
|
||||
amount = createAmount(amountToSend, currencyToSend, blockchain),
|
||||
destinationAddress = destinationAddress,
|
||||
data = data,
|
||||
)
|
||||
}
|
||||
return getFeeForEthereumBlockchain(
|
||||
walletManager = walletManager,
|
||||
blockchain = blockchain,
|
||||
amount = amountToSend,
|
||||
amountToSend = amountToSend,
|
||||
currency = currencyToSend,
|
||||
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)
|
||||
}
|
||||
}
|
||||
increaseBy = increaseBy,
|
||||
)
|
||||
} else {
|
||||
val fee = (walletManager as? TransactionSender)?.getFee(
|
||||
amount = createAmount(amountToSend, currencyToSend, blockchain),
|
||||
destination = destinationAddress,
|
||||
) ?: error("Cannot cast to TransactionSender")
|
||||
when (fee) {
|
||||
is Result.Success -> {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
return getFeeForBlockchain(
|
||||
walletManager = walletManager,
|
||||
amountToSend = amountToSend,
|
||||
currency = currencyToSend,
|
||||
blockchain = blockchain,
|
||||
destinationAddress = destinationAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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")
|
||||
private suspend fun getGasLimit(
|
||||
evmWalletManager: EthereumWalletManager,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -1,13 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
|
||||
<debug-overrides>
|
||||
<trust-anchors>
|
||||
<certificates src="user" />
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</debug-overrides>
|
||||
|
||||
<domain-config>
|
||||
<domain includeSubdomains="true">api.tangem.com</domain>
|
||||
<trust-anchors>
|
||||
|
|
@ -17,5 +9,4 @@
|
|||
</domain-config>
|
||||
|
||||
<base-config cleartextTrafficPermitted="false" />
|
||||
|
||||
</network-security-config>
|
||||
|
|
|
|||
|
|
@ -1,37 +1,13 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven(url = "https://maven.fabric.io/public")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath(ClasspathDependency.AndroidGradlePlugin)
|
||||
classpath(ClasspathDependency.KotlinGradlePlugin)
|
||||
classpath(ClasspathDependency.AndroidMavenGradlePlugin)
|
||||
classpath(ClasspathDependency.GoogleServices)
|
||||
classpath(ClasspathDependency.GoogleFirebaseCrashlytics)
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.google.dagger.hilt.android") version "2.44" apply false
|
||||
kotlin("plugin.serialization") version Versions.kotlin apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter() // unable to replace with mavenCentral() due to rekotlin and com.otaliastudios:cameraview
|
||||
mavenLocal()
|
||||
maven("https://nexus.tangem-tech.com/repository/maven-releases/")
|
||||
maven("https://jitpack.io")
|
||||
maven("https://zendesk.jfrog.io/zendesk/repo")
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply(plugin = "detekt-convention")
|
||||
alias(deps.plugins.kotlin.android) apply false
|
||||
alias(deps.plugins.kotlin.jvm) apply false
|
||||
alias(deps.plugins.kotlin.serialization) apply false
|
||||
alias(deps.plugins.kotlin.kapt) apply false
|
||||
alias(deps.plugins.android.application) apply false
|
||||
alias(deps.plugins.android.library) apply false
|
||||
alias(deps.plugins.hilt.android) apply false
|
||||
alias(deps.plugins.google.services) apply false
|
||||
alias(deps.plugins.firebase.crashlytics) apply false
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete::class) {
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.android.tools.build:gradle:7.1.3")
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
|
||||
//TODO("Upgrade version to 1.22.0 after upgrading kotlin version to 1.7.21")
|
||||
implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.21.0")
|
||||
implementation("com.squareup:javapoet:1.13.0")
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
object ClasspathDependency {
|
||||
const val AndroidGradlePlugin = "com.android.tools.build:gradle:" + Versions.androidGradlePlugin
|
||||
const val AndroidMavenGradlePlugin = "com.github.dcendents:android-maven-gradle-plugin:" +
|
||||
Versions.androidMavenGradlePlugin
|
||||
const val GoogleFirebaseCrashlytics = "com.google.firebase:firebase-crashlytics-gradle:" +
|
||||
Versions.googleFirebaseCrashlytics
|
||||
const val GoogleServices = "com.google.gms:google-services:" + Versions.googleServices
|
||||
const val KotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:" + Versions.kotlin
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
typealias ConfigFieldBuilder = (String, String, String) -> Unit
|
||||
|
||||
class BuildConfigFieldFactory(
|
||||
private val fields: List<Field>,
|
||||
private val builder: ConfigFieldBuilder,
|
||||
) {
|
||||
fun create() {
|
||||
fields.forEach { field -> builder(field.type, field.name, field.value) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build config field
|
||||
*
|
||||
* @property type field type
|
||||
* @property name field name
|
||||
* @property value field value
|
||||
*/
|
||||
sealed class Field(val type: String, val name: String, val value: String) {
|
||||
|
||||
class Environment(value: String) : Field("String", "ENVIRONMENT", "\"$value\"")
|
||||
|
||||
class TestActionEnabled(isEnabled: Boolean) : Field("Boolean","TEST_ACTION_ENABLED", isEnabled.toString())
|
||||
|
||||
class LogEnabled(isEnabled: Boolean) : Field("Boolean","LOG_ENABLED", isEnabled.toString())
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
object AndroidX {
|
||||
const val activityCompose = "androidx.activity:activity-compose:" + Versions.androidxActivityCompose
|
||||
const val appCompat = "androidx.appcompat:appcompat:" + Versions.androidxAppCompat
|
||||
const val browser = "androidx.browser:browser:" + Versions.androidxBrowser
|
||||
const val constraintLayout = "androidx.constraintlayout:constraintlayout:" + Versions.androidxConstraintLayout
|
||||
const val coreKtx = "androidx.core:core-ktx:" + Versions.androidxCore
|
||||
const val fragmentKtx = "androidx.fragment:fragment-ktx:" + Versions.androidxFragment
|
||||
const val lifecycleCommonJava8 = "androidx.lifecycle:lifecycle-common-java8:" + Versions.androidxLifecycle
|
||||
const val lifecycleLiveDataKtx = "androidx.lifecycle:lifecycle-livedata-ktx:" + Versions.androidxLifecycle
|
||||
const val lifecycleRuntimeKtx = "androidx.lifecycle:lifecycle-runtime-ktx:" + Versions.androidxLifecycle
|
||||
const val lifecycleViewModelKtx = "androidx.lifecycle:lifecycle-viewmodel-ktx:" + Versions.androidxLifecycle
|
||||
}
|
||||
|
||||
object Compose {
|
||||
const val animation = "androidx.compose.animation:animation:" + Versions.compose
|
||||
const val coil = "io.coil-kt:coil-compose:" + Versions.coil
|
||||
const val foundation = "androidx.compose.foundation:foundation:" + Versions.composeFoundation
|
||||
const val material = "androidx.compose.material:material:" + Versions.compose
|
||||
const val ui = "androidx.compose.ui:ui:" + Versions.compose
|
||||
const val uiTooling = "androidx.compose.ui:ui-tooling:" + Versions.compose
|
||||
const val constraintLayout = "androidx.constraintlayout:constraintlayout-compose:" + Versions.composeConstraint
|
||||
}
|
||||
|
||||
object Firebase {
|
||||
const val bom = "com.google.firebase:firebase-bom:" + Versions.firebase
|
||||
const val firebaseAnalytics = "com.google.firebase:firebase-analytics-ktx"
|
||||
const val firebaseCrashlytics = "com.google.firebase:firebase-crashlytics-ktx"
|
||||
}
|
||||
|
||||
object Library {
|
||||
const val accompanistAppCompatTheme = "com.google.accompanist:accompanist-appcompat-theme:" + Versions.accompanist
|
||||
const val accompanistSystemUiController =
|
||||
"com.google.accompanist:accompanist-systemuicontroller:" + Versions.accompanist
|
||||
const val accompanistWebView = "com.google.accompanist:accompanist-webview:" + Versions.accompanist
|
||||
const val amplitude = "com.amplitude:android-sdk:" + Versions.amplitude
|
||||
const val appsflyer = "com.appsflyer:af-android-sdk:" + Versions.appsflyer
|
||||
const val armadillo = "at.favre.lib:armadillo:" + Versions.armadillo
|
||||
const val coil = "io.coil-kt:coil:" + Versions.coil
|
||||
const val composeShimmer = "com.valentinilk.shimmer:compose-shimmer:" + Versions.composeShimmer
|
||||
const val coroutine = "org.jetbrains.kotlinx:kotlinx-coroutines-core:" + Versions.coroutine
|
||||
const val desugarJdkLibs = "com.android.tools:desugar_jdk_libs:" + Versions.desugarJdkLibs
|
||||
const val googlePlayCore = "com.google.android.play:core:" + Versions.googlePlayCore
|
||||
const val googlePlayCoreKtx = "com.google.android.play:core-ktx:" + Versions.googlePlayCoreKtx
|
||||
const val googlePlayServicesWallet =
|
||||
"com.google.android.gms:play-services-wallet:" + Versions.googlePlayServicesWallet
|
||||
const val hilt = "com.google.dagger:hilt-android:" + Versions.hilt
|
||||
const val hiltCore = "com.google.dagger:hilt-core:" + Versions.hilt
|
||||
const val hiltKapt = "com.google.dagger:hilt-compiler:" + Versions.hilt
|
||||
const val jodatime = "joda-time:joda-time:" + Versions.jodatime
|
||||
const val kotsonGsonExt = "com.github.salomonbrys.kotson:kotson:" + Versions.kotsonGsonExt
|
||||
const val lottie = "com.airbnb.android:lottie:" + Versions.lottie
|
||||
const val materialComponent = "com.google.android.material:material:" + Versions.googleMaterialComponent
|
||||
const val moshi = "com.squareup.moshi:moshi:" + Versions.moshi
|
||||
const val moshiKotlin = "com.squareup.moshi:moshi-kotlin:" + Versions.moshi
|
||||
const val okHttp = "com.squareup.okhttp3:okhttp:" + Versions.okhttp
|
||||
const val okHttpLogging = "com.squareup.okhttp3:logging-interceptor:" + Versions.okhttp
|
||||
const val otaliastudiosCameraView = "com.otaliastudios:cameraview:" + Versions.otaliastudiosCameraView
|
||||
const val shopifyBuySdk = "com.shopify.mobilebuysdk:buy3:" + Versions.shopifyBuySdk
|
||||
const val spongecastleCryptoCore = "com.madgag.spongycastle:core:" + Versions.spongycastleCryptoCore
|
||||
const val reKotlin = "org.rekotlin:rekotlin:" + Versions.rekotlin
|
||||
const val retrofit = "com.squareup.retrofit2:retrofit:" + Versions.retrofit
|
||||
const val retrofitMoshiConverter = "com.squareup.retrofit2:converter-moshi:" + Versions.retrofitMoshiConverter
|
||||
const val timber = "com.jakewharton.timber:timber:" + Versions.timber
|
||||
const val viewBindingDelegate =
|
||||
"com.github.kirich1409:viewbindingpropertydelegate-noreflection:" + Versions.viewBindingDelegate
|
||||
const val xmlShimmer = "com.github.skydoves:androidveil:" + Versions.xmlShimmer
|
||||
const val zendeskChat = "com.zendesk:chat:" + Versions.zendeskChat
|
||||
const val zendeskMessaging = "com.zendesk:messaging:" + Versions.zendeskMessaging
|
||||
const val zxingQrBarcodeScanner = "me.dm7.barcodescanner:zxing:" + Versions.zxingQrBarcodeScanner
|
||||
const val zxingQrCore = "com.google.zxing:core:" + Versions.zxingQrCode
|
||||
const val mviCoreWatcher = "com.github.badoo.mvicore:mvicore-diff:" + Versions.mviCore
|
||||
const val kotlinSerialization = "org.jetbrains.kotlinx:kotlinx-serialization-json:" + Versions.kotlinSerialization
|
||||
}
|
||||
|
||||
object Tangem {
|
||||
const val blockchain = "com.tangem:blockchain:" + Versions.tangemBlockchainSdk
|
||||
const val cardAndroid = "com.tangem.tangem-sdk-kotlin:android:" + Versions.tangemCardSdk
|
||||
const val cardCore = "com.tangem.tangem-sdk-kotlin:core:" + Versions.tangemCardSdk
|
||||
}
|
||||
|
||||
object Tools {
|
||||
const val composeDetektRules = "ru.kode:detekt-rules-compose:" + Versions.composeDetektRules
|
||||
const val formattingDetektRules = "io.gitlab.arturbosch.detekt:detekt-formatting:" + Versions.formattingDetektRules
|
||||
}
|
||||
|
||||
object Test {
|
||||
const val espresso = "androidx.test.espresso:espresso-core:" + Versions.espresso
|
||||
const val junit = "junit:junit:" + Versions.junit
|
||||
const val junitAndroidExt = "androidx.test.ext:junit:" + Versions.junitAndroidExt
|
||||
const val truth = "com.google.truth:truth:" + Versions.truth
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
object Versions {
|
||||
// region Classpath
|
||||
const val androidGradlePlugin = "7.1.3"
|
||||
const val androidMavenGradlePlugin = "2.1"
|
||||
const val googleFirebaseCrashlytics = "2.7.1"
|
||||
const val googleServices = "4.3.10"
|
||||
const val kotlin = "1.7.10"
|
||||
// endregion Classpath
|
||||
|
||||
// region AndroidX
|
||||
const val androidxActivityCompose = "1.5.0"
|
||||
const val androidxAppCompat = "1.5.1"
|
||||
const val androidxBrowser = "1.4.0"
|
||||
const val androidxConstraintLayout = "2.1.4"
|
||||
const val androidxCore = "1.9.0"
|
||||
const val androidxFragment = "1.5.3"
|
||||
const val androidxLifecycle = "2.5.1"
|
||||
// endregion AndroidX
|
||||
|
||||
// region Compose
|
||||
const val compose = "1.3.1"
|
||||
const val composeFoundation = "1.3.1"
|
||||
const val composeConstraint = "1.0.1"
|
||||
// endregion Compose
|
||||
|
||||
// region Other libraries
|
||||
const val accompanist = "0.28.0"
|
||||
const val amplitude = "2.36.1"
|
||||
const val appsflyer = "6.5.1"
|
||||
const val armadillo = "0.9.0"
|
||||
const val coil = "2.1.0"
|
||||
const val composeShimmer = "1.0.3"
|
||||
const val coroutine = "1.5.2"
|
||||
const val desugarJdkLibs = "1.1.5"
|
||||
const val firebase = "26.0.0"
|
||||
const val googleMaterialComponent = "1.6.1"
|
||||
const val googlePlayCore = "1.10.3"
|
||||
const val googlePlayCoreKtx = "1.8.1"
|
||||
const val googlePlayServicesWallet = "19.1.0"
|
||||
const val hilt = "2.44"
|
||||
const val jodatime = "2.12.1"
|
||||
const val kotsonGsonExt = "2.5.0"
|
||||
const val lottie = "3.4.0"
|
||||
const val moshi = "1.12.0"
|
||||
const val okhttp = "4.9.3"
|
||||
const val otaliastudiosCameraView = "2.6.3"
|
||||
const val rekotlin = "1.0.4"
|
||||
const val retrofit = "2.8.1"
|
||||
const val retrofitMoshiConverter = "2.6.0"
|
||||
const val shopifyBuySdk = "12.0.0"
|
||||
const val spongycastleCryptoCore = "1.58.0.0"
|
||||
const val timber = "4.7.1"
|
||||
const val viewBindingDelegate = "1.5.6"
|
||||
const val xmlShimmer = "1.1.3"
|
||||
const val zendeskChat = "3.3.5"
|
||||
const val zendeskMessaging = "5.2.4"
|
||||
const val zxingQrBarcodeScanner = "1.9.8"
|
||||
const val zxingQrCode = "3.3.3"
|
||||
const val mviCore = "1.3.1"
|
||||
const val kotlinSerialization = "1.4.1"
|
||||
// endregion Other libraries
|
||||
|
||||
// region Tangem
|
||||
const val tangemBlockchainSdk = "develop-174"
|
||||
// const val tangemBlockchainSdk = "0.0.1" // Keep it! - used for local builds
|
||||
|
||||
const val tangemCardSdk = "develop-200"
|
||||
// const val tangemCardSdk = "0.0.1" // Keep it! - used for local builds
|
||||
// endregion Tangem
|
||||
|
||||
// region Tools
|
||||
const val composeDetektRules = "1.2.2"
|
||||
const val formattingDetektRules = "1.21.0"
|
||||
// endregion Tools
|
||||
|
||||
// region Testing
|
||||
const val espresso = "3.4.0"
|
||||
const val junit = "4.13.2"
|
||||
const val truth = "1.1.3"
|
||||
const val junitAndroidExt = "1.1.3"
|
||||
// endregion Testing
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import io.gitlab.arturbosch.detekt.Detekt
|
||||
|
||||
plugins {
|
||||
id("io.gitlab.arturbosch.detekt")
|
||||
}
|
||||
|
||||
detekt {
|
||||
parallel = true
|
||||
ignoreFailures = false
|
||||
autoCorrect = true
|
||||
buildUponDefaultConfig = true
|
||||
config.setFrom(rootProject.files("config/detekt/detekt.yml"))
|
||||
}
|
||||
|
||||
tasks.withType<Detekt> {
|
||||
include("**/*.kt")
|
||||
exclude("**/resources/**", "**/build/**")
|
||||
reports {
|
||||
sarif {
|
||||
required.set(false)
|
||||
}
|
||||
txt {
|
||||
required.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
detektPlugins(Tools.composeDetektRules)
|
||||
detektPlugins(Tools.formattingDetektRules)
|
||||
}
|
||||
|
|
@ -1,9 +1,4 @@
|
|||
plugins {
|
||||
id("java-library")
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
id("configuration")
|
||||
}
|
||||
|
|
@ -1,47 +1,16 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
isCoreLibraryDesugaringEnabled = false
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
create("debug_beta") {
|
||||
initWith(getByName("release"))
|
||||
BuildConfigFieldFactory(
|
||||
fields = listOf(
|
||||
Field.Environment("release"),
|
||||
Field.TestActionEnabled(true),
|
||||
Field.LogEnabled(true),
|
||||
),
|
||||
builder = ::buildConfigField,
|
||||
).create()
|
||||
}
|
||||
}
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Core shouldn't depends on core, but in case with utils and logging its necessary */
|
||||
implementation(project(":core:utils"))
|
||||
|
|
|
|||
|
|
@ -1,40 +1,9 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
isCoreLibraryDesugaringEnabled = false
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
create("debug_beta") {
|
||||
initWith(getByName("release"))
|
||||
BuildConfigFieldFactory(
|
||||
fields = listOf(
|
||||
Field.Environment("release"),
|
||||
Field.TestActionEnabled(true),
|
||||
Field.LogEnabled(true),
|
||||
),
|
||||
builder = ::buildConfigField,
|
||||
).create()
|
||||
}
|
||||
}
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
@ -44,30 +13,31 @@ dependencies {
|
|||
implementation(project(":libs:auth"))
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(Tangem.blockchain)
|
||||
implementation(Tangem.cardCore)
|
||||
implementation(deps.tangem.blockchain)
|
||||
implementation(deps.tangem.card.core)
|
||||
|
||||
/** DI */
|
||||
implementation(Library.hilt)
|
||||
kapt(Library.hiltKapt)
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Coroutines */
|
||||
implementation(Library.coroutine)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
|
||||
/** Logging */
|
||||
implementation(Library.timber)
|
||||
implementation(deps.timber)
|
||||
|
||||
/** Network */
|
||||
implementation(Library.retrofit)
|
||||
implementation(Library.retrofitMoshiConverter)
|
||||
implementation(Library.moshi)
|
||||
implementation(Library.moshiKotlin)
|
||||
implementation(Library.okHttp)
|
||||
implementation(Library.okHttpLogging)
|
||||
implementation(deps.krateSharedPref)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.okHttp)
|
||||
implementation(deps.okHttp.logging)
|
||||
implementation(deps.retrofit)
|
||||
implementation(deps.retrofit.moshi)
|
||||
|
||||
/** Time */
|
||||
implementation(Library.jodatime)
|
||||
implementation(deps.jodatime)
|
||||
|
||||
/** Security */
|
||||
implementation(Library.spongecastleCryptoCore)
|
||||
implementation(deps.spongecastle.core)
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.datasource.asset
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.BufferedReader
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Implementation of asset file reader
|
||||
*
|
||||
* @property context application context
|
||||
*/
|
||||
internal class AndroidAssetReader @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) : AssetReader {
|
||||
|
||||
override fun readJson(fileName: String): String {
|
||||
return context.assets
|
||||
.open("$fileName.json")
|
||||
.bufferedReader()
|
||||
.use(BufferedReader::readText)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.asset
|
||||
|
||||
/**
|
||||
* Asset file reader
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface AssetReader {
|
||||
|
||||
/** Read content of json file [fileName] from asset */
|
||||
fun readJson(fileName: String): String
|
||||
}
|
||||
|
|
@ -102,6 +102,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
saltPayAuthToken = configValues.saltPay.credentials.basicAuthToken,
|
||||
nowNodeCredentials = NowNodeCredentials(configValues.nowNodesApiKey),
|
||||
getBlockCredentials = GetBlockCredentials(configValues.getBlockApiKey),
|
||||
kaspaSecondaryApiUrl = configValues.kaspaSecondaryApiUrl,
|
||||
),
|
||||
appsFlyerDevKey = configValues.appsFlyer.appsFlyerDevKey,
|
||||
amplitudeApiKey = configValues.amplitudeApiKey,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package com.tangem.datasource.config
|
|||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.datasource.config.models.ConfigModel
|
||||
import com.tangem.datasource.config.models.ConfigValueModel
|
||||
import com.tangem.datasource.config.models.FeatureModel
|
||||
import com.tangem.datasource.utils.AssetReader
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
|
|
@ -25,8 +25,8 @@ class FeaturesLocalLoader(
|
|||
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
|
||||
val valuesAdapter: JsonAdapter<ConfigValueModel> = moshi.adapter(ConfigValueModel::class.java)
|
||||
|
||||
val jsonFeatures = assetReader.readAssetAsString(featuresName)
|
||||
val jsonConfigValues = assetReader.readAssetAsString(configValuesName)
|
||||
val jsonFeatures = assetReader.readJson(featuresName)
|
||||
val jsonConfigValues = assetReader.readJson(configValuesName)
|
||||
|
||||
ConfigModel(featureAdapter.fromJson(jsonFeatures), valuesAdapter.fromJson(jsonConfigValues))
|
||||
} catch (ex: Exception) {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class ConfigValueModel(
|
|||
val tronGridApiKey: String,
|
||||
val amplitudeApiKey: String,
|
||||
val swapReferrerAccount: SwapReferrerAccount?,
|
||||
val kaspaSecondaryApiUrl: String,
|
||||
)
|
||||
|
||||
data class AppsFlyer(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -36,9 +35,9 @@ internal class AndroidNetworkConnectionManager @Inject constructor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : NetworkConnectionManager {
|
||||
|
||||
override val connectionStatus: StateFlow<ConnectionStatus> get() = _connectionStatus
|
||||
override val isOnline: Boolean get() = _isOnline.value
|
||||
|
||||
private val _connectionStatus = MutableStateFlow(ConnectionStatus.OFFLINE)
|
||||
private val _isOnline = MutableStateFlow(value = false)
|
||||
private val callbacks = NetworkConnectionManagerCallbacks()
|
||||
private val receiver = NetworkConnectionBroadcastReceiver()
|
||||
|
||||
|
|
@ -78,28 +77,26 @@ internal class AndroidNetworkConnectionManager @Inject constructor(
|
|||
|
||||
CoroutineScope(SupervisorJob()).launch(dispatchers.io) {
|
||||
try {
|
||||
val status = getCurrentStatus()
|
||||
val status = isConnected()
|
||||
|
||||
_connectionStatus.emit(value = status)
|
||||
Timber.i("Status changed to $status")
|
||||
_isOnline.emit(value = status)
|
||||
Timber.i("Connection status is ${if (status) "ONLINE" else "OFFLINE"}")
|
||||
} finally {
|
||||
pendingResult.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCurrentStatus(): ConnectionStatus {
|
||||
connectivityManager ?: return ConnectionStatus.OFFLINE
|
||||
private fun isConnected(): Boolean {
|
||||
connectivityManager ?: return false
|
||||
|
||||
val isConnected = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||
capabilities != null && capabilities.hasNetworkTransport()
|
||||
} else {
|
||||
val networkInfo = connectivityManager.activeNetworkInfo
|
||||
networkInfo != null && networkInfo.isConnectedOrConnecting
|
||||
}
|
||||
|
||||
return if (isConnected) ConnectionStatus.ONLINE else ConnectionStatus.OFFLINE
|
||||
}
|
||||
|
||||
private fun NetworkCapabilities.hasNetworkTransport(): Boolean {
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.datasource.connection
|
||||
|
||||
/**
|
||||
* Network connection status
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
enum class ConnectionStatus {
|
||||
ONLINE, OFFLINE
|
||||
}
|
||||
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