Updated on 2026-08-14
This commit is contained in:
commit
ca49abf2b1
892 changed files with 17979 additions and 6555 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -9,6 +9,9 @@ local.properties
|
||||||
|
|
||||||
# Google services
|
# Google services
|
||||||
|
|
||||||
|
app/src/debug/google-services.json
|
||||||
|
app/src/internal/google-services.json
|
||||||
|
app/src/external/google-services.json
|
||||||
|
|
||||||
# Gradle generated files
|
# Gradle generated files
|
||||||
.gradle
|
.gradle
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,8 @@ dependencies {
|
||||||
implementation(projects.domain.networks)
|
implementation(projects.domain.networks)
|
||||||
implementation(projects.domain.quotes)
|
implementation(projects.domain.quotes)
|
||||||
implementation(projects.domain.notifications)
|
implementation(projects.domain.notifications)
|
||||||
|
implementation(projects.domain.notifications.models)
|
||||||
|
implementation(projects.domain.notifications.toggles)
|
||||||
|
|
||||||
implementation(projects.common)
|
implementation(projects.common)
|
||||||
implementation(projects.common.routing)
|
implementation(projects.common.routing)
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:host="*"
|
android:host="onramp"
|
||||||
android:scheme="tangem" />
|
android:scheme="tangem" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
|
@ -150,7 +150,29 @@
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:host="*"
|
android:host="referral"
|
||||||
|
android:scheme="tangem" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="main"
|
||||||
|
android:scheme="tangem" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="token"
|
||||||
android:scheme="tangem" />
|
android:scheme="tangem" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit a20134ee4be4d7e1e34c6f44089b3cb0713eacc3
|
Subproject commit 4ec5de66321afa82c04104746015ea9e6bc9fe64
|
||||||
|
|
@ -15,6 +15,7 @@ import com.tangem.core.navigation.share.ShareManager
|
||||||
import com.tangem.core.navigation.url.UrlOpener
|
import com.tangem.core.navigation.url.UrlOpener
|
||||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||||
import com.tangem.data.card.TransactionSignerFactory
|
import com.tangem.data.card.TransactionSignerFactory
|
||||||
|
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||||
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
|
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
|
||||||
|
|
@ -142,4 +143,6 @@ interface ApplicationEntryPoint {
|
||||||
fun getOnlineCardVerifier(): OnlineCardVerifier
|
fun getOnlineCardVerifier(): OnlineCardVerifier
|
||||||
|
|
||||||
fun getUserWalletBuilderFactory(): UserWalletBuilder.Factory
|
fun getUserWalletBuilderFactory(): UserWalletBuilder.Factory
|
||||||
|
|
||||||
|
fun getApiConfigsManager(): ApiConfigsManager
|
||||||
}
|
}
|
||||||
|
|
@ -19,16 +19,19 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.flowWithLifecycle
|
import androidx.lifecycle.flowWithLifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
|
import com.tangem.common.routing.RoutingFeatureToggle
|
||||||
import com.tangem.common.routing.entity.SerializableIntent
|
import com.tangem.common.routing.entity.SerializableIntent
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.decompose.context.AppComponentContext
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
import com.tangem.core.decompose.di.RootAppComponentContext
|
import com.tangem.core.decompose.di.RootAppComponentContext
|
||||||
|
import com.tangem.core.deeplink.DEEPLINK_KEY
|
||||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||||
import com.tangem.core.navigation.email.EmailSender
|
import com.tangem.core.navigation.email.EmailSender
|
||||||
import com.tangem.core.ui.UiDependencies
|
import com.tangem.core.ui.UiDependencies
|
||||||
|
|
@ -48,6 +51,7 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||||
|
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||||
import com.tangem.google.GoogleServicesHelper
|
import com.tangem.google.GoogleServicesHelper
|
||||||
import com.tangem.operations.backup.BackupService
|
import com.tangem.operations.backup.BackupService
|
||||||
import com.tangem.sdk.api.BackupServiceHolder
|
import com.tangem.sdk.api.BackupServiceHolder
|
||||||
|
|
@ -67,6 +71,7 @@ import com.tangem.tap.proxy.AppStateHolder
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||||
import com.tangem.tap.routing.component.RoutingComponent
|
import com.tangem.tap.routing.component.RoutingComponent
|
||||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||||
|
import com.tangem.tap.routing.utils.DeepLinkFactory
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
@ -171,6 +176,15 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
@Inject
|
@Inject
|
||||||
internal lateinit var defaultDeviceFlipDetector: DefaultDeviceFlipDetector
|
internal lateinit var defaultDeviceFlipDetector: DefaultDeviceFlipDetector
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
internal lateinit var routingFeatureToggle: RoutingFeatureToggle
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
internal lateinit var deeplinkFactory: DeepLinkFactory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
internal lateinit var walletConnectFeatureToggles: WalletConnectFeatureToggles
|
||||||
|
|
||||||
internal val viewModel: MainViewModel by viewModels()
|
internal val viewModel: MainViewModel by viewModels()
|
||||||
|
|
||||||
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
||||||
|
|
@ -223,9 +237,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
sendStakingUnsubmittedHashes()
|
sendStakingUnsubmittedHashes()
|
||||||
checkGoogleServicesAvailability()
|
checkGoogleServicesAvailability()
|
||||||
|
|
||||||
if (intent != null && savedInstanceState == null) {
|
if (routingFeatureToggle.isDeepLinkNavigationEnabled.not() && intent != null && savedInstanceState == null) {
|
||||||
// handle intent only on start, not on recreate
|
// handle intent only on start, not on recreate
|
||||||
deepLinksRegistry.launch(intent)
|
handleDeepLink(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
lifecycle.addObserver(WindowObscurationObserver)
|
lifecycle.addObserver(WindowObscurationObserver)
|
||||||
|
|
@ -341,7 +355,10 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets }
|
val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets }
|
||||||
intentProcessor.addHandler(OnPushClickedIntentHandler(analyticsEventsHandler))
|
intentProcessor.addHandler(OnPushClickedIntentHandler(analyticsEventsHandler))
|
||||||
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope))
|
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope))
|
||||||
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
|
|
||||||
|
if (!walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
|
||||||
|
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateAppTheme(appThemeMode: AppThemeMode) {
|
private fun updateAppTheme(appThemeMode: AppThemeMode) {
|
||||||
|
|
@ -374,7 +391,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
deepLinksRegistry.launch(intent)
|
handleDeepLink(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -455,9 +472,24 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (routingFeatureToggle.isDeepLinkNavigationEnabled && intent != null) {
|
||||||
|
handleDeepLink(intent)
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.checkForUnfinishedBackup()
|
viewModel.checkForUnfinishedBackup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleDeepLink(intent: Intent) {
|
||||||
|
if (routingFeatureToggle.isDeepLinkNavigationEnabled) {
|
||||||
|
val deepLinkExtras = intent.getStringExtra(DEEPLINK_KEY)?.toUri()
|
||||||
|
val receivedDeepLink = intent.data ?: deepLinkExtras ?: return
|
||||||
|
|
||||||
|
deeplinkFactory.handleDeeplink(deeplinkUri = receivedDeepLink, coroutineScope = lifecycleScope)
|
||||||
|
} else {
|
||||||
|
deepLinksRegistry.launch(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun observePolkadotAccountHealthCheck() {
|
private fun observePolkadotAccountHealthCheck() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
getPolkadotCheckHasResetUseCase()
|
getPolkadotCheckHasResetUseCase()
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import com.tangem.core.navigation.settings.SettingsManager
|
||||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||||
import com.tangem.data.card.TransactionSignerFactory
|
import com.tangem.data.card.TransactionSignerFactory
|
||||||
import com.tangem.datasource.api.common.MoshiConverter
|
import com.tangem.datasource.api.common.MoshiConverter
|
||||||
|
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||||
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
||||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||||
|
|
@ -228,6 +229,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
||||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory
|
private val userWalletBuilderFactory: UserWalletBuilder.Factory
|
||||||
get() = entryPoint.getUserWalletBuilderFactory()
|
get() = entryPoint.getUserWalletBuilderFactory()
|
||||||
|
|
||||||
|
private val apiConfigsManager: ApiConfigsManager
|
||||||
|
get() = entryPoint.getApiConfigsManager()
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
private val appScope = MainScope()
|
private val appScope = MainScope()
|
||||||
|
|
@ -275,6 +279,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
|
apiConfigsManager.initialize()
|
||||||
|
|
||||||
store = createReduxStore()
|
store = createReduxStore()
|
||||||
|
|
||||||
tangemAppLoggerInitializer.initialize()
|
tangemAppLoggerInitializer.initialize()
|
||||||
|
|
@ -285,12 +291,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
||||||
// We need to initialize the toggles and excludedBlockchainsManager before the MainActivity starts using them.
|
// We need to initialize the toggles and excludedBlockchainsManager before the MainActivity starts using them.
|
||||||
runBlocking {
|
runBlocking {
|
||||||
awaitAll(
|
awaitAll(
|
||||||
async {
|
async { featureTogglesManager.init() },
|
||||||
featureTogglesManager.init()
|
async { excludedBlockchainsManager.init() },
|
||||||
},
|
|
||||||
async {
|
|
||||||
excludedBlockchainsManager.init()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
|
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,6 @@ sealed class AnalyticsParam {
|
||||||
const val PERMISSION_TYPE = "Permission Type"
|
const val PERMISSION_TYPE = "Permission Type"
|
||||||
const val PRODUCT_TYPE = "Product Type"
|
const val PRODUCT_TYPE = "Product Type"
|
||||||
const val FIRMWARE = "Firmware"
|
const val FIRMWARE = "Firmware"
|
||||||
const val USER_WALLET_ID = "User Wallet ID"
|
|
||||||
const val CURRENCY = "Currency"
|
const val CURRENCY = "Currency"
|
||||||
const val ERROR_DESCRIPTION = "Error Description"
|
const val ERROR_DESCRIPTION = "Error Description"
|
||||||
const val ERROR_CODE = "Error Code"
|
const val ERROR_CODE = "Error Code"
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,23 @@
|
||||||
package com.tangem.tap.common.analytics.handlers.amplitude
|
package com.tangem.tap.common.analytics.handlers.amplitude
|
||||||
|
|
||||||
import com.tangem.core.analytics.api.AnalyticsHandler
|
import com.tangem.core.analytics.api.AnalyticsHandler
|
||||||
|
import com.tangem.core.analytics.api.AnalyticsUserIdHandler
|
||||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||||
|
|
||||||
class AmplitudeAnalyticsHandler(
|
class AmplitudeAnalyticsHandler(
|
||||||
private val client: AmplitudeAnalyticsClient,
|
private val client: AmplitudeAnalyticsClient,
|
||||||
) : AnalyticsHandler {
|
) : AnalyticsHandler, AnalyticsUserIdHandler {
|
||||||
|
|
||||||
override fun id(): String = ID
|
override fun id(): String = ID
|
||||||
|
|
||||||
|
override fun setUserId(userId: String) {
|
||||||
|
client.setUserId(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearUserId() {
|
||||||
|
client.clearUserId()
|
||||||
|
}
|
||||||
|
|
||||||
override fun send(eventId: String, params: Map<String, String>) {
|
override fun send(eventId: String, params: Map<String, String>) {
|
||||||
client.logEvent(eventId, params)
|
client.logEvent(eventId, params)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@ import android.app.Application
|
||||||
import com.amplitude.api.Amplitude
|
import com.amplitude.api.Amplitude
|
||||||
import com.amplitude.api.AmplitudeClient
|
import com.amplitude.api.AmplitudeClient
|
||||||
import com.tangem.core.analytics.api.EventLogger
|
import com.tangem.core.analytics.api.EventLogger
|
||||||
|
import com.tangem.core.analytics.api.UserIdHolder
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
interface AmplitudeAnalyticsClient : EventLogger
|
interface AmplitudeAnalyticsClient : EventLogger, UserIdHolder
|
||||||
|
|
||||||
internal class AmplitudeClient(
|
internal class AmplitudeClient(
|
||||||
application: Application,
|
application: Application,
|
||||||
|
|
@ -24,6 +25,14 @@ internal class AmplitudeClient(
|
||||||
client.enableForegroundTracking(application)
|
client.enableForegroundTracking(application)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setUserId(userId: String) {
|
||||||
|
client.setUserId(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearUserId() {
|
||||||
|
client.setUserId(null)
|
||||||
|
}
|
||||||
|
|
||||||
override fun logEvent(event: String, params: Map<String, String>) {
|
override fun logEvent(event: String, params: Map<String, String>) {
|
||||||
client.logEvent(event, ParamsToJSONObjectConverter().convert(params))
|
client.logEvent(event, ParamsToJSONObjectConverter().convert(params))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,16 @@ internal class AmplitudeLogClient(
|
||||||
|
|
||||||
private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(AmplitudeAnalyticsHandler.ID, jsonConverter)
|
private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(AmplitudeAnalyticsHandler.ID, jsonConverter)
|
||||||
|
|
||||||
|
private var userId: String? = null
|
||||||
|
|
||||||
|
override fun setUserId(userId: String) {
|
||||||
|
this.userId = userId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearUserId() {
|
||||||
|
this.userId = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun logEvent(event: String, params: Map<String, String>) {
|
override fun logEvent(event: String, params: Map<String, String>) {
|
||||||
logger.logEvent(event, params)
|
logger.logEvent(event, params)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.common.analytics.handlers.firebase
|
||||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||||
import com.tangem.core.analytics.api.AnalyticsHandler
|
import com.tangem.core.analytics.api.AnalyticsHandler
|
||||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||||
|
import com.tangem.core.analytics.api.AnalyticsUserIdHandler
|
||||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||||
|
|
@ -10,12 +11,20 @@ import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter
|
||||||
|
|
||||||
class FirebaseAnalyticsHandler(
|
class FirebaseAnalyticsHandler(
|
||||||
private val client: FirebaseAnalyticsClient,
|
private val client: FirebaseAnalyticsClient,
|
||||||
) : AnalyticsHandler, AnalyticsErrorHandler, AnalyticsExceptionHandler {
|
) : AnalyticsHandler, AnalyticsErrorHandler, AnalyticsExceptionHandler, AnalyticsUserIdHandler {
|
||||||
|
|
||||||
private val errorConverter = AnalyticsErrorConverter()
|
private val errorConverter = AnalyticsErrorConverter()
|
||||||
|
|
||||||
override fun id(): String = ID
|
override fun id(): String = ID
|
||||||
|
|
||||||
|
override fun setUserId(userId: String) {
|
||||||
|
client.setUserId(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearUserId() {
|
||||||
|
client.clearUserId()
|
||||||
|
}
|
||||||
|
|
||||||
override fun send(eventId: String, params: Map<String, String>) {
|
override fun send(eventId: String, params: Map<String, String>) {
|
||||||
client.logEvent(eventId, params)
|
client.logEvent(eventId, params)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,12 @@ import com.google.firebase.crashlytics.recordException
|
||||||
import com.google.firebase.ktx.Firebase
|
import com.google.firebase.ktx.Firebase
|
||||||
import com.tangem.core.analytics.api.ExceptionLogger
|
import com.tangem.core.analytics.api.ExceptionLogger
|
||||||
import com.tangem.core.analytics.api.EventLogger
|
import com.tangem.core.analytics.api.EventLogger
|
||||||
|
import com.tangem.core.analytics.api.UserIdHolder
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
interface FirebaseAnalyticsClient : EventLogger, ExceptionLogger
|
interface FirebaseAnalyticsClient : EventLogger, ExceptionLogger, UserIdHolder
|
||||||
|
|
||||||
internal class FirebaseClient : FirebaseAnalyticsClient {
|
internal class FirebaseClient : FirebaseAnalyticsClient {
|
||||||
|
|
||||||
|
|
@ -21,6 +22,14 @@ internal class FirebaseClient : FirebaseAnalyticsClient {
|
||||||
|
|
||||||
private val eventConverter = FirebaseAnalyticsEventConverter()
|
private val eventConverter = FirebaseAnalyticsEventConverter()
|
||||||
|
|
||||||
|
override fun setUserId(userId: String) {
|
||||||
|
Firebase.analytics.setUserId(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearUserId() {
|
||||||
|
Firebase.analytics.setUserId(null)
|
||||||
|
}
|
||||||
|
|
||||||
override fun logEvent(event: String, params: Map<String, String>) {
|
override fun logEvent(event: String, params: Map<String, String>) {
|
||||||
fbAnalytics.logEvent(
|
fbAnalytics.logEvent(
|
||||||
eventConverter.convertEventName(event),
|
eventConverter.convertEventName(event),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,15 @@ internal class FirebaseLogClient(
|
||||||
) : FirebaseAnalyticsClient {
|
) : FirebaseAnalyticsClient {
|
||||||
|
|
||||||
private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(FirebaseAnalyticsHandler.ID, jsonConverter)
|
private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(FirebaseAnalyticsHandler.ID, jsonConverter)
|
||||||
|
private var userId: String? = null
|
||||||
|
|
||||||
|
override fun setUserId(userId: String) {
|
||||||
|
this.userId = userId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearUserId() {
|
||||||
|
this.userId = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun logEvent(event: String, params: Map<String, String>) {
|
override fun logEvent(event: String, params: Map<String, String>) {
|
||||||
logger.logEvent(event, params)
|
logger.logEvent(event, params)
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,6 @@ class CardContextInterceptor(
|
||||||
params[AnalyticsParam.BATCH] = card.batchId
|
params[AnalyticsParam.BATCH] = card.batchId
|
||||||
params[AnalyticsParam.PRODUCT_TYPE] = getProductType()
|
params[AnalyticsParam.PRODUCT_TYPE] = getProductType()
|
||||||
params[AnalyticsParam.FIRMWARE] = card.firmwareVersion.stringValue
|
params[AnalyticsParam.FIRMWARE] = card.firmwareVersion.stringValue
|
||||||
if (userWalletId != null) {
|
|
||||||
params[AnalyticsParam.USER_WALLET_ID] = userWalletId.stringValue
|
|
||||||
}
|
|
||||||
|
|
||||||
ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)?.let {
|
ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)?.let {
|
||||||
params[AnalyticsParam.CURRENCY] = it.value
|
params[AnalyticsParam.CURRENCY] = it.value
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.common.extensions
|
||||||
|
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||||
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
|
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -12,6 +13,11 @@ import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterc
|
||||||
* Sets the new context
|
* Sets the new context
|
||||||
*/
|
*/
|
||||||
fun Analytics.setContext(scanResponse: ScanResponse) {
|
fun Analytics.setContext(scanResponse: ScanResponse) {
|
||||||
|
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||||
|
if (userWalletId != null) {
|
||||||
|
setUserId(userWalletId.stringValue)
|
||||||
|
}
|
||||||
|
|
||||||
addParamsInterceptor(LinkedCardContextInterceptor(scanResponse))
|
addParamsInterceptor(LinkedCardContextInterceptor(scanResponse))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,6 +25,7 @@ fun Analytics.setContext(scanResponse: ScanResponse) {
|
||||||
* Erases the context
|
* Erases the context
|
||||||
*/
|
*/
|
||||||
fun Analytics.eraseContext() {
|
fun Analytics.eraseContext() {
|
||||||
|
clearUserId()
|
||||||
removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,6 +33,11 @@ fun Analytics.eraseContext() {
|
||||||
* Adds a new context and keeps a previous context as the parent of the new one
|
* Adds a new context and keeps a previous context as the parent of the new one
|
||||||
*/
|
*/
|
||||||
fun Analytics.addContext(scanResponse: ScanResponse) {
|
fun Analytics.addContext(scanResponse: ScanResponse) {
|
||||||
|
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||||
|
if (userWalletId != null) {
|
||||||
|
setUserId(userWalletId.stringValue)
|
||||||
|
}
|
||||||
|
|
||||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
|
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
|
||||||
val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext)
|
val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,5 @@
|
||||||
package com.tangem.tap.common.extensions
|
package com.tangem.tap.common.extensions
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.Color
|
|
||||||
import com.google.zxing.BarcodeFormat
|
|
||||||
import com.google.zxing.EncodeHintType
|
|
||||||
import com.google.zxing.qrcode.QRCodeWriter
|
|
||||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
|
||||||
import java.util.Hashtable
|
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
|
||||||
fun String.toQrCode(): Bitmap {
|
|
||||||
val hintMap = Hashtable<EncodeHintType, Any>()
|
|
||||||
hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage
|
|
||||||
hintMap[EncodeHintType.MARGIN] = 2
|
|
||||||
|
|
||||||
val qrCodeWriter = QRCodeWriter()
|
|
||||||
|
|
||||||
val size = 256
|
|
||||||
|
|
||||||
val bitMatrix = qrCodeWriter.encode(this, BarcodeFormat.QR_CODE, size, size, hintMap)
|
|
||||||
val width = bitMatrix.width
|
|
||||||
val bmp = Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565)
|
|
||||||
for (x in 0 until width) {
|
|
||||||
for (y in 0 until width) {
|
|
||||||
bmp.setPixel(y, x, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bmp
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.removePrefixOrNull(prefix: String): String? = when {
|
fun String.removePrefixOrNull(prefix: String): String? = when {
|
||||||
startsWith(prefix) -> substring(prefix.length)
|
startsWith(prefix) -> substring(prefix.length)
|
||||||
else -> null
|
else -> null
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import com.tangem.blockchain.common.WalletManager
|
||||||
import com.tangem.blockchain.common.address.AddressType
|
import com.tangem.blockchain.common.address.AddressType
|
||||||
import com.tangem.blockchainsdk.utils.amountToCreateAccount
|
import com.tangem.blockchainsdk.utils.amountToCreateAccount
|
||||||
import com.tangem.common.services.Result
|
import com.tangem.common.services.Result
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.tap.common.TestActions
|
import com.tangem.tap.common.TestActions
|
||||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||||
import com.tangem.tap.domain.TapError
|
import com.tangem.tap.domain.TapError
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ package com.tangem.tap.data
|
||||||
import com.google.firebase.messaging.FirebaseMessaging
|
import com.google.firebase.messaging.FirebaseMessaging
|
||||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||||
import kotlinx.coroutines.tasks.await
|
import kotlinx.coroutines.tasks.await
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class FirebasePushNotificationsTokenProvider : PushNotificationsTokenProvider {
|
internal class FirebasePushNotificationsTokenProvider @Inject constructor() : PushNotificationsTokenProvider {
|
||||||
override suspend fun getToken(): String {
|
override suspend fun getToken(): String {
|
||||||
return FirebaseMessaging.getInstance().token.await()
|
return FirebaseMessaging.getInstance().token.await()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||||
import com.tangem.domain.staking.repositories.StakingRepository
|
import com.tangem.domain.staking.repositories.StakingRepository
|
||||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
|
||||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
|
@ -70,7 +69,6 @@ internal object ManageTokensDomainModule {
|
||||||
customTokensRepository: CustomTokensRepository,
|
customTokensRepository: CustomTokensRepository,
|
||||||
walletManagersFacade: WalletManagersFacade,
|
walletManagersFacade: WalletManagersFacade,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
networksRepository: NetworksRepository,
|
|
||||||
derivationsRepository: DerivationsRepository,
|
derivationsRepository: DerivationsRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
|
|
@ -83,7 +81,6 @@ internal object ManageTokensDomainModule {
|
||||||
customTokensRepository = customTokensRepository,
|
customTokensRepository = customTokensRepository,
|
||||||
walletManagersFacade = walletManagersFacade,
|
walletManagersFacade = walletManagersFacade,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
networksRepository = networksRepository,
|
|
||||||
derivationsRepository = derivationsRepository,
|
derivationsRepository = derivationsRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
quotesRepository = quotesRepository,
|
quotesRepository = quotesRepository,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||||
import com.tangem.domain.staking.repositories.StakingRepository
|
import com.tangem.domain.staking.repositories.StakingRepository
|
||||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
|
||||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -72,7 +71,6 @@ object MarketsDomainModule {
|
||||||
derivationsRepository: DerivationsRepository,
|
derivationsRepository: DerivationsRepository,
|
||||||
marketsTokenRepository: MarketsTokenRepository,
|
marketsTokenRepository: MarketsTokenRepository,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
networksRepository: NetworksRepository,
|
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
|
|
@ -84,7 +82,6 @@ object MarketsDomainModule {
|
||||||
derivationsRepository = derivationsRepository,
|
derivationsRepository = derivationsRepository,
|
||||||
marketsTokenRepository = marketsTokenRepository,
|
marketsTokenRepository = marketsTokenRepository,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
networksRepository = networksRepository,
|
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
quotesRepository = quotesRepository,
|
quotesRepository = quotesRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
|
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||||
import com.tangem.domain.nft.*
|
import com.tangem.domain.nft.*
|
||||||
import com.tangem.domain.nft.repository.NFTRepository
|
import com.tangem.domain.nft.repository.NFTRepository
|
||||||
|
import com.tangem.domain.quotes.single.SingleQuoteFetcher
|
||||||
|
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -72,10 +75,11 @@ internal object NFTDomainModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesGetNFTNetworkStatusUseCase(networksRepository: NetworksRepository): GetNFTNetworkStatusUseCase =
|
fun providesGetNFTNetworkStatusUseCase(
|
||||||
GetNFTNetworkStatusUseCase(
|
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||||
networksRepository = networksRepository,
|
): GetNFTNetworkStatusUseCase {
|
||||||
)
|
return GetNFTNetworkStatusUseCase(singleNetworkStatusSupplier = singleNetworkStatusSupplier)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
@ -83,4 +87,53 @@ internal object NFTDomainModule {
|
||||||
GetNFTExploreUrlUseCase(
|
GetNFTExploreUrlUseCase(
|
||||||
nftRepository = nftRepository,
|
nftRepository = nftRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetNFTPriceUseCase(
|
||||||
|
nftRepository: NFTRepository,
|
||||||
|
singleQuoteSupplier: SingleQuoteSupplier,
|
||||||
|
): GetNFTPriceUseCase {
|
||||||
|
return GetNFTPriceUseCase(nftRepository, singleQuoteSupplier)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideFetchNFTPriceUseCase(
|
||||||
|
nftRepository: NFTRepository,
|
||||||
|
singleQuoteFetcher: SingleQuoteFetcher,
|
||||||
|
): FetchNFTPriceUseCase {
|
||||||
|
return FetchNFTPriceUseCase(nftRepository, singleQuoteFetcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideEnableWalletNFTUseCase(walletsRepository: WalletsRepository): EnableWalletNFTUseCase {
|
||||||
|
return EnableWalletNFTUseCase(walletsRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideDisableWalletNFTUseCase(
|
||||||
|
walletsRepository: WalletsRepository,
|
||||||
|
nftRepository: NFTRepository,
|
||||||
|
currenciesRepository: CurrenciesRepository,
|
||||||
|
): DisableWalletNFTUseCase {
|
||||||
|
return DisableWalletNFTUseCase(walletsRepository, nftRepository, currenciesRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetWalletNFTEnabledUseCase(walletsRepository: WalletsRepository): GetWalletNFTEnabledUseCase {
|
||||||
|
return GetWalletNFTEnabledUseCase(walletsRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideClearNFTCacheUseCase(
|
||||||
|
nftRepository: NFTRepository,
|
||||||
|
currenciesRepository: CurrenciesRepository,
|
||||||
|
): ObserveAndClearNFTCacheIfNeedUseCase {
|
||||||
|
return ObserveAndClearNFTCacheIfNeedUseCase(nftRepository, currenciesRepository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
|
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
import com.tangem.domain.notifications.GetApplicationIdUseCase
|
import com.tangem.domain.notifications.GetApplicationIdUseCase
|
||||||
import com.tangem.domain.notifications.GetTronFeeNotificationShowCountUseCase
|
import com.tangem.domain.notifications.GetTronFeeNotificationShowCountUseCase
|
||||||
import com.tangem.domain.notifications.IncrementNotificationsShowCountUseCase
|
import com.tangem.domain.notifications.IncrementNotificationsShowCountUseCase
|
||||||
import com.tangem.domain.notifications.SendPushTokenUseCase
|
import com.tangem.domain.notifications.SendPushTokenUseCase
|
||||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||||
|
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||||
|
import com.tangem.tap.domain.notifications.DefaultNotificationsFeatureToggles
|
||||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -28,12 +31,10 @@ internal object NotificationsDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesSendPushTokenUseCase(
|
fun providesSendPushTokenUseCase(
|
||||||
notificationsRepository: NotificationsRepository,
|
notificationsRepository: NotificationsRepository,
|
||||||
getApplicationIdUseCase: GetApplicationIdUseCase,
|
|
||||||
pushNotificationsTokenProvider: PushNotificationsTokenProvider,
|
pushNotificationsTokenProvider: PushNotificationsTokenProvider,
|
||||||
): SendPushTokenUseCase {
|
): SendPushTokenUseCase {
|
||||||
return SendPushTokenUseCase(
|
return SendPushTokenUseCase(
|
||||||
notificationsRepository = notificationsRepository,
|
notificationsRepository = notificationsRepository,
|
||||||
getApplicationIdUseCase = getApplicationIdUseCase,
|
|
||||||
pushNotificationsTokenProvider = pushNotificationsTokenProvider,
|
pushNotificationsTokenProvider = pushNotificationsTokenProvider,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -57,4 +58,10 @@ internal object NotificationsDomainModule {
|
||||||
notificationsRepository = notificationsRepository,
|
notificationsRepository = notificationsRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideNotificationsFeatureToggles(featureTogglesManager: FeatureTogglesManager): NotificationsFeatureToggles {
|
||||||
|
return DefaultNotificationsFeatureToggles(featureTogglesManager = featureTogglesManager)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
import com.tangem.domain.exchange.RampStateManager
|
import com.tangem.domain.exchange.RampStateManager
|
||||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
||||||
|
import com.tangem.domain.networks.repository.NetworksRepository
|
||||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||||
import com.tangem.domain.promo.PromoRepository
|
import com.tangem.domain.promo.PromoRepository
|
||||||
|
|
@ -18,7 +19,10 @@ import com.tangem.domain.tokens.*
|
||||||
import com.tangem.domain.tokens.operations.BaseCurrenciesStatusesOperations
|
import com.tangem.domain.tokens.operations.BaseCurrenciesStatusesOperations
|
||||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||||
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
||||||
import com.tangem.domain.tokens.repository.*
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||||
|
import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository
|
||||||
|
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
|
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
|
@ -37,7 +41,6 @@ internal object TokensDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideAddCryptoCurrenciesUseCase(
|
fun provideAddCryptoCurrenciesUseCase(
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
networksRepository: NetworksRepository,
|
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
|
|
@ -47,7 +50,6 @@ internal object TokensDomainModule {
|
||||||
): AddCryptoCurrenciesUseCase {
|
): AddCryptoCurrenciesUseCase {
|
||||||
return AddCryptoCurrenciesUseCase(
|
return AddCryptoCurrenciesUseCase(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
networksRepository = networksRepository,
|
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
quotesRepository = quotesRepository,
|
quotesRepository = quotesRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
|
|
@ -62,7 +64,6 @@ internal object TokensDomainModule {
|
||||||
fun provideFetchTokenListUseCase(
|
fun provideFetchTokenListUseCase(
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
networksRepository: NetworksRepository,
|
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteFetcher: MultiQuoteFetcher,
|
||||||
|
|
@ -71,7 +72,6 @@ internal object TokensDomainModule {
|
||||||
): FetchTokenListUseCase {
|
): FetchTokenListUseCase {
|
||||||
return FetchTokenListUseCase(
|
return FetchTokenListUseCase(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
networksRepository = networksRepository,
|
|
||||||
quotesRepository = quotesRepository,
|
quotesRepository = quotesRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
|
|
@ -121,8 +121,8 @@ internal object TokensDomainModule {
|
||||||
fun provideGetCurrencyUseCase(
|
fun provideGetCurrencyUseCase(
|
||||||
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
|
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): GetCurrencyStatusUpdatesUseCase {
|
): GetSingleCryptoCurrencyStatusUseCase {
|
||||||
return GetCurrencyStatusUpdatesUseCase(
|
return GetSingleCryptoCurrencyStatusUseCase(
|
||||||
currencyStatusOperations = baseCurrencyStatusOperations,
|
currencyStatusOperations = baseCurrencyStatusOperations,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
)
|
)
|
||||||
|
|
@ -147,7 +147,6 @@ internal object TokensDomainModule {
|
||||||
fun provideGetCurrencyWarningsUseCase(
|
fun provideGetCurrencyWarningsUseCase(
|
||||||
walletManagersFacade: WalletManagersFacade,
|
walletManagersFacade: WalletManagersFacade,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
networksRepository: NetworksRepository,
|
|
||||||
currencyChecksRepository: CurrencyChecksRepository,
|
currencyChecksRepository: CurrencyChecksRepository,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
|
baseCurrencyStatusOperations: BaseCurrencyStatusOperations,
|
||||||
|
|
@ -155,31 +154,17 @@ internal object TokensDomainModule {
|
||||||
return GetCurrencyWarningsUseCase(
|
return GetCurrencyWarningsUseCase(
|
||||||
walletManagersFacade = walletManagersFacade,
|
walletManagersFacade = walletManagersFacade,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
networksRepository = networksRepository,
|
dispatchers = dispatchers,
|
||||||
currencyChecksRepository = currencyChecksRepository,
|
currencyChecksRepository = currencyChecksRepository,
|
||||||
dispatchers = dispatchers,
|
|
||||||
currencyStatusOperations = baseCurrencyStatusOperations,
|
currencyStatusOperations = baseCurrencyStatusOperations,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideGetPrimaryCurrencyUseCase(
|
|
||||||
currencyStatusOperations: BaseCurrencyStatusOperations,
|
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
|
||||||
): GetPrimaryCurrencyStatusUpdatesUseCase {
|
|
||||||
return GetPrimaryCurrencyStatusUpdatesUseCase(
|
|
||||||
currencyStatusOperations = currencyStatusOperations,
|
|
||||||
dispatchers = dispatchers,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideFetchCurrencyStatusUseCase(
|
fun provideFetchCurrencyStatusUseCase(
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
networksRepository: NetworksRepository,
|
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteFetcher: MultiQuoteFetcher,
|
||||||
|
|
@ -188,7 +173,6 @@ internal object TokensDomainModule {
|
||||||
): FetchCurrencyStatusUseCase {
|
): FetchCurrencyStatusUseCase {
|
||||||
return FetchCurrencyStatusUseCase(
|
return FetchCurrencyStatusUseCase(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
networksRepository = networksRepository,
|
|
||||||
quotesRepository = quotesRepository,
|
quotesRepository = quotesRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||||
|
|
@ -203,7 +187,6 @@ internal object TokensDomainModule {
|
||||||
fun provideFetchCardTokenListUseCase(
|
fun provideFetchCardTokenListUseCase(
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
networksRepository: NetworksRepository,
|
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||||
multiQuoteFetcher: MultiQuoteFetcher,
|
multiQuoteFetcher: MultiQuoteFetcher,
|
||||||
|
|
@ -212,7 +195,6 @@ internal object TokensDomainModule {
|
||||||
): FetchCardTokenListUseCase {
|
): FetchCardTokenListUseCase {
|
||||||
return FetchCardTokenListUseCase(
|
return FetchCardTokenListUseCase(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
networksRepository = networksRepository,
|
|
||||||
quotesRepository = quotesRepository,
|
quotesRepository = quotesRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||||
|
|
@ -222,14 +204,6 @@ internal object TokensDomainModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun providesGetCryptoCurrencyStatusSyncUseCase(
|
|
||||||
currencyStatusOperations: BaseCurrencyStatusOperations,
|
|
||||||
): GetCryptoCurrencyStatusSyncUseCase {
|
|
||||||
return GetCryptoCurrencyStatusSyncUseCase(currencyStatusOperations)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGetCryptoCurrencyUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrencyUseCase {
|
fun provideGetCryptoCurrencyUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrencyUseCase {
|
||||||
|
|
@ -328,14 +302,10 @@ internal object TokensDomainModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideUpdateDelayedCurrencyStatusUseCase(
|
fun provideUpdateDelayedCurrencyStatusUseCase(
|
||||||
networksRepository: NetworksRepository,
|
|
||||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||||
tokensFeatureToggles: TokensFeatureToggles,
|
|
||||||
): UpdateDelayedNetworkStatusUseCase {
|
): UpdateDelayedNetworkStatusUseCase {
|
||||||
return UpdateDelayedNetworkStatusUseCase(
|
return UpdateDelayedNetworkStatusUseCase(
|
||||||
networksRepository = networksRepository,
|
|
||||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||||
tokensFeatureToggles = tokensFeatureToggles,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -413,7 +383,6 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
quotesRepositoryV2: QuotesRepositoryV2,
|
quotesRepositoryV2: QuotesRepositoryV2,
|
||||||
networksRepository: NetworksRepository,
|
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||||
|
|
@ -427,7 +396,6 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
quotesRepository = quotesRepository,
|
quotesRepository = quotesRepository,
|
||||||
quotesRepositoryV2 = quotesRepositoryV2,
|
quotesRepositoryV2 = quotesRepositoryV2,
|
||||||
networksRepository = networksRepository,
|
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||||
|
|
@ -447,7 +415,6 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
quotesRepositoryV2: QuotesRepositoryV2,
|
quotesRepositoryV2: QuotesRepositoryV2,
|
||||||
networksRepository: NetworksRepository,
|
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||||
|
|
@ -461,7 +428,6 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
quotesRepository = quotesRepository,
|
quotesRepository = quotesRepository,
|
||||||
quotesRepositoryV2 = quotesRepositoryV2,
|
quotesRepositoryV2 = quotesRepositoryV2,
|
||||||
networksRepository = networksRepository,
|
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||||
import com.tangem.domain.demo.DemoConfig
|
import com.tangem.domain.demo.DemoConfig
|
||||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
|
||||||
import com.tangem.domain.transaction.FeeRepository
|
import com.tangem.domain.transaction.FeeRepository
|
||||||
import com.tangem.domain.transaction.TransactionRepository
|
import com.tangem.domain.transaction.TransactionRepository
|
||||||
import com.tangem.domain.transaction.usecase.*
|
import com.tangem.domain.transaction.usecase.*
|
||||||
|
|
@ -43,7 +41,6 @@ internal object TransactionDomainModule {
|
||||||
transactionRepository: TransactionRepository,
|
transactionRepository: TransactionRepository,
|
||||||
walletManagersFacade: WalletManagersFacade,
|
walletManagersFacade: WalletManagersFacade,
|
||||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||||
tokensFeatureToggles: TokensFeatureToggles,
|
|
||||||
): SendTransactionUseCase {
|
): SendTransactionUseCase {
|
||||||
return SendTransactionUseCase(
|
return SendTransactionUseCase(
|
||||||
demoConfig = DemoConfig(),
|
demoConfig = DemoConfig(),
|
||||||
|
|
@ -51,7 +48,6 @@ internal object TransactionDomainModule {
|
||||||
transactionRepository = transactionRepository,
|
transactionRepository = transactionRepository,
|
||||||
walletManagersFacade = walletManagersFacade,
|
walletManagersFacade = walletManagersFacade,
|
||||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||||
tokensFeatureToggles = tokensFeatureToggles,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,17 +57,13 @@ internal object TransactionDomainModule {
|
||||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||||
walletManagersFacade: WalletManagersFacade,
|
walletManagersFacade: WalletManagersFacade,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
networksRepository: NetworksRepository,
|
|
||||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||||
tokensFeatureToggles: TokensFeatureToggles,
|
|
||||||
): AssociateAssetUseCase {
|
): AssociateAssetUseCase {
|
||||||
return AssociateAssetUseCase(
|
return AssociateAssetUseCase(
|
||||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||||
walletManagersFacade = walletManagersFacade,
|
walletManagersFacade = walletManagersFacade,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
networksRepository = networksRepository,
|
|
||||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||||
tokensFeatureToggles = tokensFeatureToggles,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase
|
||||||
import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
|
import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
|
||||||
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
|
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
|
import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate
|
||||||
|
import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||||
|
|
@ -28,6 +30,17 @@ import javax.inject.Singleton
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
internal object WalletsDomainModule {
|
internal object WalletsDomainModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun providesUserWalletsSyncDelegate(
|
||||||
|
userWalletsListManager: UserWalletsListManager,
|
||||||
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
): UserWalletsSyncDelegate {
|
||||||
|
return DefaultUserWalletsSyncDelegate(
|
||||||
|
userWalletsListManager = userWalletsListManager,
|
||||||
|
dispatchers = dispatchers,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesGetWalletsUseCase(userWalletsListManager: UserWalletsListManager): GetWalletsUseCase {
|
fun providesGetWalletsUseCase(userWalletsListManager: UserWalletsListManager): GetWalletsUseCase {
|
||||||
|
|
@ -102,10 +115,13 @@ internal object WalletsDomainModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesRenameWalletUseCase(
|
fun providesRenameWalletUseCase(
|
||||||
userWalletsListManager: UserWalletsListManager,
|
walletsRepository: WalletsRepository,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
userWalletsSyncDelegate: UserWalletsSyncDelegate,
|
||||||
): RenameWalletUseCase {
|
): RenameWalletUseCase {
|
||||||
return RenameWalletUseCase(userWalletsListManager = userWalletsListManager, dispatchers = dispatchers)
|
return RenameWalletUseCase(
|
||||||
|
walletsRepository = walletsRepository,
|
||||||
|
userWalletsSyncDelegate = userWalletsSyncDelegate,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
@ -197,4 +213,36 @@ internal object WalletsDomainModule {
|
||||||
nftFeatureToggles = nftFeatureToggles,
|
nftFeatureToggles = nftFeatureToggles,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providesUpdateRemoteWalletsInfoUseCase(
|
||||||
|
walletsRepository: WalletsRepository,
|
||||||
|
userWalletsSyncDelegate: UserWalletsSyncDelegate,
|
||||||
|
): UpdateRemoteWalletsInfoUseCase {
|
||||||
|
return UpdateRemoteWalletsInfoUseCase(
|
||||||
|
walletsRepository = walletsRepository,
|
||||||
|
userWalletsSyncDelegate = userWalletsSyncDelegate,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providesGetSavedWalletChangesIdUseCase(
|
||||||
|
userWalletsListManager: UserWalletsListManager,
|
||||||
|
): GetSavedWalletChangesUseCase {
|
||||||
|
return GetSavedWalletChangesUseCase(
|
||||||
|
userWalletsListManager = userWalletsListManager,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providesAssociateWalletsWithApplicationIdUseCase(
|
||||||
|
walletsRepository: WalletsRepository,
|
||||||
|
): AssociateWalletsWithApplicationIdUseCase {
|
||||||
|
return AssociateWalletsWithApplicationIdUseCase(
|
||||||
|
walletsRepository = walletsRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.tangem.tap.di.routing
|
package com.tangem.tap.di.routing
|
||||||
|
|
||||||
import com.tangem.common.routing.AppRouter
|
import com.tangem.common.routing.AppRouter
|
||||||
|
import com.tangem.common.routing.RoutingFeatureToggle
|
||||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||||
|
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
import com.tangem.tap.routing.ProxyAppRouter
|
import com.tangem.tap.routing.ProxyAppRouter
|
||||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||||
import com.tangem.tap.routing.configurator.MutableAppRouterConfig
|
import com.tangem.tap.routing.configurator.MutableAppRouterConfig
|
||||||
|
|
@ -31,4 +33,10 @@ internal object AppRouterModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideAppRouterConfigurator(): AppRouterConfig = MutableAppRouterConfig()
|
fun provideAppRouterConfigurator(): AppRouterConfig = MutableAppRouterConfig()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideRoutingFeatureToggle(featureTogglesManager: FeatureTogglesManager): RoutingFeatureToggle {
|
||||||
|
return RoutingFeatureToggle(featureTogglesManager)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -15,9 +15,9 @@ import com.tangem.data.common.currency.getNetwork
|
||||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||||
import com.tangem.domain.card.BackendId
|
import com.tangem.domain.card.BackendId
|
||||||
import com.tangem.domain.card.repository.DerivationsRepository
|
import com.tangem.domain.card.repository.DerivationsRepository
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
|
||||||
import com.tangem.domain.tokens.model.Network
|
|
||||||
import com.tangem.domain.wallets.models.UserWallet
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||||
|
|
@ -41,7 +41,7 @@ internal class DefaultDerivationsRepository(
|
||||||
derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network))
|
derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List<Network.ID>) {
|
override suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List<Network.RawID>) {
|
||||||
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
||||||
|
|
||||||
derivePublicKeysByNetworks(
|
derivePublicKeysByNetworks(
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ import com.tangem.common.extensions.toMapKey
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.domain.common.configs.CardConfig
|
import com.tangem.domain.common.configs.CardConfig
|
||||||
import com.tangem.domain.common.util.derivationStyleProvider
|
import com.tangem.domain.common.util.derivationStyleProvider
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
|
||||||
import com.tangem.domain.tokens.model.Network
|
|
||||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||||
|
|
||||||
private typealias DerivationData = Pair<ByteArrayKey, List<DerivationPath>>
|
private typealias DerivationData = Pair<ByteArrayKey, List<DerivationPath>>
|
||||||
|
|
@ -49,7 +49,7 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
||||||
private fun List<Network>.mapToNewDerivations(): List<DerivationData> {
|
private fun List<Network>.mapToNewDerivations(): List<DerivationData> {
|
||||||
val config = CardConfig.createConfig(scanResponse.card)
|
val config = CardConfig.createConfig(scanResponse.card)
|
||||||
return mapNotNull { network ->
|
return mapNotNull { network ->
|
||||||
val blockchain = Blockchain.fromId(id = network.id.value)
|
val blockchain = Blockchain.fromId(id = network.rawId)
|
||||||
val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null
|
val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null
|
||||||
|
|
||||||
findNewDerivations(curve = curve, scanResponse = scanResponse, network = network)
|
findNewDerivations(curve = curve, scanResponse = scanResponse, network = network)
|
||||||
|
|
@ -74,7 +74,7 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Network.getDerivationCandidates(curve: EllipticCurve): List<DerivationPath> {
|
private fun Network.getDerivationCandidates(curve: EllipticCurve): List<DerivationPath> {
|
||||||
val blockchain = Blockchain.fromId(id = this.id.value)
|
val blockchain = Blockchain.fromId(id = rawId)
|
||||||
|
|
||||||
return buildList {
|
return buildList {
|
||||||
add(blockchain.getDerivationPath(curve = curve))
|
add(blockchain.getDerivationPath(curve = curve))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.tangem.tap.domain.notifications
|
||||||
|
|
||||||
|
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
|
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||||
|
|
||||||
|
internal class DefaultNotificationsFeatureToggles(
|
||||||
|
private val featureTogglesManager: FeatureTogglesManager,
|
||||||
|
) : NotificationsFeatureToggles {
|
||||||
|
override val isNotificationsEnabled: Boolean
|
||||||
|
get() = featureTogglesManager.isFeatureEnabled("PUSH_NOTIFICATIONS_ENABLED")
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.domain.tasks.visa
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import arrow.core.raise.catch
|
|
||||||
import arrow.core.raise.either
|
import arrow.core.raise.either
|
||||||
import com.tangem.blockchain.common.UnmarshalHelper
|
import com.tangem.blockchain.common.UnmarshalHelper
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
|
|
@ -14,22 +13,18 @@ import com.tangem.common.extensions.toHexString
|
||||||
import com.tangem.common.map
|
import com.tangem.common.map
|
||||||
import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource
|
import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource
|
||||||
import com.tangem.core.error.ext.tangemError
|
import com.tangem.core.error.ext.tangemError
|
||||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
|
||||||
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
||||||
import com.tangem.datasource.local.visa.VisaOTPStorage
|
import com.tangem.datasource.local.visa.VisaOTPStorage
|
||||||
import com.tangem.datasource.local.visa.VisaOtpData
|
import com.tangem.datasource.local.visa.VisaOtpData
|
||||||
import com.tangem.datasource.local.visa.hasSavedOTP
|
import com.tangem.datasource.local.visa.hasSavedOTP
|
||||||
import com.tangem.domain.common.visa.VisaUtilities
|
|
||||||
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
|
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.visa.error.VisaActivationError
|
import com.tangem.domain.visa.error.VisaActivationError
|
||||||
import com.tangem.domain.visa.error.VisaAuthorizationAPIError
|
|
||||||
import com.tangem.domain.visa.model.*
|
import com.tangem.domain.visa.model.*
|
||||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||||
import com.tangem.domain.visa.repository.VisaAuthRepository
|
import com.tangem.domain.visa.repository.VisaAuthRepository
|
||||||
import com.tangem.operations.GenerateOTPCommand
|
import com.tangem.operations.GenerateOTPCommand
|
||||||
import com.tangem.operations.attestation.AttestCardKeyCommand
|
import com.tangem.operations.attestation.AttestCardKeyCommand
|
||||||
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
|
||||||
import com.tangem.operations.pins.SetUserCodeCommand
|
import com.tangem.operations.pins.SetUserCodeCommand
|
||||||
import com.tangem.operations.sign.SignHashCommand
|
import com.tangem.operations.sign.SignHashCommand
|
||||||
import com.tangem.operations.sign.SignHashResponse
|
import com.tangem.operations.sign.SignHashResponse
|
||||||
|
|
@ -95,23 +90,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
context.signAuthorizationChallenge(mode.authorizationChallenge)
|
context.signAuthorizationChallenge(mode.authorizationChallenge)
|
||||||
}
|
}
|
||||||
is VisaCardActivationTaskMode.SignOnly -> {
|
is VisaCardActivationTaskMode.SignOnly -> {
|
||||||
val wallet =
|
context.signData(mode.dataToSignByCardWallet)
|
||||||
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
|
||||||
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
|
||||||
|
|
||||||
val derivedPublicKey = when (val deriveKeyResult = context.deriveKey(wallet.publicKey)) {
|
|
||||||
is CompletionResult.Failure -> {
|
|
||||||
return CompletionResult.Failure(deriveKeyResult.error)
|
|
||||||
}
|
|
||||||
is CompletionResult.Success -> {
|
|
||||||
deriveKeyResult.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.signData(
|
|
||||||
mode.dataToSignByCardWallet,
|
|
||||||
derivedPublicKey,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,16 +143,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||||
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
|
|
||||||
val derivedPublicKey = when (val deriveKeyResult = deriveKey(wallet.publicKey)) {
|
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(wallet.publicKey)
|
||||||
is CompletionResult.Failure -> {
|
|
||||||
return CompletionResult.Failure(deriveKeyResult.error)
|
|
||||||
}
|
|
||||||
is CompletionResult.Success -> {
|
|
||||||
deriveKeyResult.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey)
|
|
||||||
.getOrElse { return CompletionResult.Failure(it.tangemError) }
|
.getOrElse { return CompletionResult.Failure(it.tangemError) }
|
||||||
.value
|
.value
|
||||||
|
|
||||||
|
|
@ -190,10 +160,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
|
|
||||||
otpTaskDeferred.await()
|
otpTaskDeferred.await()
|
||||||
|
|
||||||
signData(
|
signData(dataToSign = dataToSign)
|
||||||
dataToSign = dataToSign,
|
|
||||||
derivedPublicKey = derivedPublicKey,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,28 +168,24 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
signedChallenge: VisaAuthSignedChallenge,
|
signedChallenge: VisaAuthSignedChallenge,
|
||||||
cardWalletAddress: String,
|
cardWalletAddress: String,
|
||||||
): Either<TangemError, VisaDataToSignByCardWallet> = either {
|
): Either<TangemError, VisaDataToSignByCardWallet> = either {
|
||||||
catch(
|
val tokens = visaAuthRepository.getAccessTokens(signedChallenge)
|
||||||
block = {
|
.getOrElse { raise(it.tangemError) }
|
||||||
val tokens = visaAuthRepository.getAccessTokens(signedChallenge)
|
|
||||||
|
|
||||||
visaAuthTokenStorage.store(cardId, tokens)
|
visaAuthTokenStorage.store(cardId, tokens)
|
||||||
|
|
||||||
val remoteState = visaActivationRepository.getActivationRemoteState()
|
val remoteState = visaActivationRepository.getActivationRemoteState()
|
||||||
if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) {
|
.getOrElse { raise(it.tangemError) }
|
||||||
raise(VisaActivationError.WrongRemoteState.tangemError)
|
|
||||||
}
|
|
||||||
|
|
||||||
visaActivationRepository.getCardWalletAcceptanceData(
|
if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) {
|
||||||
VisaCardWalletDataToSignRequest(
|
return raise(VisaActivationError.WrongRemoteState.tangemError)
|
||||||
activationOrderInfo = remoteState.activationOrderInfo,
|
}
|
||||||
cardWalletAddress = cardWalletAddress,
|
|
||||||
),
|
visaActivationRepository.getCardWalletAcceptanceData(
|
||||||
)
|
VisaCardWalletDataToSignRequest(
|
||||||
},
|
activationOrderInfo = remoteState.activationOrderInfo,
|
||||||
catch = {
|
cardWalletAddress = cardWalletAddress,
|
||||||
raise(VisaAuthorizationAPIError.tangemError)
|
),
|
||||||
},
|
).getOrElse { raise(it.tangemError) }
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun SessionContext.createWallet(): CompletionResult<Unit> {
|
private suspend fun SessionContext.createWallet(): CompletionResult<Unit> {
|
||||||
|
|
@ -294,7 +257,6 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
|
|
||||||
private suspend fun SessionContext.signData(
|
private suspend fun SessionContext.signData(
|
||||||
dataToSign: VisaDataToSignByCardWallet,
|
dataToSign: VisaDataToSignByCardWallet,
|
||||||
derivedPublicKey: ExtendedPublicKey,
|
|
||||||
): CompletionResult<VisaCardActivationResponse> {
|
): CompletionResult<VisaCardActivationResponse> {
|
||||||
val card =
|
val card =
|
||||||
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
|
|
@ -306,7 +268,6 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
val task = SignHashCommand(
|
val task = SignHashCommand(
|
||||||
hash = dataToSign.hashToSign.hexToBytes(),
|
hash = dataToSign.hashToSign.hexToBytes(),
|
||||||
walletPublicKey = wallet.publicKey,
|
walletPublicKey = wallet.publicKey,
|
||||||
derivationPath = VisaUtilities.visaDefaultDerivationPath,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||||
|
|
@ -325,7 +286,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
handleSignedData(
|
handleSignedData(
|
||||||
dataToSign = dataToSign,
|
dataToSign = dataToSign,
|
||||||
response = result.data,
|
response = result.data,
|
||||||
derivedPublicKey = derivedPublicKey,
|
walletPublicKey = wallet.publicKey,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
|
|
@ -335,23 +296,9 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun SessionContext.deriveKey(publicKey: ByteArray): CompletionResult<ExtendedPublicKey> {
|
|
||||||
val derivationPath = VisaUtilities.visaDefaultDerivationPath
|
|
||||||
?: return CompletionResult.Failure(VisaActivationError.FailedToCreateAddress.tangemError)
|
|
||||||
|
|
||||||
val derivationTask = DeriveWalletPublicKeyTask(publicKey, derivationPath)
|
|
||||||
val derivationTaskResult = suspendCancellableCoroutine { continuation ->
|
|
||||||
derivationTask.run(session) { result ->
|
|
||||||
continuation.resume(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return derivationTaskResult
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun SessionContext.handleSignedData(
|
private suspend fun SessionContext.handleSignedData(
|
||||||
dataToSign: VisaDataToSignByCardWallet,
|
dataToSign: VisaDataToSignByCardWallet,
|
||||||
derivedPublicKey: ExtendedPublicKey,
|
walletPublicKey: ByteArray,
|
||||||
response: SignHashResponse,
|
response: SignHashResponse,
|
||||||
): CompletionResult<VisaCardActivationResponse> {
|
): CompletionResult<VisaCardActivationResponse> {
|
||||||
val otp = otpStorage.getOTP(cardId) ?: run {
|
val otp = otpStorage.getOTP(cardId) ?: run {
|
||||||
|
|
@ -362,7 +309,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended(
|
val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended(
|
||||||
signature = response.signature,
|
signature = response.signature,
|
||||||
hash = dataToSign.hashToSign.hexToBytes(),
|
hash = dataToSign.hashToSign.hexToBytes(),
|
||||||
publicKey = derivedPublicKey.publicKey.toDecompressedPublicKey(),
|
publicKey = walletPublicKey.toDecompressedPublicKey(),
|
||||||
).asRSVLegacyEVM().toHexString().lowercase()
|
).asRSVLegacyEVM().toHexString().lowercase()
|
||||||
|
|
||||||
val signedActivationData = dataToSign.sign(
|
val signedActivationData = dataToSign.sign(
|
||||||
|
|
|
||||||
|
|
@ -40,17 +40,12 @@ class VisaCustomerWalletApproveTask(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VisaUtilities.isVisaCard(card.firmwareVersion.doubleValue, card.batchId)) {
|
if (VisaUtilities.isVisaCard(card.firmwareVersion.doubleValue, card.batchId)) {
|
||||||
// TODO TVF-21
|
callback(CompletionResult.Failure(VisaActivationError.VisaCardForApproval.tangemError))
|
||||||
callback(CompletionResult.Failure(TangemSdkError.Underlying("Can't use Visa card for approve")))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (visaDataForApprove.customerWalletCardId != null && card.cardId != visaDataForApprove.customerWalletCardId) {
|
if (visaDataForApprove.customerWalletCardId != null && card.cardId != visaDataForApprove.customerWalletCardId) {
|
||||||
callback(
|
callback(CompletionResult.Failure(VisaActivationError.CardIdNotMatched.tangemError))
|
||||||
CompletionResult.Failure(
|
|
||||||
TangemSdkError.Underlying("Use tangem wallet specified during visa registration"), // TODO TVF-21
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,6 @@ internal class DefaultTokensFeatureToggles(
|
||||||
private val featureTogglesManager: FeatureTogglesManager,
|
private val featureTogglesManager: FeatureTogglesManager,
|
||||||
) : TokensFeatureToggles {
|
) : TokensFeatureToggles {
|
||||||
|
|
||||||
override val isNetworksLoadingRefactoringEnabled: Boolean
|
|
||||||
get() = featureTogglesManager.isFeatureEnabled(name = "NETWORKS_LOADING_REFACTORING_ENABLED")
|
|
||||||
|
|
||||||
override val isQuotesLoadingRefactoringEnabled: Boolean
|
override val isQuotesLoadingRefactoringEnabled: Boolean
|
||||||
get() = featureTogglesManager.isFeatureEnabled(name = "QUOTES_LOADING_REFACTORING_ENABLED")
|
get() = featureTogglesManager.isFeatureEnabled(name = "QUOTES_LOADING_REFACTORING_ENABLED")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,24 @@ package com.tangem.tap.domain.visa
|
||||||
|
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
import com.tangem.common.card.CardWallet
|
|
||||||
import com.tangem.common.card.EllipticCurve
|
import com.tangem.common.card.EllipticCurve
|
||||||
import com.tangem.common.core.CardSession
|
import com.tangem.common.core.CardSession
|
||||||
import com.tangem.common.core.TangemSdkError
|
import com.tangem.common.core.TangemSdkError
|
||||||
import com.tangem.common.extensions.hexToBytes
|
import com.tangem.common.extensions.hexToBytes
|
||||||
import com.tangem.common.extensions.toHexString
|
import com.tangem.common.extensions.toHexString
|
||||||
import com.tangem.core.error.ext.tangemError
|
import com.tangem.core.error.ext.tangemError
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
|
||||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
|
||||||
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
||||||
import com.tangem.domain.common.visa.VisaUtilities
|
|
||||||
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
|
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
|
||||||
import com.tangem.domain.visa.error.VisaActivationError
|
import com.tangem.domain.visa.error.VisaActivationError
|
||||||
import com.tangem.domain.visa.error.VisaAuthorizationAPIError
|
import com.tangem.domain.visa.error.VisaApiError
|
||||||
import com.tangem.domain.visa.error.VisaCardScanError
|
import com.tangem.domain.visa.error.VisaCardScanError
|
||||||
import com.tangem.domain.visa.model.*
|
import com.tangem.domain.visa.model.*
|
||||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||||
import com.tangem.domain.visa.repository.VisaAuthRepository
|
import com.tangem.domain.visa.repository.VisaAuthRepository
|
||||||
import com.tangem.operations.attestation.AttestCardKeyCommand
|
import com.tangem.operations.attestation.AttestCardKeyCommand
|
||||||
import com.tangem.operations.attestation.AttestCardKeyResponse
|
import com.tangem.operations.attestation.AttestCardKeyResponse
|
||||||
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
import com.tangem.operations.attestation.AttestWalletKeyResponse
|
||||||
import com.tangem.operations.sign.SignHashCommand
|
import com.tangem.operations.attestation.AttestWalletKeyTask
|
||||||
import com.tangem.operations.sign.SignHashResponse
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -61,54 +56,19 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
session = session,
|
session = session,
|
||||||
)
|
)
|
||||||
|
|
||||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
||||||
val activationInput =
|
val activationInput =
|
||||||
VisaActivationInput(card.cardId, card.cardPublicKey.toHexString(), card.isAccessCodeSet)
|
VisaActivationInput(card.cardId, card.cardPublicKey.toHexString(), card.isAccessCodeSet)
|
||||||
val activationStatus = VisaCardActivationStatus.NotStartedActivation(activationInput)
|
val activationStatus = VisaCardActivationStatus.NotStartedActivation(activationInput)
|
||||||
return CompletionResult.Success(activationStatus)
|
return CompletionResult.Success(activationStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.deriveKey(wallet)
|
return context.handleWalletAuthorization()
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun SessionContext.deriveKey(wallet: CardWallet): CompletionResult<VisaCardActivationStatus> {
|
|
||||||
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
|
|
||||||
Timber.e("Failed to create derivation path while first scan")
|
|
||||||
|
|
||||||
return CompletionResult.Failure(VisaCardScanError.FailedToCreateDerivationPath.tangemError)
|
|
||||||
}
|
|
||||||
|
|
||||||
val derivationTask = DeriveWalletPublicKeyTask(wallet.publicKey, derivationPath)
|
|
||||||
val derivationTaskResult = suspendCancellableCoroutine { continuation ->
|
|
||||||
derivationTask.run(session) { result ->
|
|
||||||
continuation.resume(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return handleDerivationResponse(derivationTaskResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun SessionContext.handleDerivationResponse(
|
|
||||||
result: CompletionResult<ExtendedPublicKey>,
|
|
||||||
): CompletionResult<VisaCardActivationStatus> {
|
|
||||||
return when (result) {
|
|
||||||
is CompletionResult.Success -> {
|
|
||||||
Timber.i("Start task for loading challenge for Visa wallet")
|
|
||||||
handleWalletAuthorization()
|
|
||||||
}
|
|
||||||
is CompletionResult.Failure -> {
|
|
||||||
CompletionResult.Failure(result.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun SessionContext.handleWalletAuthorization(): CompletionResult<VisaCardActivationStatus> {
|
private suspend fun SessionContext.handleWalletAuthorization(): CompletionResult<VisaCardActivationStatus> {
|
||||||
Timber.i("Started handling authorization using Visa wallet")
|
Timber.i("Started handling authorization using Visa wallet")
|
||||||
|
|
||||||
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
|
|
||||||
Timber.e("Failed to create derivation path while handling wallet authorization")
|
|
||||||
return CompletionResult.Failure(VisaCardScanError.FailedToCreateDerivationPath.tangemError)
|
|
||||||
}
|
|
||||||
|
|
||||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
|
|
||||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
||||||
|
|
@ -116,43 +76,35 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
return CompletionResult.Failure(VisaCardScanError.FailedToFindWallet.tangemError)
|
return CompletionResult.Failure(VisaCardScanError.FailedToFindWallet.tangemError)
|
||||||
}
|
}
|
||||||
|
|
||||||
val extendedPublicKey = wallet.derivedKeys[derivationPath] ?: run {
|
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(wallet.publicKey)
|
||||||
Timber.e("Failed to find extended public key while handling wallet authorization")
|
|
||||||
return CompletionResult.Failure(VisaCardScanError.FailedToFindDerivedWalletKey.tangemError)
|
|
||||||
}
|
|
||||||
|
|
||||||
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(extendedPublicKey.publicKey)
|
|
||||||
.getOrElse {
|
.getOrElse {
|
||||||
return CompletionResult.Failure(it.tangemError)
|
return CompletionResult.Failure(it.tangemError)
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.i("Requesting challenge for wallet authorization")
|
Timber.i("Requesting challenge for wallet authorization")
|
||||||
val challengeResponse = runCatching {
|
|
||||||
// TODO [REDACTED_TASK_KEY]
|
val challengeResponse = visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value)
|
||||||
error("sign and get specific error to switch to card_id flow")
|
.getOrElse {
|
||||||
visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value)
|
Timber.i("Failed to get Access token for Wallet public key authorization")
|
||||||
}.getOrElse {
|
return CompletionResult.Failure(it.tangemError)
|
||||||
Timber.i(
|
}
|
||||||
"Failed to get Access token for Wallet public key authoziation. Authorizing using Card Pub key",
|
|
||||||
)
|
|
||||||
return handleCardAuthorization(
|
|
||||||
cardWalletAddress = walletAddress.value,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val signChallengeResult = signChallengeWithWallet(
|
val signChallengeResult = signChallengeWithWallet(
|
||||||
publicKey = wallet.publicKey,
|
publicKey = wallet.publicKey,
|
||||||
derivationPath = derivationPath,
|
|
||||||
nonce = challengeResponse.challenge,
|
nonce = challengeResponse.challenge,
|
||||||
)
|
)
|
||||||
|
|
||||||
return when (signChallengeResult) {
|
return when (signChallengeResult) {
|
||||||
is CompletionResult.Success -> {
|
is CompletionResult.Success -> {
|
||||||
|
val signature = signChallengeResult.data.cardSignature ?: run {
|
||||||
|
Timber.i("Failed to sign challenge with Wallet public key")
|
||||||
|
return CompletionResult.Failure(VisaCardScanError.FailedToSignChallenge.tangemError)
|
||||||
|
}
|
||||||
|
|
||||||
Timber.i("Challenge signed with Wallet public key")
|
Timber.i("Challenge signed with Wallet public key")
|
||||||
handleWalletAuthorizationTokens(
|
handleWalletAuthorizationTokens(
|
||||||
cardWalletAddress = walletAddress.value,
|
cardWalletAddress = walletAddress.value,
|
||||||
signedChallenge = challengeResponse
|
signedChallenge = challengeResponse.toSignedChallenge(signature.toHexString()),
|
||||||
.toSignedChallenge(signChallengeResult.data.signature.toHexString()),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is CompletionResult.Failure -> {
|
is CompletionResult.Failure -> {
|
||||||
|
|
@ -166,16 +118,19 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
cardWalletAddress: String,
|
cardWalletAddress: String,
|
||||||
signedChallenge: VisaAuthSignedChallenge,
|
signedChallenge: VisaAuthSignedChallenge,
|
||||||
): CompletionResult<VisaCardActivationStatus> {
|
): CompletionResult<VisaCardActivationStatus> {
|
||||||
val authorizationTokensResponse = runCatching {
|
val authorizationTokensResponse = visaAuthRepository.getAccessTokens(signedChallenge = signedChallenge)
|
||||||
visaAuthRepository.getAccessTokens(signedChallenge = signedChallenge)
|
.getOrElse {
|
||||||
}.getOrElse {
|
Timber.i("Failed to get Access token for Wallet public key authorization.")
|
||||||
Timber.i(
|
return if (
|
||||||
"Failed to get Access token for Wallet public key authoziation. Authorizing using Card Pub key",
|
it is VisaApiError.ProductInstanceIsNotActivated ||
|
||||||
)
|
it is VisaApiError.ProductInstanceNotFoundActivationRequired
|
||||||
return handleCardAuthorization(
|
) {
|
||||||
cardWalletAddress = cardWalletAddress,
|
Timber.i("Proceeding with card authorization.")
|
||||||
)
|
handleCardAuthorization(cardWalletAddress = cardWalletAddress)
|
||||||
}
|
} else {
|
||||||
|
CompletionResult.Failure(it.tangemError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timber.i("Authorized using Wallet public key successfully")
|
Timber.i("Authorized using Wallet public key successfully")
|
||||||
|
|
||||||
|
|
@ -190,14 +145,12 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
|
|
||||||
Timber.i("Requesting authorization challenge to sign")
|
Timber.i("Requesting authorization challenge to sign")
|
||||||
|
|
||||||
val challengeResponse = runCatching {
|
val challengeResponse = visaAuthRepository.getCardAuthChallenge(
|
||||||
visaAuthRepository.getCardAuthChallenge(
|
cardId = card.cardId,
|
||||||
cardId = card.cardId,
|
cardPublicKey = card.cardPublicKey.toHexString(),
|
||||||
cardPublicKey = card.cardPublicKey.toHexString(),
|
).getOrElse {
|
||||||
)
|
Timber.e("Failed to get challenge for Card authorization. Plain error: ${it.errorCode}")
|
||||||
}.getOrElse {
|
return CompletionResult.Failure(it.tangemError)
|
||||||
Timber.e("Failed to get challenge for Card authorization. Plain error: ${it.message}")
|
|
||||||
return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.i("Received challenge to sign: ${challengeResponse.challenge}")
|
Timber.i("Received challenge to sign: ${challengeResponse.challenge}")
|
||||||
|
|
@ -217,17 +170,14 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UnusedPrivateMember")
|
val authorizationTokensResponse = visaAuthRepository.getAccessTokens(
|
||||||
val authorizationTokensResponse = runCatching {
|
signedChallenge = challengeResponse.toSignedChallenge(
|
||||||
visaAuthRepository.getAccessTokens(
|
signedChallenge = attestCardKeyResponse.cardSignature.toHexString(),
|
||||||
signedChallenge = challengeResponse.toSignedChallenge(
|
salt = attestCardKeyResponse.salt.toHexString(),
|
||||||
signedChallenge = attestCardKeyResponse.cardSignature.toHexString(),
|
),
|
||||||
salt = attestCardKeyResponse.salt.toHexString(),
|
).getOrElse {
|
||||||
),
|
Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.errorCode}")
|
||||||
)
|
return CompletionResult.Failure(it.tangemError)
|
||||||
}.getOrElse {
|
|
||||||
Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.message}")
|
|
||||||
return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visaAuthTokenStorage.store(
|
visaAuthTokenStorage.store(
|
||||||
|
|
@ -235,11 +185,9 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
tokens = authorizationTokensResponse,
|
tokens = authorizationTokensResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
val activationRemoteState = runCatching {
|
val activationRemoteState = visaActivationRepository.getActivationRemoteState().getOrElse {
|
||||||
visaActivationRepository.getActivationRemoteState()
|
Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.errorCode}")
|
||||||
}.getOrElse {
|
return CompletionResult.Failure(it.tangemError)
|
||||||
Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.message}")
|
|
||||||
return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val error = when (activationRemoteState) {
|
val error = when (activationRemoteState) {
|
||||||
|
|
@ -271,13 +219,11 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
|
|
||||||
private suspend fun SessionContext.signChallengeWithWallet(
|
private suspend fun SessionContext.signChallengeWithWallet(
|
||||||
publicKey: ByteArray,
|
publicKey: ByteArray,
|
||||||
derivationPath: DerivationPath,
|
|
||||||
nonce: String,
|
nonce: String,
|
||||||
): CompletionResult<SignHashResponse> {
|
): CompletionResult<AttestWalletKeyResponse> {
|
||||||
val signHashCommand = SignHashCommand(
|
val signHashCommand = AttestWalletKeyTask(
|
||||||
hash = nonce.hexToBytes(),
|
publicKey = publicKey,
|
||||||
walletPublicKey = publicKey,
|
challenge = nonce.hexToBytes(),
|
||||||
derivationPath = derivationPath,
|
|
||||||
)
|
)
|
||||||
return suspendCancellableCoroutine {
|
return suspendCancellableCoroutine {
|
||||||
signHashCommand.run(session) { result ->
|
signHashCommand.run(session) { result ->
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import com.tangem.domain.walletconnect.WcPairService
|
||||||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||||
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
|
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
|
||||||
|
|
@ -64,14 +65,18 @@ internal class DefaultLegacyWalletConnectRepositoryFacade constructor(
|
||||||
if (isNewWc) stub.updateSessions() else legacy.updateSessions()
|
if (isNewWc) stub.updateSessions() else legacy.updateSessions()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pair(uri: String, source: SourceType) {
|
override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) {
|
||||||
val src = when (source) {
|
val src = when (source) {
|
||||||
SourceType.QR -> WcPairRequest.Source.QR
|
SourceType.QR -> WcPairRequest.Source.QR
|
||||||
SourceType.DEEPLINK -> WcPairRequest.Source.DEEPLINK
|
SourceType.DEEPLINK -> WcPairRequest.Source.DEEPLINK
|
||||||
SourceType.CLIPBOARD -> WcPairRequest.Source.CLIPBOARD
|
SourceType.CLIPBOARD -> WcPairRequest.Source.CLIPBOARD
|
||||||
SourceType.ETC -> WcPairRequest.Source.ETC
|
SourceType.ETC -> WcPairRequest.Source.ETC
|
||||||
}
|
}
|
||||||
if (isNewWc) wcPairService.pair(WcPairRequest(uri, src)) else legacy.pair(uri, source)
|
if (isNewWc) {
|
||||||
|
wcPairService.pair(WcPairRequest(uri = uri, source = src, userWalletId = userWalletId))
|
||||||
|
} else {
|
||||||
|
legacy.pair(userWalletId = userWalletId, uri = uri, source = source)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disconnect(topic: String) {
|
override fun disconnect(topic: String) {
|
||||||
|
|
@ -110,7 +115,7 @@ internal class LegacyWalletConnectRepositoryStub : LegacyWalletConnectRepository
|
||||||
|
|
||||||
override fun updateSessions() = Unit
|
override fun updateSessions() = Unit
|
||||||
|
|
||||||
override fun pair(uri: String, source: SourceType) = Unit
|
override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) = Unit
|
||||||
|
|
||||||
override fun disconnect(topic: String) = Unit
|
override fun disconnect(topic: String) = Unit
|
||||||
|
|
||||||
|
|
@ -405,7 +410,7 @@ internal class DefaultLegacyWalletConnectRepository(
|
||||||
this.userNamespaces = userNamespaces
|
this.userNamespaces = userNamespaces
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pair(uri: String, source: SourceType) {
|
override fun pair(userWalletId: UserWalletId, uri: String, source: SourceType) {
|
||||||
analyticsHandler.send(WalletConnect.NewSessionInitiated(source = source))
|
analyticsHandler.send(WalletConnect.NewSessionInitiated(source = source))
|
||||||
WalletKit.pair(
|
WalletKit.pair(
|
||||||
params = Wallet.Params.Pair(uri),
|
params = Wallet.Params.Pair(uri),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.tap.domain.walletconnect2.domain
|
package com.tangem.tap.domain.walletconnect2.domain
|
||||||
|
|
||||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.tap.domain.walletconnect2.domain.models.*
|
import com.tangem.tap.domain.walletconnect2.domain.models.*
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType
|
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.OpenSession.SourceType
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
@ -19,7 +20,7 @@ interface LegacyWalletConnectRepository {
|
||||||
|
|
||||||
fun updateSessions()
|
fun updateSessions()
|
||||||
|
|
||||||
fun pair(uri: String, source: SourceType)
|
fun pair(userWalletId: UserWalletId, uri: String, source: SourceType)
|
||||||
|
|
||||||
fun disconnect(topic: String)
|
fun disconnect(topic: String)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package com.tangem.tap.domain.walletconnect2.domain
|
||||||
import arrow.core.flatten
|
import arrow.core.flatten
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.tokens.model.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||||
import com.tangem.domain.walletconnect.model.legacy.Session
|
import com.tangem.domain.walletconnect.model.legacy.Session
|
||||||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsReposit
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.domain.wallets.models.UserWallet
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||||
import com.tangem.tap.common.extensions.filterNotNull
|
import com.tangem.tap.common.extensions.filterNotNull
|
||||||
|
|
@ -139,7 +140,11 @@ class WalletConnectInteractor(
|
||||||
if (deeplinkStack.empty()) return
|
if (deeplinkStack.empty()) return
|
||||||
val lastDeeplink = deeplinkStack.pop()
|
val lastDeeplink = deeplinkStack.pop()
|
||||||
val action = WalletConnectAction
|
val action = WalletConnectAction
|
||||||
.OpenSession(lastDeeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK)
|
.OpenSession(
|
||||||
|
wcUri = lastDeeplink,
|
||||||
|
source = WalletConnectAction.OpenSession.SourceType.DEEPLINK,
|
||||||
|
userWalletId = UserWalletId(userWalletId),
|
||||||
|
)
|
||||||
store.dispatchOnMain(action)
|
store.dispatchOnMain(action)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e("WC deeplink handling failed. $it")
|
Timber.e("WC deeplink handling failed. $it")
|
||||||
|
|
@ -393,7 +398,11 @@ class WalletConnectInteractor(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWalletConnectReadyForDeepLinks) {
|
if (isWalletConnectReadyForDeepLinks) {
|
||||||
val action = WalletConnectAction.OpenSession(deeplink, WalletConnectAction.OpenSession.SourceType.DEEPLINK)
|
val action = WalletConnectAction.OpenSession(
|
||||||
|
wcUri = deeplink,
|
||||||
|
source = WalletConnectAction.OpenSession.SourceType.DEEPLINK,
|
||||||
|
userWalletId = UserWalletId(userWalletId),
|
||||||
|
)
|
||||||
store.dispatchOnMain(action)
|
store.dispatchOnMain(action)
|
||||||
} else {
|
} else {
|
||||||
deeplinkStack.push(deeplink)
|
deeplinkStack.push(deeplink)
|
||||||
|
|
@ -410,7 +419,7 @@ class WalletConnectInteractor(
|
||||||
|
|
||||||
private suspend fun getAccountsForWc(userWallet: UserWallet, networks: List<Network>): List<Account> {
|
private suspend fun getAccountsForWc(userWallet: UserWallet, networks: List<Network>): List<Account> {
|
||||||
val walletManagers = networks.mapNotNull {
|
val walletManagers = networks.mapNotNull {
|
||||||
val blockchain = Blockchain.fromId(it.id.value)
|
val blockchain = Blockchain.fromId(it.rawId)
|
||||||
walletManagersFacade.getOrCreateWalletManager(
|
walletManagersFacade.getOrCreateWalletManager(
|
||||||
userWalletId = userWallet.walletId,
|
userWalletId = userWallet.walletId,
|
||||||
blockchain = blockchain,
|
blockchain = blockchain,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.tap.features.details.redux.walletconnect
|
package com.tangem.tap.features.details.redux.walletconnect
|
||||||
|
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||||
|
|
@ -14,6 +15,7 @@ sealed class WalletConnectAction : Action {
|
||||||
data class OpenSession(
|
data class OpenSession(
|
||||||
val wcUri: String,
|
val wcUri: String,
|
||||||
val source: SourceType,
|
val source: SourceType,
|
||||||
|
val userWalletId: UserWalletId,
|
||||||
) : WalletConnectAction() {
|
) : WalletConnectAction() {
|
||||||
enum class SourceType { QR, DEEPLINK, CLIPBOARD, ETC }
|
enum class SourceType { QR, DEEPLINK, CLIPBOARD, ETC }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,11 @@ class WalletConnectMiddleware {
|
||||||
is WalletConnectAction.OpenSession -> {
|
is WalletConnectAction.OpenSession -> {
|
||||||
val index = action.wcUri.indexOf("@")
|
val index = action.wcUri.indexOf("@")
|
||||||
when (action.wcUri[index + 1]) {
|
when (action.wcUri[index + 1]) {
|
||||||
'2' -> walletConnectRepository.pair(uri = action.wcUri, source = action.source)
|
'2' -> walletConnectRepository.pair(
|
||||||
|
uri = action.wcUri,
|
||||||
|
source = action.source,
|
||||||
|
userWalletId = action.userWalletId,
|
||||||
|
)
|
||||||
'1' -> {
|
'1' -> {
|
||||||
store.dispatchOnMain(WalletConnectAction.UnsupportedDappRequest)
|
store.dispatchOnMain(WalletConnectAction.UnsupportedDappRequest)
|
||||||
store.dispatchOnMain(
|
store.dispatchOnMain(
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@ import org.rekotlin.StoreSubscriber
|
||||||
@Suppress("UnusedPrivateMember")
|
@Suppress("UnusedPrivateMember")
|
||||||
internal class DefaultWalletConnectComponent @AssistedInject constructor(
|
internal class DefaultWalletConnectComponent @AssistedInject constructor(
|
||||||
@Assisted appComponentContext: AppComponentContext,
|
@Assisted appComponentContext: AppComponentContext,
|
||||||
@Assisted params: Unit,
|
@Assisted params: WalletConnectComponent.Params,
|
||||||
) : WalletConnectComponent, AppComponentContext by appComponentContext, StoreSubscriber<WalletConnectState> {
|
) : WalletConnectComponent, AppComponentContext by appComponentContext, StoreSubscriber<WalletConnectState> {
|
||||||
|
|
||||||
private val model: WalletConnectModel = getOrCreateModel()
|
private val model: WalletConnectModel = getOrCreateModel(params)
|
||||||
|
|
||||||
private var screenState: MutableState<WalletConnectScreenState> =
|
private var screenState: MutableState<WalletConnectScreenState> =
|
||||||
mutableStateOf(model.updateState(store.state.walletConnectState))
|
mutableStateOf(model.updateState(store.state.walletConnectState))
|
||||||
|
|
@ -65,6 +65,9 @@ internal class DefaultWalletConnectComponent @AssistedInject constructor(
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory : WalletConnectComponent.Factory {
|
interface Factory : WalletConnectComponent.Factory {
|
||||||
override fun create(context: AppComponentContext, params: Unit): DefaultWalletConnectComponent
|
override fun create(
|
||||||
|
context: AppComponentContext,
|
||||||
|
params: WalletConnectComponent.Params,
|
||||||
|
): DefaultWalletConnectComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,12 @@ import androidx.compose.runtime.Stable
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
import com.tangem.domain.qrscanning.models.SourceType
|
import com.tangem.domain.qrscanning.models.SourceType
|
||||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||||
|
import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
@ -22,13 +24,22 @@ import javax.inject.Inject
|
||||||
internal class WalletConnectModel @Inject constructor(
|
internal class WalletConnectModel @Inject constructor(
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
||||||
|
paramsContainer: ParamsContainer,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
|
private val params = paramsContainer.require<WalletConnectComponent.Params>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
listenToQrScanningUseCase(SourceType.WALLET_CONNECT)
|
listenToQrScanningUseCase(SourceType.WALLET_CONNECT)
|
||||||
.getOrElse { emptyFlow() }
|
.getOrElse { emptyFlow() }
|
||||||
.map { WalletConnectAction.OpenSession(it, WalletConnectAction.OpenSession.SourceType.QR) }
|
.map {
|
||||||
|
WalletConnectAction.OpenSession(
|
||||||
|
wcUri = it,
|
||||||
|
source = WalletConnectAction.OpenSession.SourceType.QR,
|
||||||
|
userWalletId = params.userWalletId,
|
||||||
|
)
|
||||||
|
}
|
||||||
.collect { store.dispatch(it) }
|
.collect { store.dispatch(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ package com.tangem.tap.features.details.ui.walletconnect.api
|
||||||
|
|
||||||
import com.tangem.core.decompose.factory.ComponentFactory
|
import com.tangem.core.decompose.factory.ComponentFactory
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
|
||||||
interface WalletConnectComponent : ComposableContentComponent {
|
interface WalletConnectComponent : ComposableContentComponent {
|
||||||
interface Factory : ComponentFactory<Unit, WalletConnectComponent>
|
data class Params(val userWalletId: UserWalletId)
|
||||||
|
interface Factory : ComponentFactory<Params, WalletConnectComponent>
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||||
import com.tangem.common.keyboard.KeyboardValidator
|
import com.tangem.common.keyboard.KeyboardValidator
|
||||||
|
import com.tangem.common.routing.RoutingFeatureToggle
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.analytics.models.event.TechAnalyticsEvent
|
import com.tangem.core.analytics.models.event.TechAnalyticsEvent
|
||||||
|
|
@ -22,6 +23,10 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings
|
||||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||||
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||||
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
||||||
|
import com.tangem.domain.notifications.GetApplicationIdUseCase
|
||||||
|
import com.tangem.domain.notifications.SendPushTokenUseCase
|
||||||
|
import com.tangem.domain.notifications.models.ApplicationId
|
||||||
|
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||||
import com.tangem.domain.onramp.FetchHotCryptoUseCase
|
import com.tangem.domain.onramp.FetchHotCryptoUseCase
|
||||||
import com.tangem.domain.promo.GetStoryContentUseCase
|
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||||
|
|
@ -31,6 +36,9 @@ import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
|
||||||
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
||||||
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
|
import com.tangem.domain.wallets.usecase.AssociateWalletsWithApplicationIdUseCase
|
||||||
|
import com.tangem.domain.wallets.usecase.GetSavedWalletChangesUseCase
|
||||||
|
import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase
|
||||||
import com.tangem.feature.swap.analytics.StoriesEvents
|
import com.tangem.feature.swap.analytics.StoriesEvents
|
||||||
import com.tangem.features.onramp.deeplink.OnrampDeepLink
|
import com.tangem.features.onramp.deeplink.OnrampDeepLink
|
||||||
import com.tangem.tap.common.extensions.setContext
|
import com.tangem.tap.common.extensions.setContext
|
||||||
|
|
@ -38,11 +46,15 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import com.tangem.wallet.BuildConfig
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
|
|
@ -56,7 +68,6 @@ internal class MainViewModel @Inject constructor(
|
||||||
private val userWalletsListManager: UserWalletsListManager,
|
private val userWalletsListManager: UserWalletsListManager,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
|
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
|
||||||
private val apiConfigsManager: ApiConfigsManager,
|
|
||||||
private val fetchUserCountryUseCase: FetchUserCountryUseCase,
|
private val fetchUserCountryUseCase: FetchUserCountryUseCase,
|
||||||
@GlobalUiMessageSender private val messageSender: UiMessageSender,
|
@GlobalUiMessageSender private val messageSender: UiMessageSender,
|
||||||
private val keyboardValidator: KeyboardValidator,
|
private val keyboardValidator: KeyboardValidator,
|
||||||
|
|
@ -67,6 +78,14 @@ internal class MainViewModel @Inject constructor(
|
||||||
private val onboardingRepository: OnboardingRepository,
|
private val onboardingRepository: OnboardingRepository,
|
||||||
private val deepLinksRegistry: DeepLinksRegistry,
|
private val deepLinksRegistry: DeepLinksRegistry,
|
||||||
private val onrampDeepLinkFactory: OnrampDeepLink.Factory,
|
private val onrampDeepLinkFactory: OnrampDeepLink.Factory,
|
||||||
|
private val notificationsToggles: NotificationsFeatureToggles,
|
||||||
|
private val getApplicationIdUseCase: GetApplicationIdUseCase,
|
||||||
|
private val subscribeOnWalletsUseCase: GetSavedWalletChangesUseCase,
|
||||||
|
private val associateWalletsWithApplicationIdUseCase: AssociateWalletsWithApplicationIdUseCase,
|
||||||
|
private val updateRemoteWalletsInfoUseCase: UpdateRemoteWalletsInfoUseCase,
|
||||||
|
private val sendPushTokenUseCase: SendPushTokenUseCase,
|
||||||
|
private val apiConfigsManager: ApiConfigsManager,
|
||||||
|
routingFeatureToggle: RoutingFeatureToggle,
|
||||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
|
@ -77,32 +96,38 @@ internal class MainViewModel @Inject constructor(
|
||||||
private set
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
/**
|
||||||
|
* Run any data initialization here that needs to happen before the app starts
|
||||||
|
* and is hidden behind the SplashScreen
|
||||||
|
*/
|
||||||
loadApplicationResources()
|
loadApplicationResources()
|
||||||
|
|
||||||
viewModelScope.launch(dispatchers.main) { incrementAppLaunchCounterUseCase() }
|
/** Run any API data load here that runs in parallel and does not block the app from starting */
|
||||||
|
launchAPIRequests {
|
||||||
|
launch { fetchHotCryptoUseCase() }
|
||||||
|
|
||||||
viewModelScope.launch {
|
launch { fetchAppCurrenciesUseCase() }
|
||||||
fetchUserCountryUseCase().onLeft {
|
|
||||||
Timber.e("Unable to fetch the user country code $it")
|
launch { fetchStakingTokens() }
|
||||||
}
|
|
||||||
|
launch { initPushNotifications() }
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch { fetchHotCryptoUseCase() }
|
viewModelScope.launch { incrementAppLaunchCounterUseCase() }
|
||||||
|
|
||||||
updateAppCurrencies()
|
|
||||||
observeFlips()
|
observeFlips()
|
||||||
displayBalancesHidingStatusToast()
|
displayBalancesHidingStatusToast()
|
||||||
displayHiddenBalancesModalNotification()
|
displayHiddenBalancesModalNotification()
|
||||||
|
|
||||||
fetchStakingTokens()
|
|
||||||
|
|
||||||
deleteDeprecatedLogsUseCase()
|
deleteDeprecatedLogsUseCase()
|
||||||
|
|
||||||
sendKeyboardIdentifierEvent()
|
sendKeyboardIdentifierEvent()
|
||||||
|
|
||||||
preloadImages()
|
preloadImages()
|
||||||
|
|
||||||
initializeDeepLinks()
|
if (!routingFeatureToggle.isDeepLinkNavigationEnabled) {
|
||||||
|
initializeDeepLinks()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkForUnfinishedBackup() {
|
fun checkForUnfinishedBackup() {
|
||||||
|
|
@ -114,16 +139,41 @@ internal class MainViewModel @Inject constructor(
|
||||||
|
|
||||||
/** Loading the resources needed to run the application */
|
/** Loading the resources needed to run the application */
|
||||||
private fun loadApplicationResources() {
|
private fun loadApplicationResources() {
|
||||||
viewModelScope.launch(dispatchers.main) {
|
viewModelScope.launch {
|
||||||
apiConfigsManager.initialize()
|
launchAPIRequests {
|
||||||
|
launch { blockchainSDKFactory.init() }
|
||||||
|
|
||||||
|
launch {
|
||||||
|
withTimeout(timeMillis = 1.seconds.inWholeMilliseconds) { fetchUserCountry() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
blockchainSDKFactory.init()
|
|
||||||
prepareSelectedWalletFeedback()
|
prepareSelectedWalletFeedback()
|
||||||
|
|
||||||
isSplashScreenShown = false
|
isSplashScreenShown = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchUserCountry() {
|
||||||
|
fetchUserCountryUseCase().onLeft {
|
||||||
|
Timber.e("Unable to fetch the user country code $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchAPIRequests(function: suspend CoroutineScope.() -> Unit) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||||
|
apiConfigsManager.isInitialized
|
||||||
|
.filter { it }
|
||||||
|
.first() // wait until isInitialized becomes true
|
||||||
|
|
||||||
|
function()
|
||||||
|
} else {
|
||||||
|
function()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun prepareSelectedWalletFeedback() {
|
private fun prepareSelectedWalletFeedback() {
|
||||||
userWalletsListManager.selectedUserWallet
|
userWalletsListManager.selectedUserWallet
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
@ -134,18 +184,10 @@ internal class MainViewModel @Inject constructor(
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateAppCurrencies() {
|
private suspend fun fetchStakingTokens() {
|
||||||
viewModelScope.launch(dispatchers.main) {
|
fetchStakingTokensUseCase()
|
||||||
fetchAppCurrenciesUseCase.invoke()
|
.onLeft { Timber.e(it.toString(), "Unable to fetch the staking tokens list") }
|
||||||
}
|
.onRight { Timber.d("Staking token list was fetched successfully") }
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchStakingTokens() {
|
|
||||||
viewModelScope.launch(dispatchers.main) {
|
|
||||||
fetchStakingTokensUseCase()
|
|
||||||
.onLeft { Timber.e(it.toString(), "Unable to fetch the staking tokens list") }
|
|
||||||
.onRight { Timber.d("Staking token list was fetched successfully") }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeFlips() {
|
private fun observeFlips() {
|
||||||
|
|
@ -343,4 +385,20 @@ internal class MainViewModel @Inject constructor(
|
||||||
private fun initializeDeepLinks() {
|
private fun initializeDeepLinks() {
|
||||||
deepLinksRegistry.register(onrampDeepLinkFactory.create(viewModelScope))
|
deepLinksRegistry.register(onrampDeepLinkFactory.create(viewModelScope))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun initPushNotifications() {
|
||||||
|
if (notificationsToggles.isNotificationsEnabled) {
|
||||||
|
getApplicationIdUseCase().onRight { applicationId ->
|
||||||
|
sendPushTokenUseCase(applicationId = applicationId)
|
||||||
|
associateWalletsWithApplicationId(applicationId = applicationId)
|
||||||
|
updateRemoteWalletsInfoUseCase(applicationId = applicationId)
|
||||||
|
}.onLeft { Timber.e(it.toString()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun associateWalletsWithApplicationId(applicationId: ApplicationId) {
|
||||||
|
subscribeOnWalletsUseCase().onEach { wallets ->
|
||||||
|
associateWalletsWithApplicationIdUseCase(applicationId, wallets)
|
||||||
|
}.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,10 +5,10 @@ import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.common.routing.AppRouter
|
import com.tangem.common.routing.AppRouter
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.network.NetworkAddress
|
||||||
import com.tangem.domain.onramp.model.OnrampSource
|
import com.tangem.domain.onramp.model.OnrampSource
|
||||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
|
||||||
import com.tangem.domain.tokens.model.NetworkAddress
|
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.tap.common.analytics.events.Token
|
import com.tangem.tap.common.analytics.events.Token
|
||||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||||
|
|
@ -79,7 +79,7 @@ object TradeCryptoMiddleware {
|
||||||
|
|
||||||
val status = action.cryptoCurrencyStatus
|
val status = action.cryptoCurrencyStatus
|
||||||
val currency = status.currency
|
val currency = status.currency
|
||||||
val blockchain = Blockchain.fromId(currency.network.id.value)
|
val blockchain = Blockchain.fromId(currency.network.rawId)
|
||||||
val exchangeManager = store.state.globalState.exchangeManager
|
val exchangeManager = store.state.globalState.exchangeManager
|
||||||
val topUrl = exchangeManager.getUrl(
|
val topUrl = exchangeManager.getUrl(
|
||||||
action = CurrencyExchangeManager.Action.Buy,
|
action = CurrencyExchangeManager.Action.Buy,
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,10 @@ internal class DefaultAuthProvider(private val userWalletsListManager: UserWalle
|
||||||
override fun getCardId(): String {
|
override fun getCardId(): String {
|
||||||
return userWalletsListManager.selectedUserWalletSync?.scanResponse?.card?.cardId ?: ""
|
return userWalletsListManager.selectedUserWalletSync?.scanResponse?.card?.cardId ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCardsPublicKeys(): Map<String, String> {
|
||||||
|
return userWalletsListManager.userWalletsSync.associate {
|
||||||
|
it.scanResponse.card.cardId to it.scanResponse.card.cardPublicKey.toHexString()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchain.common.Token
|
import com.tangem.blockchain.common.Token
|
||||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.tap.common.extensions.inject
|
import com.tangem.tap.common.extensions.inject
|
||||||
import com.tangem.tap.domain.model.Currency
|
import com.tangem.tap.domain.model.Currency
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
|
|
@ -44,7 +44,7 @@ internal class CryptoCurrencyConverter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun convertBack(value: CryptoCurrency): Currency {
|
override fun convertBack(value: CryptoCurrency): Currency {
|
||||||
val blockchain = Blockchain.fromId(value.network.id.value)
|
val blockchain = Blockchain.fromId(value.network.rawId)
|
||||||
if (blockchain == Blockchain.Unknown) error("CryptoCurrencyConverter convertBack Unknown blockchain")
|
if (blockchain == Blockchain.Unknown) error("CryptoCurrencyConverter convertBack Unknown blockchain")
|
||||||
return when (value) {
|
return when (value) {
|
||||||
is CryptoCurrency.Coin -> Currency.Blockchain(
|
is CryptoCurrency.Coin -> Currency.Blockchain(
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ import com.tangem.blockchain.extensions.Result
|
||||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||||
import com.tangem.domain.core.utils.lceContent
|
import com.tangem.domain.core.utils.lceContent
|
||||||
import com.tangem.domain.core.utils.lceLoading
|
import com.tangem.domain.core.utils.lceLoading
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
|
||||||
import com.tangem.tap.common.extensions.inject
|
import com.tangem.tap.common.extensions.inject
|
||||||
import com.tangem.tap.common.extensions.safeUpdate
|
import com.tangem.tap.common.extensions.safeUpdate
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
|
|
@ -68,7 +68,7 @@ class CurrencyExchangeManager(
|
||||||
walletAddress: String,
|
walletAddress: String,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
): String? {
|
): String? {
|
||||||
val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value)
|
val blockchain = Blockchain.fromId(cryptoCurrency.network.rawId)
|
||||||
if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl()
|
if (blockchain.isTestnet()) return blockchain.getTestnetTopUpUrl()
|
||||||
|
|
||||||
val urlBuilder = getExchangeUrlBuilder(action)
|
val urlBuilder = getExchangeUrlBuilder(action)
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
|
||||||
import com.tangem.domain.core.lce.Lce
|
import com.tangem.domain.core.lce.Lce
|
||||||
import com.tangem.domain.exchange.ExpressAvailabilityState
|
import com.tangem.domain.exchange.ExpressAvailabilityState
|
||||||
import com.tangem.domain.exchange.RampStateManager
|
import com.tangem.domain.exchange.RampStateManager
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase
|
import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package com.tangem.tap.network.exchangeServices
|
||||||
|
|
||||||
import com.tangem.domain.core.lce.Lce
|
import com.tangem.domain.core.lce.Lce
|
||||||
import com.tangem.domain.core.utils.lceLoading
|
import com.tangem.domain.core.utils.lceLoading
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
|
||||||
import com.tangem.tap.domain.model.Currency
|
import com.tangem.tap.domain.model.Currency
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import com.tangem.data.onramp.legacy.mercuryoNetwork
|
||||||
import com.tangem.domain.core.utils.lceContent
|
import com.tangem.domain.core.utils.lceContent
|
||||||
import com.tangem.domain.core.utils.lceError
|
import com.tangem.domain.core.utils.lceError
|
||||||
import com.tangem.domain.core.utils.lceLoading
|
import com.tangem.domain.core.utils.lceLoading
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
|
||||||
import com.tangem.tap.domain.model.Currency
|
import com.tangem.tap.domain.model.Currency
|
||||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||||
|
|
@ -77,7 +77,7 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
|
||||||
): String {
|
): String {
|
||||||
if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException()
|
if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException()
|
||||||
|
|
||||||
val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value)
|
val blockchain = Blockchain.fromId(cryptoCurrency.network.rawId)
|
||||||
|
|
||||||
val builder = Uri.Builder()
|
val builder = Uri.Builder()
|
||||||
.scheme(ExchangeUrlBuilder.SCHEME)
|
.scheme(ExchangeUrlBuilder.SCHEME)
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import com.tangem.domain.common.extensions.withIOContext
|
||||||
import com.tangem.domain.core.utils.lceContent
|
import com.tangem.domain.core.utils.lceContent
|
||||||
import com.tangem.domain.core.utils.lceError
|
import com.tangem.domain.core.utils.lceError
|
||||||
import com.tangem.domain.core.utils.lceLoading
|
import com.tangem.domain.core.utils.lceLoading
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
|
||||||
import com.tangem.tap.domain.model.Currency
|
import com.tangem.tap.domain.model.Currency
|
||||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||||
|
|
@ -133,7 +133,7 @@ class MoonPayService(
|
||||||
): String? {
|
): String? {
|
||||||
if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException()
|
if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException()
|
||||||
|
|
||||||
val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value)
|
val blockchain = Blockchain.fromId(cryptoCurrency.network.rawId)
|
||||||
val supportedCurrency = blockchain.moonPaySupportedCurrency ?: return null
|
val supportedCurrency = blockchain.moonPaySupportedCurrency ?: return null
|
||||||
val moonpayCurrency = status?.availableForSell?.firstOrNull {
|
val moonpayCurrency = status?.availableForSell?.firstOrNull {
|
||||||
when (cryptoCurrency) {
|
when (cryptoCurrency) {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import com.tangem.tap.routing.component.RoutingComponent
|
||||||
import com.tangem.tap.routing.component.RoutingComponent.Child
|
import com.tangem.tap.routing.component.RoutingComponent.Child
|
||||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||||
import com.tangem.tap.routing.utils.ChildFactory
|
import com.tangem.tap.routing.utils.ChildFactory
|
||||||
|
import com.tangem.tap.routing.utils.DeepLinkFactory
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
|
@ -34,6 +35,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
||||||
private val appRouterConfig: AppRouterConfig,
|
private val appRouterConfig: AppRouterConfig,
|
||||||
private val uiDependencies: UiDependencies,
|
private val uiDependencies: UiDependencies,
|
||||||
private val wcRoutingComponentFactory: WcRoutingComponent.Factory,
|
private val wcRoutingComponentFactory: WcRoutingComponent.Factory,
|
||||||
|
private val deeplinkFactory: DeepLinkFactory,
|
||||||
) : RoutingComponent,
|
) : RoutingComponent,
|
||||||
AppComponentContext by context,
|
AppComponentContext by context,
|
||||||
SnackbarHandler {
|
SnackbarHandler {
|
||||||
|
|
@ -60,7 +62,10 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
||||||
|
|
||||||
stack.subscribe(lifecycle) { stack ->
|
stack.subscribe(lifecycle) { stack ->
|
||||||
val stackItems = stack.items.map { it.configuration }
|
val stackItems = stack.items.map { it.configuration }
|
||||||
|
|
||||||
wcRoutingComponent.onAppRouteChange(stack.active.configuration)
|
wcRoutingComponent.onAppRouteChange(stack.active.configuration)
|
||||||
|
deeplinkFactory.checkRoutingReadiness(stack.active.configuration)
|
||||||
|
|
||||||
if (appRouterConfig.stack != stackItems) {
|
if (appRouterConfig.stack != stackItems) {
|
||||||
appRouterConfig.stack = stackItems
|
appRouterConfig.stack = stackItems
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -291,13 +291,13 @@ internal class ChildFactory @Inject constructor(
|
||||||
if (walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
|
if (walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) {
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = Unit,
|
params = RedesignedWalletConnectComponent.Params(route.userWalletId),
|
||||||
componentFactory = redesignedWalletConnectComponentFactory,
|
componentFactory = redesignedWalletConnectComponentFactory,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
createComponentChild(
|
createComponentChild(
|
||||||
context = context,
|
context = context,
|
||||||
params = Unit,
|
params = WalletConnectComponent.Params(route.userWalletId),
|
||||||
componentFactory = walletConnectComponentFactory,
|
componentFactory = walletConnectComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
package com.tangem.tap.routing.utils
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
|
import com.tangem.common.routing.DeepLinkRoute
|
||||||
|
import com.tangem.common.routing.DeepLinkScheme
|
||||||
|
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
|
||||||
|
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
|
||||||
|
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
|
||||||
|
import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler
|
||||||
|
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
||||||
|
import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler
|
||||||
|
import com.tangem.features.walletconnect.components.deeplink.WalletConnectDeepLinkHandler
|
||||||
|
import com.tangem.utils.coroutines.JobHolder
|
||||||
|
import com.tangem.utils.coroutines.saveIn
|
||||||
|
import dagger.hilt.android.scopes.ActivityScoped
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.transformLatest
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
|
@ActivityScoped
|
||||||
|
internal class DeepLinkFactory @Inject constructor(
|
||||||
|
private val onrampDeepLink: OnrampDeepLinkHandler.Factory,
|
||||||
|
private val sellDeepLink: SellDeepLinkHandler.Factory,
|
||||||
|
private val buyDeepLink: BuyDeepLinkHandler.Factory,
|
||||||
|
private val referralDeepLink: ReferralDeepLinkHandler.Factory,
|
||||||
|
private val walletConnectDeepLink: WalletConnectDeepLinkHandler.Factory,
|
||||||
|
private val walletDeepLink: WalletDeepLinkHandler.Factory,
|
||||||
|
private val tokenDetailsDeepLink: TokenDetailsDeepLinkHandler.Factory,
|
||||||
|
) {
|
||||||
|
private val permittedAppRoute = MutableStateFlow(false)
|
||||||
|
|
||||||
|
private var lastDeepLink: Uri? = null
|
||||||
|
private val deepLinkHandlerJobHolder = JobHolder()
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
fun handleDeeplink(deeplinkUri: Uri, coroutineScope: CoroutineScope) {
|
||||||
|
lastDeepLink = deeplinkUri
|
||||||
|
|
||||||
|
Timber.i(
|
||||||
|
"""
|
||||||
|
Received deep link intent
|
||||||
|
|- Received URI: $deeplinkUri
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
permittedAppRoute
|
||||||
|
.transformLatest<Boolean, Unit> { isPermitted ->
|
||||||
|
if (isPermitted) {
|
||||||
|
lastDeepLink?.let {
|
||||||
|
launchDeepLink(it, coroutineScope)
|
||||||
|
}
|
||||||
|
lastDeepLink = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(coroutineScope)
|
||||||
|
.saveIn(deepLinkHandlerJobHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if app is ready to handle deeplink
|
||||||
|
*/
|
||||||
|
fun checkRoutingReadiness(appRoute: AppRoute) {
|
||||||
|
permittedAppRoute.value = when (appRoute) {
|
||||||
|
AppRoute.Initial,
|
||||||
|
AppRoute.Home,
|
||||||
|
is AppRoute.Welcome,
|
||||||
|
is AppRoute.Disclaimer,
|
||||||
|
is AppRoute.Stories,
|
||||||
|
is AppRoute.Onboarding,
|
||||||
|
-> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchDeepLink(deeplinkUri: Uri, coroutineScope: CoroutineScope) {
|
||||||
|
when (deeplinkUri.scheme) {
|
||||||
|
DeepLinkScheme.Tangem.scheme -> handleTangemDeepLinks(deeplinkUri, coroutineScope)
|
||||||
|
DeepLinkScheme.WalletConnect.scheme -> walletConnectDeepLink.create(deeplinkUri)
|
||||||
|
else -> {
|
||||||
|
Timber.i(
|
||||||
|
"""
|
||||||
|
No match found for deep link
|
||||||
|
|- Received URI: $deeplinkUri
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleTangemDeepLinks(deeplinkUri: Uri, coroutineScope: CoroutineScope) {
|
||||||
|
val queryParams = getQueryParams(deeplinkUri)
|
||||||
|
when (deeplinkUri.host) {
|
||||||
|
DeepLinkRoute.Onramp.host -> onrampDeepLink.create(coroutineScope, queryParams)
|
||||||
|
DeepLinkRoute.Sell.host -> sellDeepLink.create(coroutineScope, queryParams)
|
||||||
|
DeepLinkRoute.Buy.host -> buyDeepLink.create(coroutineScope)
|
||||||
|
DeepLinkRoute.Referral.host -> referralDeepLink.create()
|
||||||
|
DeepLinkRoute.Wallet.host -> walletDeepLink.create()
|
||||||
|
DeepLinkRoute.TokenDetails.host -> tokenDetailsDeepLink.create(coroutineScope, queryParams)
|
||||||
|
else -> {
|
||||||
|
Timber.i(
|
||||||
|
"""
|
||||||
|
No match found for deep link
|
||||||
|
|- Received URI: $deeplinkUri
|
||||||
|
|- With params: $queryParams
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getQueryParams(uri: Uri): Map<String, String> {
|
||||||
|
val params = mutableMapOf<String, String>()
|
||||||
|
|
||||||
|
uri.queryParameterNames.forEach { paramName ->
|
||||||
|
val paramValue = uri.getQueryParameter(paramName)
|
||||||
|
|
||||||
|
if (paramName.validate() && paramValue?.validate() == true) {
|
||||||
|
params[paramName] = paramValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for malicious symbol in uri part
|
||||||
|
*/
|
||||||
|
private fun String.validate(): Boolean {
|
||||||
|
val regex = DEEPLINK_VALIDATION_REGEX.toRegex()
|
||||||
|
|
||||||
|
return !regex.containsMatchIn(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val DEEPLINK_VALIDATION_REGEX = "['\";<>()+\\\\]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,324 @@
|
||||||
|
package com.tangem.tap.routing.utils
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
|
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
|
||||||
|
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
|
||||||
|
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
|
||||||
|
import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler
|
||||||
|
import com.tangem.features.tokendetails.deeplink.TokenDetailsDeepLinkHandler
|
||||||
|
import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler
|
||||||
|
import com.tangem.features.walletconnect.components.deeplink.WalletConnectDeepLinkHandler
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.test.*
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class DeepLinkFactoryTest {
|
||||||
|
|
||||||
|
private val onrampDeepLinkFactory = mockk<OnrampDeepLinkHandler.Factory>(relaxed = true) {
|
||||||
|
every { create(any(), any()) } returns mockk()
|
||||||
|
}
|
||||||
|
private val sellDeepLinkFactory = mockk<SellDeepLinkHandler.Factory>(relaxed = true) {
|
||||||
|
every { create(any(), any()) } returns mockk()
|
||||||
|
}
|
||||||
|
private val buyDeepLinkFactory = mockk<BuyDeepLinkHandler.Factory>(relaxed = true) {
|
||||||
|
every { create(any()) } returns mockk()
|
||||||
|
}
|
||||||
|
private val referralDeepLinkFactory = mockk<ReferralDeepLinkHandler.Factory>(relaxed = true) {
|
||||||
|
every { create() } returns mockk()
|
||||||
|
}
|
||||||
|
private val walletConnectDeepLinkFactory = mockk<WalletConnectDeepLinkHandler.Factory>(relaxed = true) {
|
||||||
|
every { create(any()) } returns mockk()
|
||||||
|
}
|
||||||
|
private val walletDeepLinkFactory = mockk<WalletDeepLinkHandler.Factory>(relaxed = true) {
|
||||||
|
every { create() } returns mockk()
|
||||||
|
}
|
||||||
|
private val tokenDetailsDeepLinkFactory = mockk<TokenDetailsDeepLinkHandler.Factory>(relaxed = true) {
|
||||||
|
every { create(any(), any()) } returns mockk()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mockedUri = mockk<Uri>(relaxed = true)
|
||||||
|
|
||||||
|
private lateinit var testDispatcher: TestDispatcher
|
||||||
|
private lateinit var testScope: TestScope
|
||||||
|
|
||||||
|
private val deepLinkFactory = DeepLinkFactory(
|
||||||
|
onrampDeepLinkFactory,
|
||||||
|
sellDeepLinkFactory,
|
||||||
|
buyDeepLinkFactory,
|
||||||
|
referralDeepLinkFactory,
|
||||||
|
walletConnectDeepLinkFactory,
|
||||||
|
walletDeepLinkFactory,
|
||||||
|
tokenDetailsDeepLinkFactory,
|
||||||
|
)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
testDispatcher = StandardTestDispatcher()
|
||||||
|
testScope = TestScope(testDispatcher)
|
||||||
|
|
||||||
|
Dispatchers.setMain(testDispatcher)
|
||||||
|
|
||||||
|
every { mockedUri.path } returns "/path"
|
||||||
|
every { mockedUri.toString() } returns "https://example.com/path?query=param"
|
||||||
|
every { mockedUri.authority } returns "example.com"
|
||||||
|
every { mockedUri.port } returns 443 // Default HTTPS port
|
||||||
|
every { mockedUri.fragment } returns null // No fragment in this URI
|
||||||
|
|
||||||
|
Timber.uprootAll() // Disable Timber logging for tests
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
// Reset the main dispatcher
|
||||||
|
Dispatchers.resetMain()
|
||||||
|
// Clean up test coroutines
|
||||||
|
testScope.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `handleDeeplink stores uri and launches when permitted`() = runTest {
|
||||||
|
every { mockedUri.scheme } returns "tangem"
|
||||||
|
every { mockedUri.host } returns "onramp"
|
||||||
|
every { mockedUri.query } returns "param=value"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("param")
|
||||||
|
every { mockedUri.getQueryParameter("param") } returns "value"
|
||||||
|
|
||||||
|
// Set permittedAppRoute to true
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||||
|
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
// Verify onramp handler was called
|
||||||
|
verify {
|
||||||
|
onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `handleDeeplink does not launch when not permitted`() = runTest {
|
||||||
|
every { mockedUri.scheme } returns "tangem"
|
||||||
|
every { mockedUri.host } returns "onramp"
|
||||||
|
every { mockedUri.query } returns "param=value"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("param")
|
||||||
|
every { mockedUri.getQueryParameter("param") } returns "value"
|
||||||
|
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
deepLinkFactory.checkRoutingReadiness(AppRoute.Initial)
|
||||||
|
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
// Verify no handler was called
|
||||||
|
verify(inverse = true) { onrampDeepLinkFactory.create(any(), any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `launchDeepLink handles tangem scheme correctly`() = runTest {
|
||||||
|
every { mockedUri.scheme } returns "tangem"
|
||||||
|
every { mockedUri.host } returns "onramp"
|
||||||
|
every { mockedUri.query } returns "param=value"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("param")
|
||||||
|
every { mockedUri.getQueryParameter("param") } returns "value"
|
||||||
|
|
||||||
|
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `launchDeepLink handles wc scheme correctly`() = runTest {
|
||||||
|
every { mockedUri.scheme } returns "wc"
|
||||||
|
every { mockedUri.host } returns ""
|
||||||
|
|
||||||
|
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
walletConnectDeepLinkFactory.create(eq(mockedUri))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `launchDeepLink ignores unknown scheme`() = runTest {
|
||||||
|
every { mockedUri.scheme } returns "https"
|
||||||
|
every { mockedUri.host } returns "example.com"
|
||||||
|
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
verify(inverse = true) {
|
||||||
|
onrampDeepLinkFactory.create(any(), any())
|
||||||
|
sellDeepLinkFactory.create(any(), any())
|
||||||
|
buyDeepLinkFactory.create(any())
|
||||||
|
referralDeepLinkFactory.create()
|
||||||
|
walletConnectDeepLinkFactory.create(any())
|
||||||
|
walletDeepLinkFactory.create()
|
||||||
|
tokenDetailsDeepLinkFactory.create(any(), any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `handleTangemDeepLinks routes to correct handler`() = runTest {
|
||||||
|
every { mockedUri.scheme } returns "tangem"
|
||||||
|
every { mockedUri.query } returns "param=value"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("param")
|
||||||
|
every { mockedUri.getQueryParameter("param") } returns "value"
|
||||||
|
|
||||||
|
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||||
|
|
||||||
|
// Test Onramp
|
||||||
|
every { mockedUri.host } returns "onramp"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
|
||||||
|
|
||||||
|
// Test Sell
|
||||||
|
every { mockedUri.host } returns "redirect_sell"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { sellDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
|
||||||
|
|
||||||
|
// Test Token Details
|
||||||
|
every { mockedUri.host } returns "token"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { tokenDetailsDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
|
||||||
|
|
||||||
|
// Reset params
|
||||||
|
every { mockedUri.queryParameterNames } returns emptySet()
|
||||||
|
every { mockedUri.getQueryParameter(any()) } returns ""
|
||||||
|
|
||||||
|
// Test Buy
|
||||||
|
every { mockedUri.host } returns "redirect"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { buyDeepLinkFactory.create(eq(testScope)) }
|
||||||
|
|
||||||
|
// Test Referral
|
||||||
|
every { mockedUri.host } returns "referral"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { referralDeepLinkFactory.create() }
|
||||||
|
|
||||||
|
// Test Wallet
|
||||||
|
every { mockedUri.host } returns "main"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { walletDeepLinkFactory.create() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `handleTangemDeepLinks incorrect host`() = runTest {
|
||||||
|
every { mockedUri.scheme } returns "tangem"
|
||||||
|
every { mockedUri.host } returns "unknown"
|
||||||
|
|
||||||
|
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
verify(inverse = true) {
|
||||||
|
onrampDeepLinkFactory.create(any(), any())
|
||||||
|
sellDeepLinkFactory.create(any(), any())
|
||||||
|
buyDeepLinkFactory.create(any())
|
||||||
|
referralDeepLinkFactory.create()
|
||||||
|
walletConnectDeepLinkFactory.create(any())
|
||||||
|
walletDeepLinkFactory.create()
|
||||||
|
tokenDetailsDeepLinkFactory.create(any(), any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getParams filters malicious parameters`() = runTest {
|
||||||
|
every { mockedUri.scheme } returns "tangem"
|
||||||
|
every { mockedUri.host } returns "onramp"
|
||||||
|
every { mockedUri.query } returns "safe=ok&malicious=%3Cscript%3E"e=O%27Brien"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("safe", "malicious", "quote")
|
||||||
|
every { mockedUri.getQueryParameter("safe") } returns "ok"
|
||||||
|
every { mockedUri.getQueryParameter("malicious") } returns "<script>"
|
||||||
|
every { mockedUri.getQueryParameter("quote") } returns "O'Brien"
|
||||||
|
|
||||||
|
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("safe" to "ok"))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `validate detects malicious characters`() = runTest {
|
||||||
|
every { mockedUri.scheme } returns "tangem"
|
||||||
|
every { mockedUri.host } returns "onramp"
|
||||||
|
|
||||||
|
deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
|
||||||
|
|
||||||
|
every { mockedUri.query } returns "safe=ok"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("safe")
|
||||||
|
every { mockedUri.getQueryParameter("safe") } returns "ok"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("safe" to "ok"))) }
|
||||||
|
|
||||||
|
every { mockedUri.query } returns "param123=ok"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("param123")
|
||||||
|
every { mockedUri.getQueryParameter("param123") } returns "ok"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param123" to "ok"))) }
|
||||||
|
|
||||||
|
every { mockedUri.query } returns "unsafe=<script>"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||||
|
every { mockedUri.getQueryParameter("unsafe") } returns "<script>"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||||
|
|
||||||
|
every { mockedUri.query } returns "unsafe=O'Brien"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||||
|
every { mockedUri.getQueryParameter("unsafe") } returns "O'Brien"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||||
|
|
||||||
|
every { mockedUri.query } returns "unsafe=test;"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||||
|
every { mockedUri.getQueryParameter("unsafe") } returns "test;"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||||
|
|
||||||
|
every { mockedUri.query } returns "unsafe=test+attack"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||||
|
every { mockedUri.getQueryParameter("unsafe") } returns "test+attack"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||||
|
|
||||||
|
every { mockedUri.query } returns "unsafe=test\\path"
|
||||||
|
every { mockedUri.queryParameterNames } returns setOf("unsafe")
|
||||||
|
every { mockedUri.getQueryParameter("unsafe") } returns "test\\path"
|
||||||
|
deepLinkFactory.handleDeeplink(mockedUri, testScope)
|
||||||
|
advanceUntilIdle()
|
||||||
|
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
/* Core */
|
/* Core */
|
||||||
implementation(projects.core.decompose)
|
implementation(projects.core.decompose)
|
||||||
|
implementation(projects.core.configToggles)
|
||||||
|
|
||||||
/* Domain */
|
/* Domain */
|
||||||
implementation(projects.domain.qrScanning.models)
|
implementation(projects.domain.qrScanning.models)
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ import com.tangem.common.routing.entity.SerializableIntent
|
||||||
import com.tangem.core.decompose.navigation.Route
|
import com.tangem.core.decompose.navigation.Route
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.markets.TokenMarketParams
|
import com.tangem.domain.markets.TokenMarketParams
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.nft.models.NFTAsset
|
import com.tangem.domain.nft.models.NFTAsset
|
||||||
import com.tangem.domain.onramp.model.OnrampSource
|
import com.tangem.domain.onramp.model.OnrampSource
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object WalletConnectSessions : AppRoute(path = "/wallet_connect_sessions")
|
data class WalletConnectSessions(val userWalletId: UserWalletId) : AppRoute(path = "/wallet_connect_sessions")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class QrScanning(val source: Source) : AppRoute(path = "/$source/qr_scanning${source.path}") {
|
data class QrScanning(val source: Source) : AppRoute(path = "/$source/qr_scanning${source.path}") {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.tangem.common.routing
|
||||||
|
|
||||||
|
sealed class DeepLinkRoute {
|
||||||
|
|
||||||
|
abstract val host: String
|
||||||
|
|
||||||
|
data object Onramp : DeepLinkRoute() {
|
||||||
|
override val host: String = "onramp"
|
||||||
|
}
|
||||||
|
|
||||||
|
data object Sell : DeepLinkRoute() {
|
||||||
|
override val host: String = "redirect_sell"
|
||||||
|
}
|
||||||
|
|
||||||
|
data object Buy : DeepLinkRoute() {
|
||||||
|
override val host: String = "redirect"
|
||||||
|
}
|
||||||
|
|
||||||
|
data object Referral : DeepLinkRoute() {
|
||||||
|
override val host: String = "referral"
|
||||||
|
}
|
||||||
|
|
||||||
|
data object Wallet : DeepLinkRoute() {
|
||||||
|
override val host: String = "main"
|
||||||
|
}
|
||||||
|
|
||||||
|
data object TokenDetails : DeepLinkRoute() {
|
||||||
|
override val host: String = "token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DeepLinkScheme(val scheme: String) {
|
||||||
|
Tangem(scheme = "tangem"),
|
||||||
|
WalletConnect(scheme = "wc"),
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.tangem.common.routing
|
||||||
|
|
||||||
|
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
|
|
||||||
|
class RoutingFeatureToggle(
|
||||||
|
private val featureTogglesManager: FeatureTogglesManager,
|
||||||
|
) {
|
||||||
|
|
||||||
|
val isDeepLinkNavigationEnabled: Boolean
|
||||||
|
get() = featureTogglesManager.isFeatureEnabled(name = "DEEPLINK_NAVIGATION_ENABLED")
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ dependencies {
|
||||||
implementation(projects.domain.models)
|
implementation(projects.domain.models)
|
||||||
implementation(projects.domain.staking.models)
|
implementation(projects.domain.staking.models)
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
|
implementation(projects.domain.txhistory.models)
|
||||||
implementation(projects.domain.wallets)
|
implementation(projects.domain.wallets)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
|
|
||||||
|
|
@ -30,4 +31,6 @@ dependencies {
|
||||||
|
|
||||||
implementation(tangemDeps.blockchain)
|
implementation(tangemDeps.blockchain)
|
||||||
implementation(tangemDeps.card.core)
|
implementation(tangemDeps.card.core)
|
||||||
|
|
||||||
|
implementation(deps.test.junit5)
|
||||||
}
|
}
|
||||||
|
|
@ -2,9 +2,9 @@ package com.tangem.common.test.domain.network
|
||||||
|
|
||||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||||
import com.tangem.domain.models.StatusSource
|
import com.tangem.domain.models.StatusSource
|
||||||
import com.tangem.domain.tokens.model.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.tokens.model.NetworkAddress
|
import com.tangem.domain.models.network.NetworkAddress
|
||||||
import com.tangem.domain.tokens.model.NetworkStatus
|
import com.tangem.domain.models.network.NetworkStatus
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -14,36 +14,41 @@ object MockNetworkStatusFactory {
|
||||||
|
|
||||||
private val defaultNetwork = MockCryptoCurrencyFactory().ethereum.network
|
private val defaultNetwork = MockCryptoCurrencyFactory().ethereum.network
|
||||||
|
|
||||||
fun createVerified(network: Network = defaultNetwork): NetworkStatus {
|
fun createVerified(
|
||||||
|
network: Network = defaultNetwork,
|
||||||
|
source: StatusSource = StatusSource.ACTUAL,
|
||||||
|
transform: (NetworkStatus.Verified) -> NetworkStatus.Verified = { it },
|
||||||
|
): NetworkStatus {
|
||||||
return NetworkStatus(
|
return NetworkStatus(
|
||||||
network = network,
|
network = network,
|
||||||
value = NetworkStatus.Verified(
|
value = NetworkStatus.Verified(
|
||||||
address = NetworkAddress.Single(
|
address = NetworkAddress.Single(
|
||||||
defaultAddress = NetworkAddress.Address(
|
defaultAddress = NetworkAddress.Address(
|
||||||
value = "0x123",
|
value = "0x1",
|
||||||
type = NetworkAddress.Address.Type.Primary,
|
type = NetworkAddress.Address.Type.Primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
amounts = mapOf(),
|
amounts = mapOf(),
|
||||||
pendingTransactions = mapOf(),
|
pendingTransactions = mapOf(),
|
||||||
source = StatusSource.ACTUAL,
|
source = source,
|
||||||
),
|
)
|
||||||
|
.let(transform),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createNoAccount(network: Network = defaultNetwork): NetworkStatus {
|
fun createNoAccount(network: Network = defaultNetwork, source: StatusSource = StatusSource.ACTUAL): NetworkStatus {
|
||||||
return NetworkStatus(
|
return NetworkStatus(
|
||||||
network = network,
|
network = network,
|
||||||
value = NetworkStatus.NoAccount(
|
value = NetworkStatus.NoAccount(
|
||||||
address = NetworkAddress.Single(
|
address = NetworkAddress.Single(
|
||||||
defaultAddress = NetworkAddress.Address(
|
defaultAddress = NetworkAddress.Address(
|
||||||
value = "0x123",
|
value = "0x1",
|
||||||
type = NetworkAddress.Address.Type.Primary,
|
type = NetworkAddress.Address.Type.Primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
amountToCreateAccount = BigDecimal.ONE,
|
amountToCreateAccount = BigDecimal.ONE,
|
||||||
errorMessage = "",
|
errorMessage = "",
|
||||||
source = StatusSource.ACTUAL,
|
source = source,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ import com.tangem.data.common.currency.getNetworkDerivationPath
|
||||||
import com.tangem.data.common.currency.getNetworkStandardType
|
import com.tangem.data.common.currency.getNetworkStandardType
|
||||||
import com.tangem.domain.common.configs.GenericCardConfig
|
import com.tangem.domain.common.configs.GenericCardConfig
|
||||||
import com.tangem.domain.common.util.derivationStyleProvider
|
import com.tangem.domain.common.util.derivationStyleProvider
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
|
||||||
import com.tangem.domain.tokens.model.Network
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
|
|
@ -47,16 +47,18 @@ class MockCryptoCurrencyFactory(private val scanResponse: ScanResponse = default
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createCoin(blockchain: Blockchain): CryptoCurrency {
|
fun createCoin(blockchain: Blockchain): CryptoCurrency {
|
||||||
|
val derivationPath = getNetworkDerivationPath(
|
||||||
|
blockchain = blockchain,
|
||||||
|
extraDerivationPath = null,
|
||||||
|
cardDerivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||||
|
)
|
||||||
|
|
||||||
val network = Network(
|
val network = Network(
|
||||||
id = Network.ID(blockchain.id),
|
id = Network.ID(blockchain.id, derivationPath),
|
||||||
backendId = blockchain.toNetworkId(),
|
backendId = blockchain.toNetworkId(),
|
||||||
name = blockchain.fullName,
|
name = blockchain.fullName,
|
||||||
isTestnet = blockchain.isTestnet(),
|
isTestnet = blockchain.isTestnet(),
|
||||||
derivationPath = getNetworkDerivationPath(
|
derivationPath = derivationPath,
|
||||||
blockchain = blockchain,
|
|
||||||
extraDerivationPath = null,
|
|
||||||
cardDerivationStyleProvider = scanResponse.derivationStyleProvider,
|
|
||||||
),
|
|
||||||
currencySymbol = blockchain.currency,
|
currencySymbol = blockchain.currency,
|
||||||
standardType = getNetworkStandardType(blockchain),
|
standardType = getNetworkStandardType(blockchain),
|
||||||
hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource,
|
hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource,
|
||||||
|
|
@ -69,6 +71,12 @@ class MockCryptoCurrencyFactory(private val scanResponse: ScanResponse = default
|
||||||
|
|
||||||
// Impossible to create custom token by CryptoCurrencyFactory because it works with URI under the hood
|
// Impossible to create custom token by CryptoCurrencyFactory because it works with URI under the hood
|
||||||
fun createCustomToken(blockchain: Blockchain, derivationBlockchain: Blockchain): CryptoCurrency {
|
fun createCustomToken(blockchain: Blockchain, derivationBlockchain: Blockchain): CryptoCurrency {
|
||||||
|
val derivationPath = Network.DerivationPath.Custom(
|
||||||
|
value = derivationBlockchain.derivationPath(
|
||||||
|
scanResponse.derivationStyleProvider.getDerivationStyle(),
|
||||||
|
)!!.rawPath,
|
||||||
|
)
|
||||||
|
|
||||||
return CryptoCurrency.Token(
|
return CryptoCurrency.Token(
|
||||||
id = CryptoCurrency.ID(
|
id = CryptoCurrency.ID(
|
||||||
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||||
|
|
@ -76,15 +84,11 @@ class MockCryptoCurrencyFactory(private val scanResponse: ScanResponse = default
|
||||||
suffix = CryptoCurrency.ID.Suffix.RawID(blockchain.id),
|
suffix = CryptoCurrency.ID.Suffix.RawID(blockchain.id),
|
||||||
),
|
),
|
||||||
network = Network(
|
network = Network(
|
||||||
id = Network.ID(value = blockchain.id),
|
id = Network.ID(value = blockchain.id, derivationPath),
|
||||||
backendId = "NEVER-MIND",
|
backendId = "NEVER-MIND",
|
||||||
name = blockchain.fullName,
|
name = blockchain.fullName,
|
||||||
currencySymbol = "NEVER-MIND",
|
currencySymbol = "NEVER-MIND",
|
||||||
derivationPath = Network.DerivationPath.Custom(
|
derivationPath = derivationPath,
|
||||||
value = derivationBlockchain.derivationPath(
|
|
||||||
scanResponse.derivationStyleProvider.getDerivationStyle(),
|
|
||||||
)!!.rawPath,
|
|
||||||
),
|
|
||||||
isTestnet = false,
|
isTestnet = false,
|
||||||
standardType = Network.StandardType.ERC20,
|
standardType = Network.StandardType.ERC20,
|
||||||
hasFiatFeeRate = true,
|
hasFiatFeeRate = true,
|
||||||
|
|
@ -102,7 +106,13 @@ class MockCryptoCurrencyFactory(private val scanResponse: ScanResponse = default
|
||||||
|
|
||||||
fun createToken(blockchain: Blockchain): CryptoCurrency {
|
fun createToken(blockchain: Blockchain): CryptoCurrency {
|
||||||
return factory.createToken(
|
return factory.createToken(
|
||||||
sdkToken = Token(symbol = "NEVER-MIND", contractAddress = "NEVER-MIND", decimals = 8),
|
sdkToken = Token(
|
||||||
|
name = "NEVER-MIND",
|
||||||
|
symbol = "NEVER-MIND",
|
||||||
|
contractAddress = "NEVER-MIND",
|
||||||
|
decimals = 8,
|
||||||
|
id = "NEVER-MIND",
|
||||||
|
),
|
||||||
blockchain = blockchain,
|
blockchain = blockchain,
|
||||||
extraDerivationPath = null,
|
extraDerivationPath = null,
|
||||||
scanResponse = scanResponse,
|
scanResponse = scanResponse,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.tangem.common.test.domain.walletmanager
|
||||||
|
|
||||||
|
import com.tangem.domain.models.network.TxInfo
|
||||||
|
import com.tangem.domain.walletmanager.model.Address
|
||||||
|
import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount
|
||||||
|
import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction
|
||||||
|
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
class MockUpdateWalletManagerResultFactory {
|
||||||
|
|
||||||
|
fun createUnreachable(): UpdateWalletManagerResult {
|
||||||
|
return UpdateWalletManagerResult.Unreachable(selectedAddress = null, addresses = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createUnreachableWithAddress(): UpdateWalletManagerResult {
|
||||||
|
return UpdateWalletManagerResult.Unreachable(
|
||||||
|
selectedAddress = "0x1",
|
||||||
|
addresses = setOf(Address(value = "0x1", type = Address.Type.Primary)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createNoAccount(): UpdateWalletManagerResult {
|
||||||
|
return UpdateWalletManagerResult.NoAccount(
|
||||||
|
selectedAddress = "0x1",
|
||||||
|
addresses = setOf(Address(value = "0x1", type = Address.Type.Primary)),
|
||||||
|
amountToCreateAccount = BigDecimal.ONE,
|
||||||
|
errorMessage = "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createVerified(): UpdateWalletManagerResult {
|
||||||
|
return UpdateWalletManagerResult.Verified(
|
||||||
|
selectedAddress = "0x1",
|
||||||
|
addresses = setOf(Address(value = "0x1", type = Address.Type.Primary)),
|
||||||
|
currenciesAmounts = setOf(
|
||||||
|
CryptoCurrencyAmount.Coin(value = BigDecimal.ONE),
|
||||||
|
),
|
||||||
|
currentTransactions = setOf(
|
||||||
|
CryptoCurrencyTransaction.Coin(txInfo),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
|
||||||
|
val txInfo = TxInfo(
|
||||||
|
txHash = "erroribus",
|
||||||
|
timestampInMillis = 2771,
|
||||||
|
isOutgoing = false,
|
||||||
|
destinationType = TxInfo.DestinationType.Single(
|
||||||
|
addressType = TxInfo.AddressType.User(address = "0x1"),
|
||||||
|
),
|
||||||
|
sourceType = TxInfo.SourceType.Single(address = "0x2"),
|
||||||
|
interactionAddressType = null,
|
||||||
|
status = TxInfo.TransactionStatus.Confirmed,
|
||||||
|
type = TxInfo.TransactionType.Transfer,
|
||||||
|
amount = BigDecimal.ONE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.tangem.common.test.utils
|
||||||
|
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource
|
||||||
|
|
||||||
|
/**
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@MethodSource("provideTestModels")
|
||||||
|
annotation class ProvideTestModels
|
||||||
|
|
@ -4,8 +4,8 @@ import com.tangem.common.ui.bottomsheet.receive.AddressModel
|
||||||
import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig
|
import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig
|
||||||
import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels
|
import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels
|
||||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||||
import com.tangem.domain.tokens.model.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.tokens.model.NetworkAddress
|
import com.tangem.domain.models.network.NetworkAddress
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.combinedReference
|
import com.tangem.core.ui.extensions.combinedReference
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.tokens.model.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.tokens.model.NetworkAddress
|
import com.tangem.domain.models.network.NetworkAddress
|
||||||
|
|
||||||
private const val DSC_ADDRESS_NAME = "DSC"
|
private const val DSC_ADDRESS_NAME = "DSC"
|
||||||
private const val DEL_ADDRESS_NAME = "Main"
|
private const val DEL_ADDRESS_NAME = "Main"
|
||||||
|
|
@ -59,7 +59,7 @@ private fun Set<NetworkAddress.Address>.mapToAddressModels(name: TextReference,
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Network.getAddressDisplayName(addressType: NetworkAddress.Address.Type): TextReference {
|
private fun Network.getAddressDisplayName(addressType: NetworkAddress.Address.Type): TextReference {
|
||||||
return when (id.value) {
|
return when (rawId) {
|
||||||
"decimal", "decimal/test" -> {
|
"decimal", "decimal/test" -> {
|
||||||
when (addressType) {
|
when (addressType) {
|
||||||
NetworkAddress.Address.Type.Primary -> stringReference(value = DEL_ADDRESS_NAME)
|
NetworkAddress.Address.Type.Primary -> stringReference(value = DEL_ADDRESS_NAME)
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.domain.tokens.model.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.tokens.model.NetworkAddress
|
import com.tangem.domain.models.network.NetworkAddress
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
|
@ -41,9 +41,11 @@ data class TokenReceiveBottomSheetConfig(
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed class Asset {
|
sealed class Asset {
|
||||||
abstract val displaySymbol: TextReference
|
abstract val displaySymbol: TextReference
|
||||||
|
|
||||||
data class Currency(val name: String, val symbol: String) : Asset() {
|
data class Currency(val name: String, val symbol: String) : Asset() {
|
||||||
override val displaySymbol = stringReference(symbol)
|
override val displaySymbol = stringReference(symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
data object NFT : Asset() {
|
data object NFT : Asset() {
|
||||||
override val displaySymbol = resourceReference(R.string.common_nft)
|
override val displaySymbol = resourceReference(R.string.common_nft)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,13 @@ import com.tangem.blockchain.common.BlockchainSdkError
|
||||||
import com.tangem.common.ui.R
|
import com.tangem.common.ui.R
|
||||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||||
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
||||||
import com.tangem.common.ui.notifications.NotificationUM.Warning
|
|
||||||
import com.tangem.core.ui.extensions.networkIconResId
|
import com.tangem.core.ui.extensions.networkIconResId
|
||||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
import com.tangem.core.ui.format.bigdecimal.uncapped
|
import com.tangem.core.ui.format.bigdecimal.uncapped
|
||||||
import com.tangem.core.ui.utils.parseBigDecimal
|
import com.tangem.core.ui.utils.parseBigDecimal
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||||
|
|
@ -460,7 +459,7 @@ object NotificationsFactory {
|
||||||
onCloseClick: (Class<out NotificationUM>) -> Unit,
|
onCloseClick: (Class<out NotificationUM>) -> Unit,
|
||||||
) {
|
) {
|
||||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||||
val isTezos = isTezos(cryptoCurrencyStatus.currency.network.id.value)
|
val isTezos = isTezos(cryptoCurrencyStatus.currency.network.rawId)
|
||||||
val threshold = getTezosThreshold()
|
val threshold = getTezosThreshold()
|
||||||
val isTotalBalance = enteredAmountValue >= balance && balance > threshold
|
val isTotalBalance = enteredAmountValue >= balance && balance > threshold
|
||||||
if (!ignoreAmountReduce && isTotalBalance && isTezos) {
|
if (!ignoreAmountReduce && isTotalBalance && isTezos) {
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ class TokenItemStateConverter(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CryptoCurrencyStatus.getStakedBalance() = (value.yieldBalance as? YieldBalance.Data)
|
private fun CryptoCurrencyStatus.getStakedBalance() = (value.yieldBalance as? YieldBalance.Data)
|
||||||
?.getTotalWithRewardsStakingBalance(blockchainId = currency.network.id.value).orZero()
|
?.getTotalWithRewardsStakingBalance(blockchainId = currency.network.rawId).orZero()
|
||||||
|
|
||||||
private fun createTitleState(currencyStatus: CryptoCurrencyStatus): TokenItemState.TitleState {
|
private fun createTitleState(currencyStatus: CryptoCurrencyStatus): TokenItemState.TitleState {
|
||||||
return when (val value = currencyStatus.value) {
|
return when (val value = currencyStatus.value) {
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,7 @@ dependencies {
|
||||||
|
|
||||||
/** Core shouldn't depend on core, but in case with utils and logging its necessary */
|
/** Core shouldn't depend on core, but in case with utils and logging its necessary */
|
||||||
implementation(projects.core.utils)
|
implementation(projects.core.utils)
|
||||||
|
|
||||||
|
/** For calculating user id hash */
|
||||||
|
implementation(tangemDeps.card.core)
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.tangem.core.analytics
|
package com.tangem.core.analytics
|
||||||
|
|
||||||
|
import com.tangem.common.extensions.calculateSha256
|
||||||
|
import com.tangem.common.extensions.toHexString
|
||||||
import com.tangem.core.analytics.api.*
|
import com.tangem.core.analytics.api.*
|
||||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||||
|
|
@ -19,7 +21,8 @@ interface GlobalAnalyticsEventHandler :
|
||||||
AnalyticsFilterHolder,
|
AnalyticsFilterHolder,
|
||||||
ParamsInterceptorHolder,
|
ParamsInterceptorHolder,
|
||||||
AnalyticsErrorHandler,
|
AnalyticsErrorHandler,
|
||||||
AnalyticsExceptionHandler
|
AnalyticsExceptionHandler,
|
||||||
|
AnalyticsUserIdHandler
|
||||||
|
|
||||||
object Analytics : GlobalAnalyticsEventHandler {
|
object Analytics : GlobalAnalyticsEventHandler {
|
||||||
|
|
||||||
|
|
@ -57,6 +60,26 @@ object Analytics : GlobalAnalyticsEventHandler {
|
||||||
return paramsInterceptors.remove(interceptorId)
|
return paramsInterceptors.remove(interceptorId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setUserId(userId: String) {
|
||||||
|
analyticsScope.launch {
|
||||||
|
val userIdHash = userId.calculateSha256().toHexString()
|
||||||
|
|
||||||
|
analyticsMutex.withLock {
|
||||||
|
analyticsHandlers.filterIsInstance<AnalyticsUserIdHandler>()
|
||||||
|
.forEach { handler -> handler.setUserId(userIdHash) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearUserId() {
|
||||||
|
analyticsScope.launch {
|
||||||
|
analyticsMutex.withLock {
|
||||||
|
analyticsHandlers.filterIsInstance<AnalyticsUserIdHandler>()
|
||||||
|
.forEach { handler -> handler.clearUserId() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun send(event: AnalyticsEvent) {
|
override fun send(event: AnalyticsEvent) {
|
||||||
analyticsScope.launch {
|
analyticsScope.launch {
|
||||||
event.params = applyParamsInterceptors(event)
|
event.params = applyParamsInterceptors(event)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ interface AnalyticsExceptionHandler {
|
||||||
fun sendException(event: ExceptionAnalyticsEvent)
|
fun sendException(event: ExceptionAnalyticsEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AnalyticsUserIdHandler {
|
||||||
|
fun setUserId(userId: String)
|
||||||
|
fun clearUserId()
|
||||||
|
}
|
||||||
|
|
||||||
interface AnalyticsHandler : AnalyticsEventHandler {
|
interface AnalyticsHandler : AnalyticsEventHandler {
|
||||||
|
|
||||||
fun id(): String
|
fun id(): String
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.core.analytics.api
|
||||||
|
|
||||||
|
interface UserIdHolder {
|
||||||
|
|
||||||
|
fun setUserId(userId: String)
|
||||||
|
|
||||||
|
fun clearUserId()
|
||||||
|
}
|
||||||
|
|
@ -51,10 +51,6 @@
|
||||||
"name": "WALLET_CONNECT_REDESIGN_ENABLED",
|
"name": "WALLET_CONNECT_REDESIGN_ENABLED",
|
||||||
"version": "undefined"
|
"version": "undefined"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "NETWORKS_LOADING_REFACTORING_ENABLED",
|
|
||||||
"version": "5.23.0"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "QUOTES_LOADING_REFACTORING_ENABLED",
|
"name": "QUOTES_LOADING_REFACTORING_ENABLED",
|
||||||
"version": "5.24.0"
|
"version": "5.24.0"
|
||||||
|
|
@ -62,5 +58,13 @@
|
||||||
{
|
{
|
||||||
"name": "STAKING_LOADING_REFACTORING_ENABLED",
|
"name": "STAKING_LOADING_REFACTORING_ENABLED",
|
||||||
"version": "5.25.0"
|
"version": "5.25.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DEEPLINK_NAVIGATION_ENABLED",
|
||||||
|
"version": "5.25.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PUSH_NOTIFICATIONS_ENABLED",
|
||||||
|
"version": "undefined"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ dependencies {
|
||||||
/** Project */
|
/** Project */
|
||||||
implementation(projects.core.analytics)
|
implementation(projects.core.analytics)
|
||||||
implementation(projects.core.utils)
|
implementation(projects.core.utils)
|
||||||
|
implementation(projects.core.res)
|
||||||
implementation(projects.libs.auth)
|
implementation(projects.libs.auth)
|
||||||
implementation(projects.domain.appTheme.models)
|
implementation(projects.domain.appTheme.models)
|
||||||
implementation(projects.domain.core)
|
implementation(projects.domain.core)
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,9 @@ interface AuthProvider {
|
||||||
fun getCardPublicKey(): String
|
fun getCardPublicKey(): String
|
||||||
|
|
||||||
fun getCardId(): String
|
fun getCardId(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns map where keys(cardId) associated with cardPublicKey
|
||||||
|
*/
|
||||||
|
fun getCardsPublicKeys(): Map<String, String>
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.datasource.api.common.config.managers
|
||||||
|
|
||||||
import com.tangem.datasource.api.common.config.ApiConfig
|
import com.tangem.datasource.api.common.config.ApiConfig
|
||||||
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Api configs manager
|
* Api configs manager
|
||||||
|
|
@ -10,8 +11,11 @@ import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
||||||
*/
|
*/
|
||||||
interface ApiConfigsManager {
|
interface ApiConfigsManager {
|
||||||
|
|
||||||
|
/** Flag that determines whether the manager is initialized */
|
||||||
|
val isInitialized: StateFlow<Boolean>
|
||||||
|
|
||||||
/** Initialize resources */
|
/** Initialize resources */
|
||||||
fun initialize() {}
|
fun initialize()
|
||||||
|
|
||||||
/** Get environment config by [id] */
|
/** Get environment config by [id] */
|
||||||
fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig
|
fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,13 @@ internal class DevApiConfigsManager(
|
||||||
|
|
||||||
private val _apiConfigs = MutableStateFlow(value = apiConfigs.associateWith { it.defaultEnvironment })
|
private val _apiConfigs = MutableStateFlow(value = apiConfigs.associateWith { it.defaultEnvironment })
|
||||||
|
|
||||||
|
override val isInitialized: StateFlow<Boolean> get() = _isInitialized.asStateFlow()
|
||||||
|
|
||||||
|
private val _isInitialized = MutableStateFlow(value = false)
|
||||||
|
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
|
_isInitialized.value = false
|
||||||
|
|
||||||
// We can't use appPreferencesStore.getObjectMap as base flow,
|
// We can't use appPreferencesStore.getObjectMap as base flow,
|
||||||
// because we should keep possibility to work with configs synchronous.
|
// because we should keep possibility to work with configs synchronous.
|
||||||
// See [getBaseUrl]
|
// See [getBaseUrl]
|
||||||
|
|
@ -42,8 +48,12 @@ internal class DevApiConfigsManager(
|
||||||
savedEnvironments[config.id.name] ?: currentEnvironment
|
savedEnvironments[config.id.name] ?: currentEnvironment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_isInitialized.value) {
|
||||||
|
_isInitialized.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.launchIn(CoroutineScope(SupervisorJob() + dispatchers.main))
|
.launchIn(CoroutineScope(SupervisorJob() + dispatchers.default))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig {
|
override fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package com.tangem.datasource.api.common.config.managers
|
||||||
import com.tangem.datasource.api.common.config.ApiConfig
|
import com.tangem.datasource.api.common.config.ApiConfig
|
||||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||||
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of [ApiConfigsManager] in PROD environment
|
* Implementation of [ApiConfigsManager] in PROD environment
|
||||||
|
|
@ -13,6 +15,10 @@ internal class ProdApiConfigsManager(
|
||||||
private val apiConfigs: ApiConfigs,
|
private val apiConfigs: ApiConfigs,
|
||||||
) : ApiConfigsManager {
|
) : ApiConfigsManager {
|
||||||
|
|
||||||
|
override val isInitialized: StateFlow<Boolean> = MutableStateFlow(value = true)
|
||||||
|
|
||||||
|
override fun initialize() = Unit
|
||||||
|
|
||||||
override fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig {
|
override fun getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig {
|
||||||
val config = apiConfigs.firstOrNull { it.id == id }
|
val config = apiConfigs.firstOrNull { it.id == id }
|
||||||
?: error("Api config with id [$id] not found. Check that ApiConfig with id [$id] was provided into DI")
|
?: error("Api config with id [$id] not found. Check that ApiConfig with id [$id] was provided into DI")
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ interface TangemTechApi {
|
||||||
suspend fun updatePushTokenForApplicationId(
|
suspend fun updatePushTokenForApplicationId(
|
||||||
@Path("application_id") applicationId: String,
|
@Path("application_id") applicationId: String,
|
||||||
@Body body: NotificationApplicationCreateBody,
|
@Body body: NotificationApplicationCreateBody,
|
||||||
): ApiResponse<String>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
@PATCH("user-wallets/wallets/{wallet_id}/notify")
|
@PATCH("user-wallets/wallets/{wallet_id}/notify")
|
||||||
suspend fun setNotificationsEnabled(@Path("wallet_id") walletId: String, @Body body: WalletBody): ApiResponse<Unit>
|
suspend fun setNotificationsEnabled(@Path("wallet_id") walletId: String, @Body body: WalletBody): ApiResponse<Unit>
|
||||||
|
|
@ -172,6 +172,9 @@ interface TangemTechApi {
|
||||||
|
|
||||||
@GET("user-wallets/wallets/{wallet_id}")
|
@GET("user-wallets/wallets/{wallet_id}")
|
||||||
suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse<WalletResponse>
|
suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse<WalletResponse>
|
||||||
|
|
||||||
|
@GET("user-wallets/wallets/by-app/{app_id}")
|
||||||
|
suspend fun getWallets(@Path("app_id") appId: String): ApiResponse<List<WalletResponse>>
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class CardInfoBody(
|
||||||
|
@Json(name = "card_id") val cardId: String,
|
||||||
|
@Json(name = "card_public_key") val cardPublicKey: String,
|
||||||
|
)
|
||||||
|
|
@ -9,6 +9,7 @@ data class UserTokensResponse(
|
||||||
@Json(name = "version") val version: Int = 0,
|
@Json(name = "version") val version: Int = 0,
|
||||||
@Json(name = "group") val group: GroupType,
|
@Json(name = "group") val group: GroupType,
|
||||||
@Json(name = "sort") val sort: SortType,
|
@Json(name = "sort") val sort: SortType,
|
||||||
|
@Json(name = "notifyStatus") val notifyStatus: Boolean? = null,
|
||||||
@Json(name = "tokens") val tokens: List<Token> = emptyList(),
|
@Json(name = "tokens") val tokens: List<Token> = emptyList(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
@ -21,6 +22,7 @@ data class UserTokensResponse(
|
||||||
@Json(name = "symbol") val symbol: String,
|
@Json(name = "symbol") val symbol: String,
|
||||||
@Json(name = "decimals") val decimals: Int,
|
@Json(name = "decimals") val decimals: Int,
|
||||||
@Json(name = "contractAddress") val contractAddress: String?,
|
@Json(name = "contractAddress") val contractAddress: String?,
|
||||||
|
@Json(name = "addresses") val addresses: List<String>? = null,
|
||||||
) {
|
) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
val otherToken = other as? Token ?: return false
|
val otherToken = other as? Token ?: return false
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,6 @@ import com.squareup.moshi.JsonClass
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class WalletIdBody(
|
data class WalletIdBody(
|
||||||
@Json(name = "id") val walletId: String,
|
@Json(name = "id") val walletId: String,
|
||||||
|
@Json(name = "name") val name: String,
|
||||||
|
@Json(name = "cards") val cards: List<CardInfoBody>,
|
||||||
)
|
)
|
||||||
|
|
@ -25,16 +25,18 @@ interface TangemVisaApi {
|
||||||
// region: auth
|
// region: auth
|
||||||
|
|
||||||
@POST("v1/auth/challenge")
|
@POST("v1/auth/challenge")
|
||||||
suspend fun generateNonceByCardId(@Body request: GenerateNoneByCardIdRequest): GenerateNonceResponse
|
suspend fun generateNonceByCardId(@Body request: GenerateNoneByCardIdRequest): ApiResponse<GenerateNonceResponse>
|
||||||
|
|
||||||
@POST("v1/auth/challenge")
|
@POST("v1/auth/challenge")
|
||||||
suspend fun generateNonceByCardWallet(@Body request: GenerateNoneByCardWalletRequest): GenerateNonceResponse
|
suspend fun generateNonceByCardWallet(
|
||||||
|
@Body request: GenerateNoneByCardWalletRequest,
|
||||||
|
): ApiResponse<GenerateNonceResponse>
|
||||||
|
|
||||||
@POST("v1/auth/token")
|
@POST("v1/auth/token")
|
||||||
suspend fun getAccessTokenByCardId(@Body request: GetAccessTokenByCardIdRequest): JWTResponse
|
suspend fun getAccessTokenByCardId(@Body request: GetAccessTokenByCardIdRequest): ApiResponse<JWTResponse>
|
||||||
|
|
||||||
@POST("v1/auth/token")
|
@POST("v1/auth/token")
|
||||||
suspend fun getAccessTokenByCardWallet(@Body request: GetAccessTokenByCardWalletRequest): JWTResponse
|
suspend fun getAccessTokenByCardWallet(@Body request: GetAccessTokenByCardWalletRequest): ApiResponse<JWTResponse>
|
||||||
|
|
||||||
@POST("v1/auth/token/refresh")
|
@POST("v1/auth/token/refresh")
|
||||||
suspend fun refreshCardIdAccessToken(@Body request: RefreshTokenByCardIdRequest): ApiResponse<JWTResponse>
|
suspend fun refreshCardIdAccessToken(@Body request: RefreshTokenByCardIdRequest): ApiResponse<JWTResponse>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.tangem.datasource.api.visa.models.response
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class VisaErrorResponse(
|
||||||
|
@Json(name = "error") val error: Error,
|
||||||
|
) {
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Error(
|
||||||
|
@Json(name = "code") val code: Int,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -43,6 +43,10 @@ internal object NetworkModule {
|
||||||
private const val TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS = 60L
|
private const val TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS = 60L
|
||||||
private const val STAKE_KIT_API_TIMEOUT_SECONDS = 60L
|
private const val STAKE_KIT_API_TIMEOUT_SECONDS = 60L
|
||||||
|
|
||||||
|
private val excludedApiForLogging: Set<ApiConfig.ID> = setOf(
|
||||||
|
// ApiConfig.ID.StakeKit,
|
||||||
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideApiConfigManager(
|
fun provideApiConfigManager(
|
||||||
|
|
@ -317,7 +321,7 @@ internal object NetworkModule {
|
||||||
}
|
}
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
.addLoggers(context)
|
.addLoggers(context = context, id = id)
|
||||||
.clientBuilder()
|
.clientBuilder()
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
|
@ -325,6 +329,12 @@ internal object NetworkModule {
|
||||||
.create(T::class.java)
|
.create(T::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun OkHttpClient.Builder.addLoggers(context: Context, id: ApiConfig.ID): OkHttpClient.Builder {
|
||||||
|
if (id in excludedApiForLogging) return this
|
||||||
|
|
||||||
|
return addLoggers(context)
|
||||||
|
}
|
||||||
|
|
||||||
private data class Timeouts(
|
private data class Timeouts(
|
||||||
val callTimeoutSeconds: Long? = null,
|
val callTimeoutSeconds: Long? = null,
|
||||||
val connectTimeoutSeconds: Long? = null,
|
val connectTimeoutSeconds: Long? = null,
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
package com.tangem.datasource.di
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.datastore.core.DataStore
|
|
||||||
import androidx.datastore.core.DataStoreFactory
|
|
||||||
import androidx.datastore.dataStoreFile
|
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
|
||||||
import com.tangem.datasource.local.network.DefaultNetworksStatusesStore
|
|
||||||
import com.tangem.datasource.local.network.NetworksStatusesStore
|
|
||||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
|
||||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
|
||||||
import com.tangem.datasource.utils.mapWithStringKeyTypes
|
|
||||||
import com.tangem.datasource.utils.setTypes
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
internal object NetworksStatusesStoreModule {
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun providePersistenceNetworksStatusesStore(
|
|
||||||
@NetworkMoshi moshi: Moshi,
|
|
||||||
@ApplicationContext context: Context,
|
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
|
||||||
): DataStore<Map<String, Set<NetworkStatusDM>>> {
|
|
||||||
return DataStoreFactory.create(
|
|
||||||
serializer = MoshiDataStoreSerializer(
|
|
||||||
moshi = moshi,
|
|
||||||
types = mapWithStringKeyTypes(valueTypes = setTypes<NetworkStatusDM>()),
|
|
||||||
defaultValue = emptyMap(),
|
|
||||||
),
|
|
||||||
produceFile = { context.dataStoreFile(fileName = "networks_statuses") },
|
|
||||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideNetworksStatusesStore(
|
|
||||||
persistenceNetworksStatusesStore: DataStore<Map<String, Set<NetworkStatusDM>>>,
|
|
||||||
): NetworksStatusesStore {
|
|
||||||
return DefaultNetworksStatusesStore(
|
|
||||||
runtimeDataStore = RuntimeDataStore(),
|
|
||||||
persistenceDataStore = persistenceNetworksStatusesStore,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.datasource.di.local
|
||||||
|
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.datasource.local.token.DefaultUserTokensResponseStore
|
||||||
|
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object LocalTokenModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideUserTokensResponseStore(appPreferencesStore: AppPreferencesStore): UserTokensResponseStore {
|
||||||
|
return DefaultUserTokensResponseStore(appPreferencesStore = appPreferencesStore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,6 +43,7 @@ internal object BlockchainSDKConfigConverter : Converter<EnvironmentConfigModel,
|
||||||
bittensorOnfinalityApiKey = value.bittensorOnfinalityKey,
|
bittensorOnfinalityApiKey = value.bittensorOnfinalityKey,
|
||||||
koinosProApiKey = value.koinosProApiKey,
|
koinosProApiKey = value.koinosProApiKey,
|
||||||
alephiumApiKey = value.alephiumTangemApiKey,
|
alephiumApiKey = value.alephiumTangemApiKey,
|
||||||
|
moralisApiKey = value.moralisApiKey,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
package com.tangem.datasource.local.network
|
|
||||||
|
|
||||||
import androidx.datastore.core.DataStore
|
|
||||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
|
||||||
import com.tangem.datasource.local.network.converter.NetworkDerivationPathConverter
|
|
||||||
import com.tangem.datasource.local.network.converter.NetworkStatusConverter
|
|
||||||
import com.tangem.datasource.local.network.converter.NetworkStatusDataModelConverter
|
|
||||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
|
||||||
import com.tangem.domain.models.StatusSource
|
|
||||||
import com.tangem.domain.tokens.model.Network
|
|
||||||
import com.tangem.domain.tokens.model.NetworkStatus
|
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
|
||||||
import com.tangem.utils.extensions.addOrReplace
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
|
|
||||||
private typealias NetworkStatusesByWalletId = Map<String, Set<NetworkStatusDM>>
|
|
||||||
|
|
||||||
internal class DefaultNetworksStatusesStore(
|
|
||||||
private val runtimeDataStore: RuntimeDataStore<Set<NetworkStatus>>,
|
|
||||||
private val persistenceDataStore: DataStore<NetworkStatusesByWalletId>,
|
|
||||||
) : NetworksStatusesStore {
|
|
||||||
|
|
||||||
private val mutex = Mutex()
|
|
||||||
|
|
||||||
override fun get(key: UserWalletId): Flow<Set<NetworkStatus>> {
|
|
||||||
return runtimeDataStore.get(provideStringKey(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun get(key: UserWalletId, networks: Set<Network>): Flow<Set<NetworkStatus>> = channelFlow {
|
|
||||||
val cachedStatuses = persistenceDataStore.data.firstOrNull()
|
|
||||||
?.get(key.stringValue)
|
|
||||||
?.mapNotNullTo(mutableSetOf()) { cached ->
|
|
||||||
val network = networks.firstOrNull {
|
|
||||||
it.id == cached.networkId &&
|
|
||||||
it.derivationPath == NetworkDerivationPathConverter.convert(cached.derivationPath)
|
|
||||||
}
|
|
||||||
?: return@mapNotNullTo null
|
|
||||||
|
|
||||||
NetworkStatusConverter(network = network, isCached = true).convert(value = cached)
|
|
||||||
}
|
|
||||||
.orEmpty()
|
|
||||||
|
|
||||||
if (cachedStatuses.isNotEmpty()) {
|
|
||||||
send(cachedStatuses)
|
|
||||||
}
|
|
||||||
|
|
||||||
runtimeDataStore.get(provideStringKey(key))
|
|
||||||
.onEach { runtimeStatuses ->
|
|
||||||
val mergedStatuses = mergeStatuses(
|
|
||||||
networks = networks,
|
|
||||||
cachedStatuses = cachedStatuses,
|
|
||||||
runtimeStatuses = runtimeStatuses,
|
|
||||||
)
|
|
||||||
|
|
||||||
send(mergedStatuses)
|
|
||||||
}
|
|
||||||
.launchIn(scope = this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getSyncOrNull(key: UserWalletId): Set<NetworkStatus>? {
|
|
||||||
val runtimeStatuses = runtimeDataStore.getSyncOrNull(key = provideStringKey(key)) ?: return null
|
|
||||||
|
|
||||||
val networks = runtimeStatuses.map(NetworkStatus::network).toSet()
|
|
||||||
|
|
||||||
val cachedStatuses = persistenceDataStore.data.firstOrNull()
|
|
||||||
?.get(key.stringValue)
|
|
||||||
?.mapNotNullTo(mutableSetOf()) { cached ->
|
|
||||||
val network = networks.firstOrNull {
|
|
||||||
it.id == cached.networkId &&
|
|
||||||
it.derivationPath == NetworkDerivationPathConverter.convert(cached.derivationPath)
|
|
||||||
}
|
|
||||||
?: return@mapNotNullTo null
|
|
||||||
|
|
||||||
NetworkStatusConverter(network = network, isCached = true).convert(value = cached)
|
|
||||||
}
|
|
||||||
.orEmpty()
|
|
||||||
|
|
||||||
return mergeStatuses(
|
|
||||||
networks = networks,
|
|
||||||
cachedStatuses = cachedStatuses,
|
|
||||||
runtimeStatuses = runtimeStatuses,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun store(key: UserWalletId, value: NetworkStatus) {
|
|
||||||
storeAll(key = key, values = setOf(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun storeAll(key: UserWalletId, values: Set<NetworkStatus>) {
|
|
||||||
mutex.withLock {
|
|
||||||
coroutineScope {
|
|
||||||
launch { storeInRuntimeStore(key = key, statuses = values) }
|
|
||||||
launch { storeInPersistenceStore(userWalletId = key, statuses = values) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun refresh(key: UserWalletId, networks: Set<Network>) {
|
|
||||||
mutex.withLock {
|
|
||||||
val currentStatuses = getSyncOrNull(key).orEmpty()
|
|
||||||
|
|
||||||
storeInRuntimeStore(
|
|
||||||
key = key,
|
|
||||||
statuses = networks.mapNotNullTo(hashSetOf()) { network ->
|
|
||||||
val status = currentStatuses.firstOrNull {
|
|
||||||
it.network.id == network.id && it.network.derivationPath == network.derivationPath
|
|
||||||
} ?: return@mapNotNullTo null
|
|
||||||
|
|
||||||
status.copy(
|
|
||||||
value = status.value.copySealed(source = StatusSource.CACHE),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge [cachedStatuses] with [runtimeStatuses]
|
|
||||||
* The resulting set contains statuses from both sets.
|
|
||||||
* If a status with the same network is in both sets, the status from [runtimeStatuses] is used.
|
|
||||||
*/
|
|
||||||
private fun mergeStatuses(
|
|
||||||
networks: Set<Network>,
|
|
||||||
cachedStatuses: Set<NetworkStatus>,
|
|
||||||
runtimeStatuses: Set<NetworkStatus>,
|
|
||||||
): Set<NetworkStatus> {
|
|
||||||
return networks.mapNotNullTo(hashSetOf()) { network ->
|
|
||||||
val runtimeStatus = runtimeStatuses.firstOrNull { it.network == network }
|
|
||||||
|
|
||||||
if (runtimeStatus == null) {
|
|
||||||
getCachedStatusIfPossible(
|
|
||||||
cachedStatuses = cachedStatuses,
|
|
||||||
network = network,
|
|
||||||
source = StatusSource.CACHE,
|
|
||||||
)
|
|
||||||
} else if (runtimeStatus.value is NetworkStatus.Unreachable) {
|
|
||||||
getCachedStatusIfPossible(
|
|
||||||
cachedStatuses = cachedStatuses,
|
|
||||||
network = network,
|
|
||||||
source = StatusSource.ONLY_CACHE,
|
|
||||||
)
|
|
||||||
?: runtimeStatus
|
|
||||||
} else {
|
|
||||||
runtimeStatus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCachedStatusIfPossible(
|
|
||||||
cachedStatuses: Set<NetworkStatus>,
|
|
||||||
network: Network,
|
|
||||||
source: StatusSource,
|
|
||||||
): NetworkStatus? {
|
|
||||||
val cached = cachedStatuses.firstOrNull { it.network == network } ?: return null
|
|
||||||
|
|
||||||
val updatedCachedStatus = when (val status = cached.value) {
|
|
||||||
is NetworkStatus.NoAccount -> status.copy(source = source)
|
|
||||||
is NetworkStatus.Verified -> status.copy(source = source)
|
|
||||||
is NetworkStatus.Unreachable,
|
|
||||||
is NetworkStatus.MissedDerivation,
|
|
||||||
-> null
|
|
||||||
}
|
|
||||||
|
|
||||||
return if (updatedCachedStatus != null) {
|
|
||||||
cached.copy(value = updatedCachedStatus)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun storeInRuntimeStore(key: UserWalletId, statuses: Set<NetworkStatus>) {
|
|
||||||
val updatedValues = getSyncOrNull(key).orEmpty()
|
|
||||||
.addOrReplace(items = statuses) { prev, new -> prev.network == new.network }
|
|
||||||
|
|
||||||
runtimeDataStore.store(key = provideStringKey(key), value = updatedValues)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun storeInPersistenceStore(userWalletId: UserWalletId, statuses: Set<NetworkStatus>) {
|
|
||||||
// Converter will return null if the network status is not supported
|
|
||||||
val newStatuses = NetworkStatusDataModelConverter.convertSet(input = statuses).filterNotNull().toSet()
|
|
||||||
|
|
||||||
persistenceDataStore.updateData { storedStatuses ->
|
|
||||||
storedStatuses.toMutableMap().apply {
|
|
||||||
val updatedValues = this[userWalletId.stringValue].orEmpty()
|
|
||||||
.addOrReplace(newStatuses) { prev, new ->
|
|
||||||
prev.networkId == new.networkId && prev.derivationPath == new.derivationPath
|
|
||||||
}
|
|
||||||
|
|
||||||
this[userWalletId.stringValue] = updatedValues
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun provideStringKey(key: UserWalletId): String {
|
|
||||||
return "network_statuses_${key.stringValue}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package com.tangem.datasource.local.network
|
|
||||||
|
|
||||||
import com.tangem.domain.tokens.model.Network
|
|
||||||
import com.tangem.domain.tokens.model.NetworkStatus
|
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
interface NetworksStatusesStore {
|
|
||||||
|
|
||||||
fun get(key: UserWalletId): Flow<Set<NetworkStatus>>
|
|
||||||
|
|
||||||
fun get(key: UserWalletId, networks: Set<Network>): Flow<Set<NetworkStatus>>
|
|
||||||
|
|
||||||
suspend fun getSyncOrNull(key: UserWalletId): Set<NetworkStatus>?
|
|
||||||
|
|
||||||
suspend fun store(key: UserWalletId, value: NetworkStatus)
|
|
||||||
|
|
||||||
suspend fun storeAll(key: UserWalletId, values: Set<NetworkStatus>)
|
|
||||||
|
|
||||||
suspend fun refresh(key: UserWalletId, networks: Set<Network>)
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
package com.tangem.datasource.local.network.converter
|
|
||||||
|
|
||||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
|
||||||
import com.tangem.domain.tokens.model.NetworkAddress
|
|
||||||
import com.tangem.utils.converter.TwoWayConverter
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converter from [Set<NetworkStatusDM.Address>] to [NetworkAddress] and vice versa
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
class NetworkAddressConverter(
|
|
||||||
private val selectedAddress: String,
|
|
||||||
) : TwoWayConverter<Set<NetworkStatusDM.Address>, NetworkAddress> {
|
|
||||||
|
|
||||||
override fun convert(value: Set<NetworkStatusDM.Address>): NetworkAddress {
|
|
||||||
val defaultAddress = value
|
|
||||||
.firstOrNull { it.value == selectedAddress }
|
|
||||||
?.let(::toNetworkAddress)
|
|
||||||
|
|
||||||
requireNotNull(defaultAddress) { "Selected address must not be null" }
|
|
||||||
|
|
||||||
return if (value.size != 1) {
|
|
||||||
NetworkAddress.Selectable(
|
|
||||||
defaultAddress = defaultAddress,
|
|
||||||
availableAddresses = value.mapTo(destination = hashSetOf(), transform = ::toNetworkAddress),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
NetworkAddress.Single(defaultAddress = defaultAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun convertBack(value: NetworkAddress): Set<NetworkStatusDM.Address> {
|
|
||||||
return value.availableAddresses
|
|
||||||
.map { address ->
|
|
||||||
NetworkStatusDM.Address(
|
|
||||||
value = address.value,
|
|
||||||
type = when (address.type) {
|
|
||||||
NetworkAddress.Address.Type.Primary -> NetworkStatusDM.Address.Type.Primary
|
|
||||||
NetworkAddress.Address.Type.Secondary -> NetworkStatusDM.Address.Type.Secondary
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toNetworkAddress(address: NetworkStatusDM.Address): NetworkAddress.Address {
|
|
||||||
val type = when (address.type) {
|
|
||||||
NetworkStatusDM.Address.Type.Primary -> NetworkAddress.Address.Type.Primary
|
|
||||||
NetworkStatusDM.Address.Type.Secondary -> NetworkAddress.Address.Type.Secondary
|
|
||||||
}
|
|
||||||
|
|
||||||
if (address.value.isBlank()) {
|
|
||||||
Timber.w("Address value is blank")
|
|
||||||
}
|
|
||||||
|
|
||||||
return NetworkAddress.Address(value = address.value, type = type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
package com.tangem.datasource.local.network.converter
|
|
||||||
|
|
||||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
|
||||||
import com.tangem.domain.models.StatusSource
|
|
||||||
import com.tangem.domain.tokens.model.Network
|
|
||||||
import com.tangem.domain.tokens.model.NetworkStatus
|
|
||||||
import com.tangem.utils.converter.Converter
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converter from [NetworkStatusDM] to [NetworkStatus]
|
|
||||||
*
|
|
||||||
* @property network network
|
|
||||||
* @property isCached flag that determines whether the status is a cache
|
|
||||||
*
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
internal class NetworkStatusConverter(
|
|
||||||
private val network: Network,
|
|
||||||
private val isCached: Boolean,
|
|
||||||
) : Converter<NetworkStatusDM, NetworkStatus> {
|
|
||||||
|
|
||||||
override fun convert(value: NetworkStatusDM): NetworkStatus {
|
|
||||||
val address = NetworkAddressConverter(selectedAddress = value.selectedAddress)
|
|
||||||
.convert(value = value.availableAddresses)
|
|
||||||
|
|
||||||
val status = when (value) {
|
|
||||||
is NetworkStatusDM.Verified -> {
|
|
||||||
NetworkStatus.Verified(
|
|
||||||
address = address,
|
|
||||||
amounts = NetworkAmountsConverter.convert(value = value.amounts),
|
|
||||||
pendingTransactions = mapOf(),
|
|
||||||
source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is NetworkStatusDM.NoAccount -> {
|
|
||||||
NetworkStatus.NoAccount(
|
|
||||||
address = address,
|
|
||||||
amountToCreateAccount = value.amountToCreateAccount,
|
|
||||||
errorMessage = value.errorMessage,
|
|
||||||
source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NetworkStatus(network = network, value = status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,29 +2,63 @@ package com.tangem.datasource.local.network.entity
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import com.tangem.domain.tokens.model.Network
|
import com.tangem.datasource.local.network.entity.NetworkStatusDM.NoAccount
|
||||||
|
import com.tangem.datasource.local.network.entity.NetworkStatusDM.Verified
|
||||||
|
import dev.onenowy.moshipolymorphicadapter.PolymorphicAdapterType
|
||||||
import dev.onenowy.moshipolymorphicadapter.annotations.NameLabel
|
import dev.onenowy.moshipolymorphicadapter.annotations.NameLabel
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Network status for storage in the local cache. Supports two types - the [Verified] and [NoAccount].
|
||||||
|
*
|
||||||
|
* @see [com.tangem.domain.tokens.model.NetworkStatus]
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true, generator = PolymorphicAdapterType.NAME_POLYMORPHIC_ADAPTER)
|
||||||
sealed interface NetworkStatusDM {
|
sealed interface NetworkStatusDM {
|
||||||
|
|
||||||
val networkId: Network.ID
|
/** Network id */
|
||||||
|
val networkId: ID
|
||||||
|
|
||||||
|
/** Derivation path */
|
||||||
val derivationPath: DerivationPath
|
val derivationPath: DerivationPath
|
||||||
|
|
||||||
|
/** Selected address */
|
||||||
val selectedAddress: String
|
val selectedAddress: String
|
||||||
|
|
||||||
|
/** Available address */
|
||||||
val availableAddresses: Set<Address>
|
val availableAddresses: Set<Address>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verified
|
||||||
|
*
|
||||||
|
* @property networkId network id
|
||||||
|
* @property derivationPath derivation path
|
||||||
|
* @property selectedAddress selected address
|
||||||
|
* @property availableAddresses available addresses
|
||||||
|
* @property amounts amounts
|
||||||
|
*/
|
||||||
@NameLabel("amounts")
|
@NameLabel("amounts")
|
||||||
data class Verified(
|
data class Verified(
|
||||||
@Json(name = "network_id") override val networkId: Network.ID,
|
@Json(name = "network_id") override val networkId: ID,
|
||||||
@Json(name = "derivation_path") override val derivationPath: DerivationPath,
|
@Json(name = "derivation_path") override val derivationPath: DerivationPath,
|
||||||
@Json(name = "selected_address") override val selectedAddress: String,
|
@Json(name = "selected_address") override val selectedAddress: String,
|
||||||
@Json(name = "available_addresses") override val availableAddresses: Set<Address>,
|
@Json(name = "available_addresses") override val availableAddresses: Set<Address>,
|
||||||
@Json(name = "amounts") val amounts: Map<String, BigDecimal>,
|
@Json(name = "amounts") val amounts: Map<String, BigDecimal>,
|
||||||
) : NetworkStatusDM
|
) : NetworkStatusDM
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No account
|
||||||
|
*
|
||||||
|
* @property networkId network id
|
||||||
|
* @property derivationPath derivation path
|
||||||
|
* @property selectedAddress selected address
|
||||||
|
* @property availableAddresses available addresses
|
||||||
|
* @property amountToCreateAccount amount to create account
|
||||||
|
* @property errorMessage error message
|
||||||
|
*/
|
||||||
@NameLabel("amount_to_create_account")
|
@NameLabel("amount_to_create_account")
|
||||||
data class NoAccount(
|
data class NoAccount(
|
||||||
@Json(name = "network_id") override val networkId: Network.ID,
|
@Json(name = "network_id") override val networkId: ID,
|
||||||
@Json(name = "derivation_path") override val derivationPath: DerivationPath,
|
@Json(name = "derivation_path") override val derivationPath: DerivationPath,
|
||||||
@Json(name = "selected_address") override val selectedAddress: String,
|
@Json(name = "selected_address") override val selectedAddress: String,
|
||||||
@Json(name = "available_addresses") override val availableAddresses: Set<Address>,
|
@Json(name = "available_addresses") override val availableAddresses: Set<Address>,
|
||||||
|
|
@ -32,6 +66,11 @@ sealed interface NetworkStatusDM {
|
||||||
@Json(name = "error_message") val errorMessage: String,
|
@Json(name = "error_message") val errorMessage: String,
|
||||||
) : NetworkStatusDM
|
) : NetworkStatusDM
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class ID(
|
||||||
|
@Json(name = "value") val value: String,
|
||||||
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class DerivationPath(
|
data class DerivationPath(
|
||||||
@Json(name = "value") val value: String,
|
@Json(name = "value") val value: String,
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@ package com.tangem.datasource.local.nft
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import com.tangem.blockchain.nft.models.NFTAsset
|
import com.tangem.blockchain.nft.models.NFTAsset
|
||||||
import com.tangem.blockchain.nft.models.NFTCollection
|
import com.tangem.blockchain.nft.models.NFTCollection
|
||||||
|
import com.tangem.datasource.local.nft.custom.NFTPriceId
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
internal class DefaultNFTPersistenceStore(
|
internal class DefaultNFTPersistenceStore(
|
||||||
private val collectionsPersistenceStore: DataStore<List<NFTCollection>>,
|
private val collectionsPersistenceStore: DataStore<List<NFTCollection>>,
|
||||||
private val pricesPersistenceStore: DataStore<Map<NFTAsset.Identifier, NFTAsset.SalePrice>>,
|
private val pricesPersistenceStore: DataStore<List<NFTPriceId>>,
|
||||||
) : NFTPersistenceStore {
|
) : NFTPersistenceStore {
|
||||||
|
|
||||||
override fun getCollections(): Flow<List<NFTCollection>?> = collectionsPersistenceStore.data
|
override fun getCollections(): Flow<List<NFTCollection>?> = collectionsPersistenceStore.data
|
||||||
|
|
@ -27,11 +28,12 @@ internal class DefaultNFTPersistenceStore(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSalePrice(assetId: NFTAsset.Identifier): Flow<NFTAsset.SalePrice?> = pricesPersistenceStore.data
|
override fun getSalePrice(assetId: NFTAsset.Identifier): Flow<NFTAsset.SalePrice?> = pricesPersistenceStore.data
|
||||||
.map { it[assetId] }
|
.map { data -> data.associate { it.assetId to it.price }[assetId] }
|
||||||
|
|
||||||
override suspend fun getSalePricesSync(): Map<NFTAsset.Identifier, NFTAsset.SalePrice>? = pricesPersistenceStore
|
override suspend fun getSalePricesSync(): Map<NFTAsset.Identifier, NFTAsset.SalePrice>? = pricesPersistenceStore
|
||||||
.data
|
.data
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
|
?.associate { it.assetId to it.price }
|
||||||
|
|
||||||
override suspend fun saveCollections(collections: List<NFTCollection>) {
|
override suspend fun saveCollections(collections: List<NFTCollection>) {
|
||||||
collectionsPersistenceStore.updateData {
|
collectionsPersistenceStore.updateData {
|
||||||
|
|
@ -41,9 +43,14 @@ internal class DefaultNFTPersistenceStore(
|
||||||
|
|
||||||
override suspend fun saveSalePrice(assetId: NFTAsset.Identifier, salePrice: NFTAsset.SalePrice) {
|
override suspend fun saveSalePrice(assetId: NFTAsset.Identifier, salePrice: NFTAsset.SalePrice) {
|
||||||
pricesPersistenceStore.updateData {
|
pricesPersistenceStore.updateData {
|
||||||
it.toMutableMap().apply { this[assetId] = salePrice }
|
it.toMutableList() + NFTPriceId(assetId = assetId, price = salePrice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun clear() {
|
||||||
|
collectionsPersistenceStore.updateData { emptyList() }
|
||||||
|
pricesPersistenceStore.updateData { emptyList() }
|
||||||
|
}
|
||||||
|
|
||||||
private fun NFTCollection.getAsset(assetId: NFTAsset.Identifier) = assets.firstOrNull { it.identifier == assetId }
|
private fun NFTCollection.getAsset(assetId: NFTAsset.Identifier) = assets.firstOrNull { it.identifier == assetId }
|
||||||
}
|
}
|
||||||
|
|
@ -2,11 +2,11 @@ package com.tangem.datasource.local.nft
|
||||||
|
|
||||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||||
import com.tangem.domain.models.StatusSource
|
import com.tangem.domain.models.StatusSource
|
||||||
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.nft.models.NFTAsset
|
import com.tangem.domain.nft.models.NFTAsset
|
||||||
import com.tangem.domain.nft.models.NFTCollection
|
import com.tangem.domain.nft.models.NFTCollection
|
||||||
import com.tangem.domain.nft.models.NFTCollections
|
import com.tangem.domain.nft.models.NFTCollections
|
||||||
import com.tangem.domain.nft.models.NFTSalePrice
|
import com.tangem.domain.nft.models.NFTSalePrice
|
||||||
import com.tangem.domain.tokens.model.Network
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
@ -71,6 +71,16 @@ internal class DefaultNFTRuntimeStore(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun clear() {
|
||||||
|
collectionsRuntimeStore.store(
|
||||||
|
NFTCollections(
|
||||||
|
network = network,
|
||||||
|
content = NFTCollections.Content.Collections(null, StatusSource.ONLY_CACHE),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
pricesRuntimeStore.store(emptyMap())
|
||||||
|
}
|
||||||
|
|
||||||
private fun NFTCollections.getCollection(collectionId: NFTCollection.Identifier): NFTCollection? =
|
private fun NFTCollections.getCollection(collectionId: NFTCollection.Identifier): NFTCollection? =
|
||||||
(content as? NFTCollections.Content.Collections)
|
(content as? NFTCollections.Content.Collections)
|
||||||
?.collections
|
?.collections
|
||||||
|
|
@ -105,7 +115,6 @@ internal class DefaultNFTRuntimeStore(
|
||||||
-> assets
|
-> assets
|
||||||
is NFTCollection.Assets.Value -> assets.copy(
|
is NFTCollection.Assets.Value -> assets.copy(
|
||||||
items = assets.items
|
items = assets.items
|
||||||
.filter { !it.name.isNullOrEmpty() }
|
|
||||||
.sortedBy { it.name }
|
.sortedBy { it.name }
|
||||||
.map { asset ->
|
.map { asset ->
|
||||||
asset.mergeWithPrice(prices[asset.id] ?: NFTSalePrice.Empty(asset.id))
|
asset.mergeWithPrice(prices[asset.id] ?: NFTSalePrice.Empty(asset.id))
|
||||||
|
|
@ -122,7 +131,7 @@ internal class DefaultNFTRuntimeStore(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
?.filter { it.count > 0 }
|
?.filter { it.count > 0 }
|
||||||
?.sortedBy { it.name },
|
?.sortedBy { it.name?.lowercase() },
|
||||||
source = this.source,
|
source = this.source,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,6 @@ interface NFTPersistenceStore {
|
||||||
suspend fun saveCollections(collections: List<NFTCollection>)
|
suspend fun saveCollections(collections: List<NFTCollection>)
|
||||||
|
|
||||||
suspend fun saveSalePrice(assetId: NFTAsset.Identifier, salePrice: NFTAsset.SalePrice)
|
suspend fun saveSalePrice(assetId: NFTAsset.Identifier, salePrice: NFTAsset.SalePrice)
|
||||||
|
|
||||||
|
suspend fun clear()
|
||||||
}
|
}
|
||||||
|
|
@ -5,13 +5,12 @@ import androidx.datastore.core.DataStore
|
||||||
import androidx.datastore.core.DataStoreFactory
|
import androidx.datastore.core.DataStoreFactory
|
||||||
import androidx.datastore.dataStoreFile
|
import androidx.datastore.dataStoreFile
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.tangem.blockchain.nft.models.NFTAsset
|
|
||||||
import com.tangem.blockchain.nft.models.NFTCollection
|
import com.tangem.blockchain.nft.models.NFTCollection
|
||||||
import com.tangem.datasource.di.NetworkMoshi
|
import com.tangem.datasource.di.NetworkMoshi
|
||||||
|
import com.tangem.datasource.local.nft.custom.NFTPriceId
|
||||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||||
import com.tangem.datasource.utils.listTypes
|
import com.tangem.datasource.utils.listTypes
|
||||||
import com.tangem.datasource.utils.mapWithCustomKeyTypes
|
import com.tangem.domain.models.network.Network
|
||||||
import com.tangem.domain.tokens.model.Network
|
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
|
@ -48,8 +47,8 @@ class NFTPersistenceStoreFactory @Inject constructor(
|
||||||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_eth_m4460000_prices
|
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_eth_m4460000_prices
|
||||||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_theopennetwork_m446070_prices
|
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_theopennetwork_m446070_prices
|
||||||
fileName = "nft_${userWalletStringId}_${networkStringId}_prices",
|
fileName = "nft_${userWalletStringId}_${networkStringId}_prices",
|
||||||
types = mapWithCustomKeyTypes<NFTAsset.Identifier, NFTAsset.SalePrice>(),
|
types = listTypes<NFTPriceId>(),
|
||||||
defaultValue = emptyMap(),
|
defaultValue = emptyList(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +64,7 @@ class NFTPersistenceStoreFactory @Inject constructor(
|
||||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun Network.ID.formatted(): String = value
|
private fun Network.ID.formatted(): String = rawId.value
|
||||||
.filter(Char::isLetterOrDigit)
|
.filter(Char::isLetterOrDigit)
|
||||||
.lowercase()
|
.lowercase()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,6 @@ interface NFTRuntimeStore {
|
||||||
suspend fun saveCollections(collections: NFTCollections)
|
suspend fun saveCollections(collections: NFTCollections)
|
||||||
|
|
||||||
suspend fun saveSalePrice(salePrice: NFTSalePrice)
|
suspend fun saveSalePrice(salePrice: NFTSalePrice)
|
||||||
|
|
||||||
|
suspend fun clear()
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.tangem.datasource.local.nft
|
package com.tangem.datasource.local.nft
|
||||||
|
|
||||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||||
import com.tangem.domain.tokens.model.Network
|
import com.tangem.domain.models.network.Network
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
|
||||||
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