Updated on 2026-08-14
This commit is contained in:
commit
98dd90235a
280 changed files with 5494 additions and 1083 deletions
|
|
@ -169,6 +169,8 @@ dependencies {
|
|||
implementation(projects.features.manageTokens.impl)
|
||||
implementation(projects.features.send.api)
|
||||
implementation(projects.features.send.impl)
|
||||
implementation(projects.features.sendV2.api)
|
||||
implementation(projects.features.sendV2.impl)
|
||||
implementation(projects.features.qrScanning.api)
|
||||
implementation(projects.features.qrScanning.impl)
|
||||
implementation(projects.features.staking.api)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap
|
|||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import com.tangem.TangemSdkLogger
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.data.card.TransactionSignerFactory
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
||||
|
|
@ -15,6 +14,7 @@ import com.tangem.core.navigation.settings.SettingsManager
|
|||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.data.card.TransactionSignerFactory
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
|
||||
|
|
@ -32,13 +32,13 @@ import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
|||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
|
||||
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
|
|
@ -94,8 +94,6 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getSaveTwinsOnboardingShownUseCase(): SaveTwinsOnboardingShownUseCase
|
||||
|
||||
fun getWalletNameGenerateUseCase(): GenerateWalletNameUseCase
|
||||
|
||||
fun getCardRepository(): CardRepository
|
||||
|
||||
fun getTangemSdkLogger(): TangemSdkLogger
|
||||
|
|
@ -118,8 +116,6 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getTransactionSignerFactory(): TransactionSignerFactory
|
||||
|
||||
fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase
|
||||
|
||||
fun getOnrampFeatureToggles(): OnrampFeatureToggles
|
||||
|
||||
fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles
|
||||
|
|
@ -142,4 +138,8 @@ interface ApplicationEntryPoint {
|
|||
fun getUiMessageSender(): UiMessageSender
|
||||
|
||||
fun getWorkerFactory(): HiltWorkerFactory
|
||||
|
||||
fun getOnlineCardVerifier(): OnlineCardVerifier
|
||||
|
||||
fun getUserWalletBuilderFactory(): UserWalletBuilder.Factory
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import coil.ImageLoaderFactory
|
|||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.tangem.Log
|
||||
import com.tangem.TangemSdkLogger
|
||||
import com.tangem.blockchain.common.ExceptionHandler
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
|
|
@ -46,15 +47,16 @@ import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
|||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
|
|
@ -146,9 +148,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
private val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase
|
||||
get() = entryPoint.getSaveTwinsOnboardingShownUseCase()
|
||||
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase
|
||||
get() = entryPoint.getWalletNameGenerateUseCase()
|
||||
|
||||
private val cardRepository: CardRepository
|
||||
get() = entryPoint.getCardRepository()
|
||||
|
||||
|
|
@ -182,9 +181,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
private val transactionSignerFactory: TransactionSignerFactory
|
||||
get() = entryPoint.getTransactionSignerFactory()
|
||||
|
||||
private val getUserCountryUseCase: GetUserCountryUseCase
|
||||
get() = entryPoint.getGetUserCountryCodeUseCase()
|
||||
|
||||
private val onrampFeatureToggles: OnrampFeatureToggles
|
||||
get() = entryPoint.getOnrampFeatureToggles()
|
||||
|
||||
|
|
@ -212,6 +208,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
private val uiMessageSender: UiMessageSender
|
||||
get() = entryPoint.getUiMessageSender()
|
||||
|
||||
private val blockchainExceptionHandler: BlockchainExceptionHandler
|
||||
get() = entryPoint.getBlockchainExceptionHandler()
|
||||
|
||||
private val workerFactory: HiltWorkerFactory
|
||||
get() = entryPoint.getWorkerFactory()
|
||||
|
||||
|
|
@ -220,6 +219,12 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
.setWorkerFactory(workerFactory)
|
||||
.build()
|
||||
|
||||
private val onlineCardVerifier: OnlineCardVerifier
|
||||
get() = entryPoint.getOnlineCardVerifier()
|
||||
|
||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory
|
||||
get() = entryPoint.getUserWalletBuilderFactory()
|
||||
|
||||
// endregion
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
@ -265,7 +270,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
}
|
||||
|
||||
loadNativeLibraries()
|
||||
// ExceptionHandler.append(blockchainExceptionHandler) // TODO [REDACTED_TASK_KEY] Send only to Firebase
|
||||
ExceptionHandler.append(blockchainExceptionHandler)
|
||||
if (LogConfig.network.blockchainSdkNetwork) {
|
||||
BlockchainSdkRetrofitBuilder.interceptors = listOf(
|
||||
createNetworkLoggingInterceptor(),
|
||||
|
|
@ -301,7 +306,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
generalUserWalletsListManager = generalUserWalletsListManager,
|
||||
wasTwinsOnboardingShownUseCase = wasTwinsOnboardingShownUseCase,
|
||||
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
|
||||
generateWalletNameUseCase = generateWalletNameUseCase,
|
||||
cardRepository = cardRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
blockchainSDKFactory = blockchainSDKFactory,
|
||||
|
|
@ -312,7 +316,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
shareManager = shareManager,
|
||||
appRouter = appRouter,
|
||||
transactionSignerFactory = transactionSignerFactory,
|
||||
getUserCountryUseCase = getUserCountryUseCase,
|
||||
onrampFeatureToggles = onrampFeatureToggles,
|
||||
environmentConfigStorage = environmentConfigStorage,
|
||||
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
||||
|
|
@ -322,6 +325,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
clipboardManager = clipboardManager,
|
||||
settingsManager = settingsManager,
|
||||
uiMessageSender = uiMessageSender,
|
||||
onlineCardVerifier = onlineCardVerifier,
|
||||
userWalletBuilderFactory = userWalletBuilderFactory,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.core.analytics.api.ErrorEventLogger
|
||||
import com.tangem.core.analytics.api.ExceptionLogger
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
import timber.log.Timber
|
||||
|
||||
class AnalyticsEventsLogger(
|
||||
private val name: String,
|
||||
private val jsonConverter: MoshiJsonConverter,
|
||||
) : EventLogger, ErrorEventLogger {
|
||||
) : EventLogger, ExceptionLogger {
|
||||
|
||||
override fun logEvent(event: String, params: Map<String, String>) {
|
||||
Timber.d(jsonConverter.prettyPrint(PrintEventModel(name, event, params)))
|
||||
}
|
||||
|
||||
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
|
||||
override fun logException(error: Throwable, params: Map<String, String>) {
|
||||
Timber.e(error, jsonConverter.prettyPrint(PrintEventModel(name, "error", params)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ internal sealed class Push(event: String) : AnalyticsEvent(
|
|||
category = "Push",
|
||||
event = event,
|
||||
params = emptyMap(),
|
||||
error = null,
|
||||
) {
|
||||
|
||||
data object PushNotificationOpened : Push(event = "Push Notification Opened")
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ sealed class Settings(
|
|||
category: String = "Settings",
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
error: Throwable? = null,
|
||||
) : AnalyticsEvent(category, event, params, error) {
|
||||
) : AnalyticsEvent(category, event, params) {
|
||||
|
||||
class ScreenOpened : Settings(event = "Settings Screen Opened")
|
||||
class ButtonStartWalletConnectSession : Settings(event = "Button - Start Wallet Connect Session")
|
||||
|
|
@ -19,16 +18,14 @@ sealed class Settings(
|
|||
sealed class CardSettings(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
error: Throwable? = null,
|
||||
) : Settings("Settings / Card Settings", event, params, error) {
|
||||
) : Settings("Settings / Card Settings", event, params) {
|
||||
|
||||
class ButtonFactoryReset : CardSettings("Button - Factory Reset")
|
||||
class FactoryResetFinished(cardsCount: Int? = null, error: Throwable? = null) : CardSettings(
|
||||
class FactoryResetFinished(cardsCount: Int? = null) : CardSettings(
|
||||
event = "Factory Reset Finished",
|
||||
params = buildMap {
|
||||
cardsCount?.let { put("Cards Count", "$it") }
|
||||
},
|
||||
error = error,
|
||||
)
|
||||
|
||||
class FactoryResetCanceled(cardsCount: Int) : CardSettings(
|
||||
|
|
@ -44,10 +41,9 @@ sealed class Settings(
|
|||
params = mapOf("Type" to type.value),
|
||||
)
|
||||
|
||||
class SecurityModeChanged(mode: AnalyticsParam.SecurityMode, error: Throwable? = null) : CardSettings(
|
||||
class SecurityModeChanged(mode: AnalyticsParam.SecurityMode) : CardSettings(
|
||||
event = "Security Mode Changed",
|
||||
params = mapOf("Mode" to mode.value),
|
||||
error = error,
|
||||
)
|
||||
|
||||
class AccessCodeRecoveryChanged(status: AnalyticsParam.AccessCodeRecoveryStatus) : CardSettings(
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
|||
sealed class SignIn(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
error: Throwable? = null,
|
||||
) : AnalyticsEvent("Sign In", event, params, error) {
|
||||
) : AnalyticsEvent("Sign In", event, params) {
|
||||
|
||||
class ScreenOpened : SignIn(event = "Sign In Screen Opened")
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ sealed class Token(
|
|||
category: String,
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
error: Throwable? = null,
|
||||
) : AnalyticsEvent(category, event, params, error) {
|
||||
) : AnalyticsEvent(category, event, params) {
|
||||
|
||||
sealed class Receive(
|
||||
event: String,
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.O
|
|||
internal sealed class WalletConnect(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
error: Throwable? = null,
|
||||
) : AnalyticsEvent("Wallet Connect", event, params, error) {
|
||||
) : AnalyticsEvent("Wallet Connect", event, params) {
|
||||
|
||||
class ScreenOpened : WalletConnect(event = "WC Screen Opened")
|
||||
class NewSessionInitiated(source: SourceType) : WalletConnect(
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
package com.tangem.tap.common.analytics.handlers
|
||||
|
||||
import com.tangem.blockchain.common.ExceptionHandlerOutput
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import com.tangem.tap.common.analytics.events.BlockchainExceptionEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
class BlockchainExceptionHandler @Inject constructor(
|
||||
private val analyticsHandler: AnalyticsEventHandler,
|
||||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
) : ExceptionHandlerOutput {
|
||||
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String) {
|
||||
analyticsHandler.send(
|
||||
analyticsErrorHandler.sendErrorEvent(
|
||||
BlockchainExceptionEvent(
|
||||
selectedHost = nextHost,
|
||||
exceptionHost = currentHost,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.tap.common.analytics.handlers.amplitude
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import com.tangem.core.analytics.api.AnalyticsHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
|
||||
class AmplitudeAnalyticsHandler(
|
||||
private val client: AmplitudeAnalyticsClient,
|
||||
) : AnalyticsHandler {
|
||||
) : AnalyticsHandler, AnalyticsErrorHandler {
|
||||
|
||||
override fun id(): String = ID
|
||||
|
||||
|
|
@ -13,6 +15,10 @@ class AmplitudeAnalyticsHandler(
|
|||
client.logEvent(eventId, params)
|
||||
}
|
||||
|
||||
override fun sendErrorEvent(event: AnalyticsEvent) {
|
||||
send(event.id, event.params)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ID = "Amplitude"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
package com.tangem.tap.common.analytics.handlers.firebase
|
||||
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import com.tangem.core.analytics.api.AnalyticsHandler
|
||||
import com.tangem.core.analytics.api.ErrorEventHandler
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter
|
||||
import com.tangem.tap.common.analytics.events.Shop
|
||||
|
||||
class FirebaseAnalyticsHandler(
|
||||
private val client: FirebaseAnalyticsClient,
|
||||
) : AnalyticsHandler, ErrorEventHandler {
|
||||
) : AnalyticsHandler, AnalyticsErrorHandler, AnalyticsExceptionHandler {
|
||||
|
||||
private val errorConverter = AnalyticsErrorConverter()
|
||||
|
||||
override fun id(): String = ID
|
||||
|
||||
|
|
@ -18,30 +20,17 @@ class FirebaseAnalyticsHandler(
|
|||
client.logEvent(eventId, params)
|
||||
}
|
||||
|
||||
override fun send(event: AnalyticsEvent) {
|
||||
val error = event.error
|
||||
when {
|
||||
error != null -> {
|
||||
val errorConverter = AnalyticsErrorConverter()
|
||||
if (!errorConverter.canBeHandled(error)) return
|
||||
override fun sendException(event: ExceptionAnalyticsEvent) {
|
||||
if (!errorConverter.canBeHandled(event.exception)) return
|
||||
|
||||
val errorParams = errorConverter.convert(error).toMutableMap()
|
||||
errorParams["Category"] = event.category
|
||||
errorParams["Event"] = event.event
|
||||
errorParams.putAll(event.params)
|
||||
send(error, errorParams)
|
||||
}
|
||||
event is Shop.Purchased -> {
|
||||
send(FirebaseAnalytics.Event.PURCHASE, event.params)
|
||||
}
|
||||
else -> {
|
||||
super.send(event)
|
||||
}
|
||||
}
|
||||
val errorParams = errorConverter.convert(event.exception).toMutableMap()
|
||||
errorParams.putAll(event.params)
|
||||
|
||||
client.logException(event.exception, event.params)
|
||||
}
|
||||
|
||||
override fun send(error: Throwable, params: Map<String, String>) {
|
||||
client.logErrorEvent(error, params)
|
||||
override fun sendErrorEvent(event: AnalyticsEvent) {
|
||||
send(event)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@ import android.os.Bundle
|
|||
import androidx.core.os.bundleOf
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.crashlytics.recordException
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.tangem.core.analytics.api.ErrorEventLogger
|
||||
import com.tangem.core.analytics.api.ExceptionLogger
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface FirebaseAnalyticsClient : EventLogger, ErrorEventLogger
|
||||
interface FirebaseAnalyticsClient : EventLogger, ExceptionLogger
|
||||
|
||||
internal class FirebaseClient : FirebaseAnalyticsClient {
|
||||
|
||||
|
|
@ -27,11 +28,12 @@ internal class FirebaseClient : FirebaseAnalyticsClient {
|
|||
)
|
||||
}
|
||||
|
||||
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
|
||||
eventConverter.convertEventParams(params)
|
||||
.forEach { fbCrashlytics.setCustomKey(it.key, it.value) }
|
||||
|
||||
fbCrashlytics.recordException(error)
|
||||
// TODO [REDACTED_TASK_KEY] fix recordException usages in the app
|
||||
override fun logException(error: Throwable, params: Map<String, String>) {
|
||||
fbCrashlytics.recordException(error) {
|
||||
eventConverter.convertEventParams(params)
|
||||
.forEach { key(it.key, it.value) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun Map<String, String>.toBundle(): Bundle = bundleOf(*this.toList().toTypedArray())
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ internal class FirebaseLogClient(
|
|||
logger.logEvent(event, params)
|
||||
}
|
||||
|
||||
override fun logErrorEvent(error: Throwable, params: Map<String, String>) {
|
||||
logger.logErrorEvent(error, params)
|
||||
override fun logException(error: Throwable, params: Map<String, String>) {
|
||||
logger.logException(error, params)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,11 +10,13 @@ import com.tangem.common.authentication.AuthenticationManager
|
|||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.core.Config
|
||||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.crypto.bip39.Wordlist
|
||||
import com.tangem.data.card.sdk.CardSdkOwner
|
||||
import com.tangem.data.card.sdk.CardSdkProvider
|
||||
import com.tangem.sdk.DefaultSessionViewDelegate
|
||||
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
|
||||
import com.tangem.sdk.extensions.*
|
||||
import com.tangem.sdk.nfc.AndroidNfcAvailabilityProvider
|
||||
import com.tangem.sdk.nfc.NfcManager
|
||||
|
|
@ -32,8 +34,9 @@ import javax.inject.Singleton
|
|||
*/
|
||||
@Singleton
|
||||
internal class DefaultCardSdkProvider @Inject constructor(
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val cardSdkFeatureToggles: CardSdkFeatureToggles,
|
||||
) : CardSdkProvider, CardSdkOwner {
|
||||
|
||||
private val observer = Observer()
|
||||
|
|
@ -46,7 +49,12 @@ internal class DefaultCardSdkProvider @Inject constructor(
|
|||
override fun register(activity: FragmentActivity) = runBlocking(dispatchers.mainImmediate) {
|
||||
if (activity.isDestroyed || activity.isFinishing || activity.isChangingConfigurations) {
|
||||
val message = "Tangem SDK owner registration skipped: activity is destroyed or finishing"
|
||||
analyticsEventHandler.send(TangemSdkWarningEvent(message))
|
||||
analyticsExceptionHandler.sendException(
|
||||
ExceptionAnalyticsEvent(
|
||||
exception = IllegalStateException(message),
|
||||
params = errorParams,
|
||||
),
|
||||
)
|
||||
Log.info { message }
|
||||
return@runBlocking
|
||||
}
|
||||
|
|
@ -64,14 +72,24 @@ internal class DefaultCardSdkProvider @Inject constructor(
|
|||
|
||||
private fun tryToRegisterWithForegroundActivity(): TangemSdk = runBlocking(dispatchers.mainImmediate) {
|
||||
val warning = "Tangem SDK holder is null, trying to recreate it with foreground activity"
|
||||
analyticsEventHandler.send(TangemSdkWarningEvent(warning))
|
||||
analyticsExceptionHandler.sendException(
|
||||
ExceptionAnalyticsEvent(
|
||||
exception = IllegalStateException(warning),
|
||||
params = errorParams,
|
||||
),
|
||||
)
|
||||
Log.warning { warning }
|
||||
|
||||
val activity = foregroundActivityObserver.foregroundActivity
|
||||
|
||||
if (activity == null) {
|
||||
val error = "Tangem SDK holder is null and foreground activity is null"
|
||||
analyticsEventHandler.send(TangemSdkWarningEvent(error))
|
||||
analyticsExceptionHandler.sendException(
|
||||
ExceptionAnalyticsEvent(
|
||||
exception = IllegalStateException(error),
|
||||
params = errorParams,
|
||||
),
|
||||
)
|
||||
Log.error { error }
|
||||
error(error)
|
||||
}
|
||||
|
|
@ -82,7 +100,12 @@ internal class DefaultCardSdkProvider @Inject constructor(
|
|||
|
||||
if (sdk == null) {
|
||||
val error = "Tangem SDK is null after re-registering with foreground activity"
|
||||
analyticsEventHandler.send(TangemSdkWarningEvent(error))
|
||||
analyticsExceptionHandler.sendException(
|
||||
ExceptionAnalyticsEvent(
|
||||
exception = IllegalStateException(error),
|
||||
params = errorParams,
|
||||
),
|
||||
)
|
||||
Log.error { error }
|
||||
error(error)
|
||||
}
|
||||
|
|
@ -108,7 +131,9 @@ internal class DefaultCardSdkProvider @Inject constructor(
|
|||
authenticationManager = authenticationManager,
|
||||
keystoreManager = keystoreManager,
|
||||
wordlist = Wordlist.getWordlist(activity),
|
||||
config = config,
|
||||
config = config.apply {
|
||||
isNewOnlineAttestationEnabled = cardSdkFeatureToggles.isNewAttestationEnabled
|
||||
},
|
||||
)
|
||||
|
||||
holder = Holder(
|
||||
|
|
@ -170,5 +195,10 @@ internal class DefaultCardSdkProvider @Inject constructor(
|
|||
),
|
||||
),
|
||||
)
|
||||
|
||||
val errorParams = mapOf(
|
||||
"Category" to "Tangem SDK",
|
||||
"Event" to "Warning",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.tap.data
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
internal class TangemSdkWarningEvent(message: String) : AnalyticsEvent(
|
||||
category = "Tangem SDK",
|
||||
event = "Warning",
|
||||
error = IllegalStateException(message),
|
||||
)
|
||||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
|||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
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.walletmanager.WalletManagersFacade
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -67,6 +68,7 @@ internal object ManageTokensDomainModule {
|
|||
networksRepository: NetworksRepository,
|
||||
derivationsRepository: DerivationsRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
): SaveManagedTokensUseCase {
|
||||
return SaveManagedTokensUseCase(
|
||||
customTokensRepository = customTokensRepository,
|
||||
|
|
@ -75,6 +77,7 @@ internal object ManageTokensDomainModule {
|
|||
networksRepository = networksRepository,
|
||||
derivationsRepository = derivationsRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
|||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.markets.*
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
|
|
@ -57,12 +58,16 @@ object MarketsDomainModule {
|
|||
marketsTokenRepository: MarketsTokenRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
): SaveMarketTokensUseCase {
|
||||
return SaveMarketTokensUseCase(
|
||||
derivationsRepository = derivationsRepository,
|
||||
marketsTokenRepository = marketsTokenRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.nft.FetchNFTCollectionsUseCase
|
||||
import com.tangem.domain.nft.GetNFTCollectionsUseCase
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
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 NFTDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetNFTCollectionsUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
): GetNFTCollectionsUseCase {
|
||||
return GetNFTCollectionsUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesFetchNFTCollectionsUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
): FetchNFTCollectionsUseCase {
|
||||
return FetchNFTCollectionsUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.onramp.repositories.LegacyTopUpRepository
|
||||
import com.tangem.domain.onramp.*
|
||||
import com.tangem.domain.onramp.repositories.HotCryptoRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
|
|
@ -242,4 +243,10 @@ internal object OnrampDomainModule {
|
|||
fun provideFetchHotCryptoUseCase(hotCryptoRepository: HotCryptoRepository): FetchHotCryptoUseCase {
|
||||
return FetchHotCryptoUseCase(hotCryptoRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetLegacyTopUpUrlUseCase(legacyTopUpRepository: LegacyTopUpRepository): GetLegacyTopUpUrlUseCase {
|
||||
return GetLegacyTopUpUrlUseCase(legacyTopUpRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.staking.*
|
||||
import com.tangem.domain.staking.repositories.*
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -209,4 +210,12 @@ internal object StakingDomainModule {
|
|||
fun provideGetStakingIntegrationIdUseCase(stakingRepository: StakingRepository): GetStakingIntegrationIdUseCase {
|
||||
return GetStakingIntegrationIdUseCase(stakingRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCheckAccountInitializedUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): CheckAccountInitializedUseCase {
|
||||
return CheckAccountInitializedUseCase(walletManagersFacade)
|
||||
}
|
||||
}
|
||||
|
|
@ -27,10 +27,14 @@ internal object TokensDomainModule {
|
|||
fun provideAddCryptoCurrenciesUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
): AddCryptoCurrenciesUseCase {
|
||||
return AddCryptoCurrenciesUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
|||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -18,6 +21,7 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object WalletsDomainModule {
|
||||
|
|
@ -165,4 +169,22 @@ internal object WalletsDomainModule {
|
|||
fun providesSeedPhraseNotificationUseCase(walletsRepository: WalletsRepository): SeedPhraseNotificationUseCase {
|
||||
return SeedPhraseNotificationUseCase(walletsRepository = walletsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCardImageUseCase(onlineCardVerifier: OnlineCardVerifier): GetCardImageUseCase {
|
||||
return GetCardImageUseCase(verifier = onlineCardVerifier)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesIsWalletNFTEnabledSyncUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
nftFeatureToggles: NFTFeatureToggles,
|
||||
): IsWalletNFTEnabledSyncUseCase {
|
||||
return IsWalletNFTEnabledSyncUseCase(
|
||||
walletsRepository = walletsRepository,
|
||||
nftFeatureToggles = nftFeatureToggles,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.di.routing
|
||||
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.tap.routing.ProxyAppRouter
|
||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||
import com.tangem.tap.routing.configurator.MutableAppRouterConfig
|
||||
|
|
@ -17,8 +18,15 @@ internal object AppRouterModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppRouter(config: AppRouterConfig, dispatchers: CoroutineDispatcherProvider): AppRouter =
|
||||
ProxyAppRouter(config, dispatchers)
|
||||
fun provideAppRouter(
|
||||
config: AppRouterConfig,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
): AppRouter = ProxyAppRouter(
|
||||
config = config,
|
||||
dispatchers = dispatchers,
|
||||
analyticsExceptionHandler = analyticsExceptionHandler,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
|
|||
|
|
@ -6,14 +6,17 @@ import com.tangem.domain.common.TwinCardNumber
|
|||
import com.tangem.domain.common.getTwinCardNumber
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.operations.attestation.CardVerifyAndGetInfo
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.api.models.CardVerifyAndGetInfo
|
||||
|
||||
fun CardDTO.signedHashesCount(): Int {
|
||||
return wallets.sumOf { it.totalSignedHashes ?: 0 }
|
||||
}
|
||||
|
||||
suspend fun CardDTO.getOrLoadCardArtworkUrl(cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null): String {
|
||||
suspend fun CardDTO.getOrLoadCardArtworkUrl(
|
||||
cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null,
|
||||
onlineCardVerifier: OnlineCardVerifier,
|
||||
): String {
|
||||
fun ifAnyError(): String {
|
||||
return when {
|
||||
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
|
||||
|
|
@ -28,7 +31,7 @@ suspend fun CardDTO.getOrLoadCardArtworkUrl(cardInfo: Result<CardVerifyAndGetInf
|
|||
}
|
||||
}
|
||||
|
||||
return when (val cardInfoResult = cardInfo ?: OnlineCardVerifier().getCardInfo(cardId, cardPublicKey)) {
|
||||
return when (val cardInfoResult = cardInfo ?: onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) {
|
||||
is Result.Success -> {
|
||||
val artworkId = cardInfoResult.data.artwork?.id
|
||||
if (artworkId.isNullOrEmpty()) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.common.routing.AppRoute
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
|
|
@ -34,7 +35,12 @@ internal object UseCaseScanProcessor {
|
|||
ifLeft = {
|
||||
val error = scanCardExceptionConverter.convertBack(it)
|
||||
|
||||
Analytics.send(Basic.ScanError(error))
|
||||
Analytics.sendException(
|
||||
ExceptionAnalyticsEvent(
|
||||
exception = error,
|
||||
params = mapOf("Event" to "Scan"),
|
||||
),
|
||||
)
|
||||
CompletionResult.Failure(error)
|
||||
},
|
||||
ifRight = { CompletionResult.Success(it) },
|
||||
|
|
@ -97,7 +103,12 @@ internal object UseCaseScanProcessor {
|
|||
-> {
|
||||
val error = scanCardExceptionConverter.convertBack(exception)
|
||||
|
||||
Analytics.send(Basic.ScanError(error))
|
||||
Analytics.sendException(
|
||||
ExceptionAnalyticsEvent(
|
||||
exception = error,
|
||||
params = mapOf("Event" to "Scan"),
|
||||
),
|
||||
)
|
||||
onFailure(error)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import com.tangem.common.extensions.ByteArrayKey
|
|||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.common.usersCode.UserCodeRepository
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.core.res.getStringSafe
|
||||
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
|
|
@ -224,7 +224,12 @@ internal class DefaultTangemSdkManager(
|
|||
private fun sendScanResultsToAnalytics(result: CompletionResult<ScanResponse>) {
|
||||
if (result is CompletionResult.Failure) {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
Analytics.send(Basic.ScanError(error))
|
||||
Analytics.sendException(
|
||||
ExceptionAnalyticsEvent(
|
||||
exception = error,
|
||||
params = mapOf("Event" to "Scan"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.features.details.redux.walletconnect
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
|
|
@ -61,7 +60,11 @@ class WalletConnectMiddleware {
|
|||
if (uri != null && isWalletConnectUri(uri)) {
|
||||
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
|
||||
} else {
|
||||
store.dispatchNavigationAction { push(AppRoute.QrScanning(SourceType.WALLET_CONNECT)) }
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.QrScanning(AppRoute.QrScanning.Source.WALLET_CONNECT),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import androidx.compose.runtime.Stable
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
|
|
@ -30,6 +32,8 @@ internal class SecurityModeModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val cardSettingsInteractor: CardSettingsInteractor,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
) : Model() {
|
||||
|
||||
private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value
|
||||
|
|
@ -85,14 +89,19 @@ internal class SecurityModeModel @Inject constructor(
|
|||
val paramValue = AnalyticsParam.SecurityMode.from(selectedOption)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue))
|
||||
analyticsEventHandler.send(Settings.CardSettings.SecurityModeChanged(paramValue))
|
||||
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
val error = result.error
|
||||
if (error is TangemSdkError && error !is TangemSdkError.UserCancelled) {
|
||||
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue, error))
|
||||
analyticsExceptionHandler.sendException(
|
||||
ExceptionAnalyticsEvent(
|
||||
exception = error,
|
||||
params = mapOf("Event" to "Security Mode Changed"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ object ClipboardOrScanQrDialog {
|
|||
}
|
||||
setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ ->
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.QrScanning(source = SourceType.WALLET_CONNECT))
|
||||
push(AppRoute.QrScanning(source = AppRoute.QrScanning.Source.WALLET_CONNECT))
|
||||
}
|
||||
}
|
||||
setOnDismissListener {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
|
|
@ -44,12 +43,12 @@ import javax.inject.Inject
|
|||
internal class HomeModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
||||
) : Model() {
|
||||
|
||||
private val tangemErrorHandler = TangemTangemErrorsHandler(store)
|
||||
|
|
@ -100,10 +99,7 @@ internal class HomeModel @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(scanResponse: ScanResponse) {
|
||||
val userWallet = UserWalletBuilder(
|
||||
scanResponse = scanResponse,
|
||||
generateWalletNameUseCase = generateWalletNameUseCase,
|
||||
).build()
|
||||
val userWallet = userWalletBuilderFactory.create(scanResponse = scanResponse).build()
|
||||
|
||||
if (userWallet == null) {
|
||||
Timber.e("User wallet not created")
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import com.tangem.core.analytics.models.AnalyticsParam
|
|||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
|
|
@ -91,8 +90,9 @@ private suspend fun readCard() {
|
|||
}
|
||||
|
||||
private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch {
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build().guard {
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse)
|
||||
|
||||
val userWallet = userWalletBuilder.build().guard {
|
||||
Timber.e("User wallet not created")
|
||||
return@launch
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
|
|
@ -70,6 +69,7 @@ object OnboardingHelper {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
|
||||
val newOnboardingSupportTypes = scanResponse.productType == ProductType.Wallet2 ||
|
||||
scanResponse.productType == ProductType.Ring ||
|
||||
|
|
@ -104,7 +104,11 @@ object OnboardingHelper {
|
|||
} else {
|
||||
AppRoute.OnboardingOther
|
||||
}
|
||||
ProductType.Twins -> AppRoute.OnboardingTwins
|
||||
ProductType.Twins -> if (featureToggles.isTwinRefactoringEnabled) {
|
||||
AppRoute.Onboarding(scanResponse = scanResponse)
|
||||
} else {
|
||||
AppRoute.OnboardingTwins
|
||||
}
|
||||
ProductType.Start2Coin,
|
||||
ProductType.Visa,
|
||||
-> throw UnsupportedOperationException("Onboarding for ${type.name} cards is not supported")
|
||||
|
|
@ -250,8 +254,9 @@ object OnboardingHelper {
|
|||
hasBackupError: Boolean,
|
||||
alreadyCreatedWallet: UserWallet? = null,
|
||||
) {
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWallet = alreadyCreatedWallet ?: UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
|
||||
val userWalletBuilderFactory = store.inject(DaggerGraphState::userWalletBuilderFactory)
|
||||
|
||||
val userWallet = alreadyCreatedWallet ?: userWalletBuilderFactory.create(scanResponse)
|
||||
.hasBackupError(hasBackupError)
|
||||
.backupCardsIds(backupCardsIds?.toSet())
|
||||
.build()
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import com.tangem.common.extensions.isZero
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.CardVerifyAndGetInfo
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.api.models.CardVerifyAndGetInfo
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
|
|
@ -26,16 +26,22 @@ import java.math.BigDecimal
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Deprecated("Remove when navigation refactoring will be implemented")
|
||||
class OnboardingManager(var scanResponse: ScanResponse) {
|
||||
|
||||
private val cardRepository: CardRepository = store.inject(DaggerGraphState::cardRepository)
|
||||
private val cardRepository: CardRepository by lazy { store.inject(DaggerGraphState::cardRepository) }
|
||||
|
||||
private val onlineCardVerifier: OnlineCardVerifier by lazy { store.inject(DaggerGraphState::onlineCardVerifier) }
|
||||
|
||||
private var cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null
|
||||
|
||||
suspend fun loadArtworkUrl(): String {
|
||||
val cardInfo = cardInfo
|
||||
?: OnlineCardVerifier().getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey)
|
||||
?: onlineCardVerifier.getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey)
|
||||
|
||||
this.cardInfo = cardInfo
|
||||
return scanResponse.card.getOrLoadCardArtworkUrl(cardInfo)
|
||||
|
||||
return scanResponse.card.getOrLoadCardArtworkUrl(cardInfo, onlineCardVerifier)
|
||||
}
|
||||
|
||||
suspend fun updateBalance(walletManager: WalletManager): OnboardingWalletBalance {
|
||||
|
|
|
|||
|
|
@ -26,9 +26,7 @@ import com.tangem.tap.store
|
|||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Deprecated("Use OnboardingNoteComponent")
|
||||
class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
||||
|
||||
private var previousStep: OnboardingNoteStep = OnboardingNoteStep.None
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.net.Uri
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
|
|
@ -12,6 +11,7 @@ import com.tangem.common.routing.AppRouter
|
|||
import com.tangem.common.services.Result
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.ScreensSources
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.toWrappedList
|
||||
|
|
@ -24,7 +24,6 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_IDS
|
||||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_PREFIX
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
|
|
@ -243,7 +242,9 @@ private suspend fun readCard(): CompletionResult<ScanResponse> {
|
|||
}
|
||||
|
||||
private suspend fun loadArtworkForCard(cardId: String, cardPublicKey: ByteArray, defaultArtwork: Uri?): Uri {
|
||||
return when (val cardInfo = OnlineCardVerifier().getCardInfo(cardId, cardPublicKey)) {
|
||||
val onlineCardVerifier = store.inject(DaggerGraphState::onlineCardVerifier)
|
||||
|
||||
return when (val cardInfo = onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) {
|
||||
is Result.Success -> {
|
||||
val artworkId = cardInfo.data.artwork?.id
|
||||
if (artworkId.isNullOrEmpty()) {
|
||||
|
|
@ -453,8 +454,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
store.dispatchOnMain(BackupAction.AddBackupCard.Success(result.data))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
val crashlytics = FirebaseCrashlytics.getInstance()
|
||||
|
||||
when (val error = result.error) {
|
||||
is TangemSdkError.CardVerificationFailed -> {
|
||||
Analytics.send(
|
||||
|
|
@ -491,7 +490,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
GlobalAction.ShowDialog(BackupDialog.AttestationFailed),
|
||||
)
|
||||
}
|
||||
else -> crashlytics.recordException(error)
|
||||
else -> Analytics.sendException(ExceptionAnalyticsEvent(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -782,9 +781,10 @@ private suspend fun updateWallet(
|
|||
}
|
||||
|
||||
private suspend fun createUserWallet(scanResponse: ScanResponse, backupState: BackupState): UserWallet {
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse)
|
||||
|
||||
return requireNotNull(
|
||||
value = UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
|
||||
value = userWalletBuilder
|
||||
.backupCardsIds(backupState.backupCardIds.toSet())
|
||||
.hasBackupError(backupState.hasBackupError)
|
||||
.build(),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import androidx.appcompat.app.AlertDialog
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent
|
||||
import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.common.extensions.guard
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
|
|
@ -70,8 +69,10 @@ internal class SaveWalletMiddleware {
|
|||
scope.launch {
|
||||
val backupInfo = state.backupInfo ?: error("Backup info is null")
|
||||
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWallet = UserWalletBuilder(backupInfo.scanResponse, walletNameGenerateUseCase)
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory)
|
||||
.create(scanResponse = backupInfo.scanResponse)
|
||||
|
||||
val userWallet = userWalletBuilder
|
||||
.backupCardsIds(state.backupInfo.backupCardsIds)
|
||||
.hasBackupError(hasBackupError)
|
||||
.build()
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ import com.tangem.common.routing.utils.popTo
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
|
||||
import com.tangem.domain.wallets.legacy.unlockIfLockable
|
||||
import com.tangem.tap.backupService
|
||||
|
|
@ -118,9 +116,9 @@ internal class WelcomeMiddleware {
|
|||
)
|
||||
|
||||
scanCardInternal { scanResponse ->
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase).build()
|
||||
?: return@scanCardInternal
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse)
|
||||
|
||||
val userWallet = userWalletBuilder.build() ?: return@scanCardInternal
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.save(userWallet, canOverride = true)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.extensions.calculateSha512
|
|||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.common.services.performRequest
|
||||
import com.tangem.data.onramp.legacy.mercuryoNetwork
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
|||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
|
|
@ -56,7 +56,6 @@ data class DaggerGraphState(
|
|||
val generalUserWalletsListManager: UserWalletsListManager? = null,
|
||||
val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase? = null,
|
||||
val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null,
|
||||
val generateWalletNameUseCase: GenerateWalletNameUseCase? = null,
|
||||
val cardRepository: CardRepository? = null,
|
||||
val settingsRepository: SettingsRepository? = null,
|
||||
val blockchainSDKFactory: BlockchainSDKFactory? = null,
|
||||
|
|
@ -67,7 +66,6 @@ data class DaggerGraphState(
|
|||
val shareManager: ShareManager? = null,
|
||||
val appRouter: AppRouter? = null,
|
||||
val transactionSignerFactory: TransactionSignerFactory? = null,
|
||||
val getUserCountryUseCase: GetUserCountryUseCase? = null,
|
||||
val onrampFeatureToggles: OnrampFeatureToggles? = null,
|
||||
val environmentConfigStorage: EnvironmentConfigStorage? = null,
|
||||
val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null,
|
||||
|
|
@ -77,4 +75,6 @@ data class DaggerGraphState(
|
|||
val clipboardManager: ClipboardManager? = null,
|
||||
val settingsManager: SettingsManager? = null,
|
||||
val uiMessageSender: UiMessageSender? = null,
|
||||
val onlineCardVerifier: OnlineCardVerifier? = null,
|
||||
val userWalletBuilderFactory: UserWalletBuilder.Factory? = null,
|
||||
) : StateType
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package com.tangem.tap.routing
|
||||
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -15,6 +16,7 @@ import kotlin.reflect.KClass
|
|||
internal class ProxyAppRouter(
|
||||
private val config: AppRouterConfig,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
) : AppRouter {
|
||||
|
||||
private val routerScope: CoroutineScope
|
||||
|
|
@ -76,7 +78,7 @@ internal class ProxyAppRouter(
|
|||
|
||||
override fun defaultCompletionHandler(isSuccess: Boolean, errorMessage: String) {
|
||||
if (!isSuccess) {
|
||||
FirebaseCrashlytics.getInstance().recordException(RuntimeException(errorMessage))
|
||||
analyticsExceptionHandler.sendException(ExceptionAnalyticsEvent(RuntimeException(errorMessage)))
|
||||
Timber.w(errorMessage)
|
||||
|
||||
with(receiver = config.snackbarHandler ?: return) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.routing.utils
|
|||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.feature.qrscanning.QrScanningComponent
|
||||
import com.tangem.feature.referral.api.ReferralComponent
|
||||
import com.tangem.feature.stories.api.StoriesComponent
|
||||
|
|
@ -15,8 +16,9 @@ import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
|
|||
import com.tangem.features.onramp.component.*
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import com.tangem.features.send.api.SendComponent
|
||||
import com.tangem.features.swap.SwapComponent
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.features.staking.api.StakingComponent
|
||||
import com.tangem.features.swap.SwapComponent
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import com.tangem.features.tokendetails.TokenDetailsComponent
|
||||
import com.tangem.features.wallet.WalletEntryComponent
|
||||
|
|
@ -74,6 +76,8 @@ internal class ChildFactory @Inject constructor(
|
|||
private val referralComponentFactory: ReferralComponent.Factory,
|
||||
private val pushNotificationsComponentFactory: PushNotificationsComponent.Factory,
|
||||
private val walletComponentFactory: WalletEntryComponent.Factory,
|
||||
private val sendComponentFactoryV2: com.tangem.features.send.v2.api.SendComponent.Factory,
|
||||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
private val testerRouter: TesterRouter,
|
||||
private val routingFeatureToggles: RoutingFeatureToggles,
|
||||
) {
|
||||
|
|
@ -256,18 +260,33 @@ internal class ChildFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
is AppRoute.Send -> {
|
||||
createComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = SendComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
currency = route.currency,
|
||||
transactionId = route.transactionId,
|
||||
amount = route.amount,
|
||||
tag = route.tag,
|
||||
destinationAddress = route.destinationAddress,
|
||||
),
|
||||
componentFactory = sendComponentFactory,
|
||||
)
|
||||
if (sendFeatureToggles.isSendV2Enabled) {
|
||||
createComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = com.tangem.features.send.v2.api.SendComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
currency = route.currency,
|
||||
transactionId = route.transactionId,
|
||||
amount = route.amount,
|
||||
tag = route.tag,
|
||||
destinationAddress = route.destinationAddress,
|
||||
),
|
||||
componentFactory = sendComponentFactoryV2,
|
||||
)
|
||||
} else {
|
||||
createComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = SendComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
currency = route.currency,
|
||||
transactionId = route.transactionId,
|
||||
amount = route.amount,
|
||||
tag = route.tag,
|
||||
destinationAddress = route.destinationAddress,
|
||||
),
|
||||
componentFactory = sendComponentFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
is AppRoute.Home -> {
|
||||
createComponentChild(
|
||||
|
|
@ -284,10 +303,14 @@ internal class ChildFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
is AppRoute.QrScanning -> {
|
||||
val source = when (route.source) {
|
||||
AppRoute.QrScanning.Source.SEND -> SourceType.SEND
|
||||
AppRoute.QrScanning.Source.WALLET_CONNECT -> SourceType.WALLET_CONNECT
|
||||
}
|
||||
createComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = QrScanningComponent.Params(
|
||||
source = route.source,
|
||||
source = source,
|
||||
networkName = route.networkName,
|
||||
),
|
||||
componentFactory = qrScanningComponentFactory,
|
||||
|
|
@ -400,18 +423,33 @@ internal class ChildFactory @Inject constructor(
|
|||
route.asFragmentChild(Provider { SaveWalletBottomSheetFragment() })
|
||||
}
|
||||
is AppRoute.Send -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = SendComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
currency = route.currency,
|
||||
transactionId = route.transactionId,
|
||||
amount = route.amount,
|
||||
tag = route.tag,
|
||||
destinationAddress = route.destinationAddress,
|
||||
),
|
||||
componentFactory = sendComponentFactory,
|
||||
)
|
||||
if (sendFeatureToggles.isSendV2Enabled) {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = com.tangem.features.send.v2.api.SendComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
currency = route.currency,
|
||||
transactionId = route.transactionId,
|
||||
amount = route.amount,
|
||||
tag = route.tag,
|
||||
destinationAddress = route.destinationAddress,
|
||||
),
|
||||
componentFactory = sendComponentFactoryV2,
|
||||
)
|
||||
} else {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = SendComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
currency = route.currency,
|
||||
transactionId = route.transactionId,
|
||||
amount = route.amount,
|
||||
tag = route.tag,
|
||||
destinationAddress = route.destinationAddress,
|
||||
),
|
||||
componentFactory = sendComponentFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
is AppRoute.AppSettings -> {
|
||||
route.asComponentChild(
|
||||
|
|
@ -483,10 +521,14 @@ internal class ChildFactory @Inject constructor(
|
|||
route.asFragmentChild(Provider { OnboardingWalletFragment() })
|
||||
}
|
||||
is AppRoute.QrScanning -> {
|
||||
val source = when (route.source) {
|
||||
AppRoute.QrScanning.Source.SEND -> SourceType.SEND
|
||||
AppRoute.QrScanning.Source.WALLET_CONNECT -> SourceType.WALLET_CONNECT
|
||||
}
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = QrScanningComponent.Params(
|
||||
source = route.source,
|
||||
source = source,
|
||||
networkName = route.networkName,
|
||||
),
|
||||
componentFactory = qrScanningComponentFactory,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -147,9 +146,15 @@ sealed class AppRoute(val path: String) : Route {
|
|||
|
||||
@Serializable
|
||||
data class QrScanning(
|
||||
val source: SourceType,
|
||||
val source: Source,
|
||||
val networkName: String? = null,
|
||||
) : AppRoute(path = "/$source/qr_scanning${if (networkName != null) "/$networkName" else ""}")
|
||||
) : AppRoute(path = "/$source/qr_scanning${if (networkName != null) "/$networkName" else ""}") {
|
||||
|
||||
enum class Source {
|
||||
WALLET_CONNECT,
|
||||
SEND,
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ReferralProgram(
|
||||
|
|
|
|||
|
|
@ -159,6 +159,11 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
|||
),
|
||||
subtitle = resourceReference(id = R.string.send_notification_invalid_reserve_amount_text),
|
||||
)
|
||||
|
||||
data object DestinationMemoRequired : Error(
|
||||
title = resourceReference(id = R.string.send_validation_destination_tag_required_title),
|
||||
subtitle = resourceReference(id = R.string.send_validation_destination_tag_required_description),
|
||||
)
|
||||
}
|
||||
|
||||
open class Warning(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.BlockchainSdkError
|
|||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addRequireDestinationFlagErrorNotification
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
|
|
@ -112,6 +113,10 @@ object NotificationsFactory {
|
|||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addRequireDestinationFlagErrorNotification() {
|
||||
add(NotificationUM.Error.DestinationMemoRequired)
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addMinimumAmountErrorNotification(
|
||||
minimumSendAmount: BigDecimal?,
|
||||
sendingAmount: BigDecimal,
|
||||
|
|
@ -326,6 +331,9 @@ object NotificationsFactory {
|
|||
error = validationError,
|
||||
onReduceClick = onReduceClick,
|
||||
)
|
||||
is BlockchainSdkError.XRP -> addXRPTransactionValidationError(
|
||||
error = validationError,
|
||||
)
|
||||
null -> minAdaValue?.let {
|
||||
add(
|
||||
NotificationUM.Cardano.MinAdaValueCharged(
|
||||
|
|
@ -406,6 +414,12 @@ object NotificationsFactory {
|
|||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addXRPTransactionValidationError(error: BlockchainSdkError.XRP) {
|
||||
when (error) {
|
||||
is BlockchainSdkError.XRP.DestinationMemoRequired -> addRequireDestinationFlagErrorNotification()
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addRentExemptionNotification(rentWarning: CryptoCurrencyWarning.Rent?) {
|
||||
if (rentWarning == null) return
|
||||
add(NotificationUM.Solana.RentInfo(rentWarning))
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ open class AnalyticsEvent(
|
|||
val category: String,
|
||||
val event: String,
|
||||
var params: Map<String, String> = mapOf(),
|
||||
val error: Throwable? = null,
|
||||
) {
|
||||
|
||||
val id: String = "[$category] $event"
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ package com.tangem.core.analytics.models
|
|||
sealed class Basic(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
error: Throwable? = null,
|
||||
) : AnalyticsEvent("Basic", event, params, error) {
|
||||
) : AnalyticsEvent("Basic", event, params) {
|
||||
|
||||
class CardWasScanned(
|
||||
source: AnalyticsParam.ScreensSources,
|
||||
|
|
@ -73,11 +72,6 @@ sealed class Basic(
|
|||
}
|
||||
}
|
||||
|
||||
class ScanError(error: Throwable) : Basic(
|
||||
event = "Scan",
|
||||
error = error,
|
||||
)
|
||||
|
||||
class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic(
|
||||
event = "Request Support",
|
||||
params = mapOf(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.core.analytics.models
|
||||
|
||||
class ExceptionAnalyticsEvent(
|
||||
val exception: Throwable,
|
||||
// val category: String,
|
||||
// val event: String,
|
||||
var params: Map<String, String> = mapOf(),
|
||||
)
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.core.analytics
|
|||
|
||||
import com.tangem.core.analytics.api.*
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
|
@ -16,7 +17,9 @@ interface GlobalAnalyticsEventHandler :
|
|||
AnalyticsEventHandler,
|
||||
AnalyticsHandlerHolder,
|
||||
AnalyticsFilterHolder,
|
||||
ParamsInterceptorHolder
|
||||
ParamsInterceptorHolder,
|
||||
AnalyticsErrorHandler,
|
||||
AnalyticsExceptionHandler
|
||||
|
||||
object Analytics : GlobalAnalyticsEventHandler {
|
||||
|
||||
|
|
@ -72,6 +75,25 @@ object Analytics : GlobalAnalyticsEventHandler {
|
|||
}
|
||||
}
|
||||
|
||||
override fun sendErrorEvent(event: AnalyticsEvent) {
|
||||
analyticsScope.launch {
|
||||
event.params = applyParamsInterceptors(event)
|
||||
analyticsMutex.withLock {
|
||||
analyticsHandlers.filterIsInstance<AnalyticsErrorHandler>()
|
||||
.forEach { handler -> handler.sendErrorEvent(event) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendException(event: ExceptionAnalyticsEvent) {
|
||||
analyticsScope.launch {
|
||||
analyticsMutex.withLock {
|
||||
analyticsHandlers.filterIsInstance<AnalyticsExceptionHandler>()
|
||||
.forEach { it.sendException(event) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun applyParamsInterceptors(event: AnalyticsEvent): MutableMap<String, String> {
|
||||
val interceptedParams = event.params.toMutableMap()
|
||||
analyticsMutex.withLock {
|
||||
|
|
|
|||
|
|
@ -8,5 +8,9 @@ interface EventLogger {
|
|||
}
|
||||
|
||||
interface ErrorEventLogger {
|
||||
fun logErrorEvent(error: Throwable, params: Map<String, String> = emptyMap())
|
||||
fun logErrorEvent(event: String, params: Map<String, String> = emptyMap())
|
||||
}
|
||||
|
||||
interface ExceptionLogger {
|
||||
fun logException(error: Throwable, params: Map<String, String> = emptyMap())
|
||||
}
|
||||
|
|
@ -1,15 +1,25 @@
|
|||
package com.tangem.core.analytics.api
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
* Sends usual analytics events
|
||||
*/
|
||||
interface AnalyticsEventHandler {
|
||||
fun send(event: AnalyticsEvent)
|
||||
}
|
||||
|
||||
interface AnalyticsErrorHandler {
|
||||
fun sendErrorEvent(event: AnalyticsEvent)
|
||||
}
|
||||
|
||||
interface AnalyticsExceptionHandler {
|
||||
fun sendException(event: ExceptionAnalyticsEvent)
|
||||
}
|
||||
|
||||
interface AnalyticsHandler : AnalyticsEventHandler {
|
||||
|
||||
fun id(): String
|
||||
|
||||
fun send(eventId: String, params: Map<String, String> = emptyMap())
|
||||
|
|
@ -19,10 +29,6 @@ interface AnalyticsHandler : AnalyticsEventHandler {
|
|||
}
|
||||
}
|
||||
|
||||
interface ErrorEventHandler {
|
||||
fun send(error: Throwable, params: Map<String, String> = emptyMap())
|
||||
}
|
||||
|
||||
interface AnalyticsHandlerHolder {
|
||||
fun addHandler(name: String, handler: AnalyticsHandler)
|
||||
fun removeHandler(name: String): AnalyticsHandler?
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.core.analytics.di
|
||||
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
|
||||
import com.tangem.core.analytics.api.ParamsInterceptorHolder
|
||||
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
||||
import com.tangem.domain.analytics.repository.AnalyticsRepository
|
||||
|
|
@ -21,6 +23,18 @@ internal object AnalyticsModule {
|
|||
return Analytics // todo replace after refactoring calling Analytics in whole project
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAnalyticsErrorHandler(): AnalyticsErrorHandler {
|
||||
return Analytics
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAnalyticsExceptionHandler(): AnalyticsExceptionHandler {
|
||||
return Analytics
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideParamsInterceptorHolder(): ParamsInterceptorHolder {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,18 @@
|
|||
},
|
||||
{
|
||||
"name": "NOTE_REFACTORING_ENABLED",
|
||||
"version": "5.23.0"
|
||||
},
|
||||
{
|
||||
"name": "NEW_ATTESTATION_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "TWIN_REFACTORING_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "SEND_V2_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
import java.lang.reflect.Type
|
||||
|
||||
internal class ApiResponseCallAdapter(
|
||||
private val resultType: Type,
|
||||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
) : CallAdapter<Type, Call<ApiResponse<Type>>> {
|
||||
|
||||
override fun responseType(): Type = resultType
|
||||
|
||||
override fun adapt(call: Call<Type>): Call<ApiResponse<Type>> {
|
||||
return ApiResponseCallDelegate(call)
|
||||
return ApiResponseCallDelegate(call, analyticsErrorHandler)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
import retrofit2.Retrofit
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class ApiResponseCallAdapterFactory private constructor() : CallAdapter.Factory() {
|
||||
class ApiResponseCallAdapterFactory private constructor(
|
||||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
) : CallAdapter.Factory() {
|
||||
|
||||
override fun get(returnType: Type, annotations: Array<out Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
|
||||
if (getRawType(returnType) != Call::class.java) {
|
||||
|
|
@ -19,11 +22,11 @@ class ApiResponseCallAdapterFactory private constructor() : CallAdapter.Factory(
|
|||
}
|
||||
|
||||
val resultType = getParameterUpperBound(0, callType as ParameterizedType)
|
||||
return ApiResponseCallAdapter(resultType)
|
||||
return ApiResponseCallAdapter(resultType, analyticsErrorHandler)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun create() = ApiResponseCallAdapterFactory()
|
||||
fun create(analyticsErrorHandler: AnalyticsErrorHandler) = ApiResponseCallAdapterFactory(analyticsErrorHandler)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import okhttp3.Request
|
||||
import okio.Timeout
|
||||
import retrofit2.Call
|
||||
|
|
@ -9,6 +10,7 @@ import timber.log.Timber
|
|||
|
||||
internal class ApiResponseCallDelegate<T : Any>(
|
||||
private val wrappedCall: Call<T>,
|
||||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
) : Call<ApiResponse<T>> {
|
||||
|
||||
override fun enqueue(callback: Callback<ApiResponse<T>>) {
|
||||
|
|
@ -16,7 +18,7 @@ internal class ApiResponseCallDelegate<T : Any>(
|
|||
}
|
||||
|
||||
override fun execute(): Response<ApiResponse<T>> = throw NotImplementedError()
|
||||
override fun clone(): Call<ApiResponse<T>> = ApiResponseCallDelegate(wrappedCall.clone())
|
||||
override fun clone(): Call<ApiResponse<T>> = ApiResponseCallDelegate(wrappedCall.clone(), analyticsErrorHandler)
|
||||
override fun request(): Request = wrappedCall.request()
|
||||
override fun timeout(): Timeout = wrappedCall.timeout()
|
||||
override fun isExecuted(): Boolean = wrappedCall.isExecuted
|
||||
|
|
@ -30,7 +32,7 @@ internal class ApiResponseCallDelegate<T : Any>(
|
|||
) : Callback<T> {
|
||||
|
||||
override fun onResponse(call: Call<T>, response: Response<T>) {
|
||||
val safeResponse = response.toSafeApiResponse()
|
||||
val safeResponse = response.toSafeApiResponse(analyticsErrorHandler)
|
||||
|
||||
responseCallback.onResponse(this@ApiResponseCallDelegate, Response.success(safeResponse))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,51 +19,51 @@ sealed class ApiResponseError : Exception() {
|
|||
) : ApiResponseError() {
|
||||
|
||||
// region Error Codes
|
||||
enum class Code(val code: Int) {
|
||||
enum class Code(val numericCode: Int) {
|
||||
// 4xx Server Errors
|
||||
BAD_REQUEST(code = 400),
|
||||
UNAUTHORIZED(code = 401),
|
||||
PAYMENT_REQUIRED(code = 402),
|
||||
FORBIDDEN(code = 403),
|
||||
NOT_FOUND(code = 404),
|
||||
METHOD_NOT_ALLOWED(code = 405),
|
||||
NOT_ACCEPTABLE(code = 406),
|
||||
PROXY_AUTHENTICATION_REQUIRED(code = 407),
|
||||
REQUEST_TIMEOUT(code = 408),
|
||||
CONFLICT(code = 409),
|
||||
GONE(code = 410),
|
||||
LENGTH_REQUIRED(code = 411),
|
||||
PRECONDITION_FAILED(code = 412),
|
||||
PAYLOAD_TOO_LARGE(code = 413),
|
||||
URI_TOO_LONG(code = 414),
|
||||
UNSUPPORTED_MEDIA_TYPE(code = 415),
|
||||
RANGE_NOT_SATISFIABLE(code = 416),
|
||||
EXPECTATION_FAILED(code = 417),
|
||||
IM_A_TEAPOT(code = 418), // Not an error, but an April Fools' joke from RFC 2324
|
||||
UNPROCESSABLE_ENTITY(code = 422),
|
||||
LOCKED(code = 423),
|
||||
FAILED_DEPENDENCY(code = 424),
|
||||
TOO_EARLY(code = 425),
|
||||
UPGRADE_REQUIRED(code = 426),
|
||||
PRECONDITION_REQUIRED(code = 428),
|
||||
TOO_MANY_REQUESTS(code = 429),
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE(code = 431),
|
||||
UNAVAILABLE_FOR_LEGAL_REASONS(code = 451),
|
||||
BAD_REQUEST(numericCode = 400),
|
||||
UNAUTHORIZED(numericCode = 401),
|
||||
PAYMENT_REQUIRED(numericCode = 402),
|
||||
FORBIDDEN(numericCode = 403),
|
||||
NOT_FOUND(numericCode = 404),
|
||||
METHOD_NOT_ALLOWED(numericCode = 405),
|
||||
NOT_ACCEPTABLE(numericCode = 406),
|
||||
PROXY_AUTHENTICATION_REQUIRED(numericCode = 407),
|
||||
REQUEST_TIMEOUT(numericCode = 408),
|
||||
CONFLICT(numericCode = 409),
|
||||
GONE(numericCode = 410),
|
||||
LENGTH_REQUIRED(numericCode = 411),
|
||||
PRECONDITION_FAILED(numericCode = 412),
|
||||
PAYLOAD_TOO_LARGE(numericCode = 413),
|
||||
URI_TOO_LONG(numericCode = 414),
|
||||
UNSUPPORTED_MEDIA_TYPE(numericCode = 415),
|
||||
RANGE_NOT_SATISFIABLE(numericCode = 416),
|
||||
EXPECTATION_FAILED(numericCode = 417),
|
||||
IM_A_TEAPOT(numericCode = 418), // Not an error, but an April Fools' joke from RFC 2324
|
||||
UNPROCESSABLE_ENTITY(numericCode = 422),
|
||||
LOCKED(numericCode = 423),
|
||||
FAILED_DEPENDENCY(numericCode = 424),
|
||||
TOO_EARLY(numericCode = 425),
|
||||
UPGRADE_REQUIRED(numericCode = 426),
|
||||
PRECONDITION_REQUIRED(numericCode = 428),
|
||||
TOO_MANY_REQUESTS(numericCode = 429),
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE(numericCode = 431),
|
||||
UNAVAILABLE_FOR_LEGAL_REASONS(numericCode = 451),
|
||||
// 5xx Server Errors
|
||||
INTERNAL_SERVER_ERROR(code = 500),
|
||||
NOT_IMPLEMENTED(code = 501),
|
||||
BAD_GATEWAY(code = 502),
|
||||
SERVICE_UNAVAILABLE(code = 503),
|
||||
GATEWAY_TIMEOUT(code = 504),
|
||||
HTTP_VERSION_NOT_SUPPORTED(code = 505),
|
||||
VARIANT_ALSO_NEGOTIATES(code = 506),
|
||||
INSUFFICIENT_STORAGE(code = 507),
|
||||
LOOP_DETECTED(code = 508),
|
||||
NOT_EXTENDED(code = 510),
|
||||
NETWORK_AUTHENTICATION_REQUIRED(code = 511),
|
||||
INTERNAL_SERVER_ERROR(numericCode = 500),
|
||||
NOT_IMPLEMENTED(numericCode = 501),
|
||||
BAD_GATEWAY(numericCode = 502),
|
||||
SERVICE_UNAVAILABLE(numericCode = 503),
|
||||
GATEWAY_TIMEOUT(numericCode = 504),
|
||||
HTTP_VERSION_NOT_SUPPORTED(numericCode = 505),
|
||||
VARIANT_ALSO_NEGOTIATES(numericCode = 506),
|
||||
INSUFFICIENT_STORAGE(numericCode = 507),
|
||||
LOOP_DETECTED(numericCode = 508),
|
||||
NOT_EXTENDED(numericCode = 510),
|
||||
NETWORK_AUTHENTICATION_REQUIRED(numericCode = 511),
|
||||
;
|
||||
|
||||
override fun toString(): String = "$code - $name"
|
||||
override fun toString(): String = "$numericCode - $name"
|
||||
|
||||
companion object {
|
||||
val values = values()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import com.tangem.datasource.api.common.response.analytics.ApiErrorEvent
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import retrofit2.Response
|
||||
import timber.log.Timber
|
||||
|
|
@ -9,18 +11,20 @@ import java.net.UnknownHostException
|
|||
import java.util.concurrent.TimeoutException
|
||||
import javax.net.ssl.SSLHandshakeException
|
||||
|
||||
internal fun <T : Any> Response<T>.toSafeApiResponse(): ApiResponse<T> {
|
||||
internal fun <T : Any> Response<T>.toSafeApiResponse(analyticsErrorHandler: AnalyticsErrorHandler): ApiResponse<T> {
|
||||
val body = body()
|
||||
|
||||
return if (isSuccessful && body != null) {
|
||||
apiSuccess(body)
|
||||
} else {
|
||||
val code = ApiResponseError.HttpException.Code.values
|
||||
.firstOrNull { it.code == code() }
|
||||
.firstOrNull { it.numericCode == code() }
|
||||
val e = try {
|
||||
if (code == null) {
|
||||
ApiResponseError.UnknownException(IllegalArgumentException("Unknown error status code: ${code()}"))
|
||||
} else {
|
||||
sendHttpError(code, analyticsErrorHandler)
|
||||
|
||||
ApiResponseError.HttpException(code, message(), errorBody()?.string())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
|
@ -32,6 +36,21 @@ internal fun <T : Any> Response<T>.toSafeApiResponse(): ApiResponse<T> {
|
|||
}
|
||||
}
|
||||
|
||||
private fun <T : Any> Response<T>.sendHttpError(
|
||||
code: ApiResponseError.HttpException.Code,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
) {
|
||||
val fullRequestUrl = raw().request.url.toUrl()
|
||||
val shortUrl = fullRequestUrl.authority + fullRequestUrl.path
|
||||
analyticsErrorHandler.sendErrorEvent(
|
||||
ApiErrorEvent(
|
||||
endpoint = shortUrl,
|
||||
code = code.numericCode,
|
||||
message = errorBody()?.string().orEmpty(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun Throwable.toApiError(): ApiResponseError = when (this) {
|
||||
is ConnectException,
|
||||
is UnknownHostException,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.datasource.api.common.response.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
internal data class ApiErrorEvent(
|
||||
val endpoint: String,
|
||||
val code: Int,
|
||||
val message: String,
|
||||
) : AnalyticsEvent(
|
||||
category = "Tangem API Service",
|
||||
event = "Exception",
|
||||
params = mapOf(
|
||||
"Endpoint" to endpoint,
|
||||
"Code" to code.toString(),
|
||||
"Message" to message,
|
||||
),
|
||||
)
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.datasource.api.stakekit.models.response.model
|
|||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.stakekit.models.request.Address
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO.PendingAction.PendingActionArgs.Amount
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
|
||||
import org.joda.time.DateTime
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -31,6 +32,8 @@ data class BalanceDTO(
|
|||
val pricePerShare: BigDecimal,
|
||||
@Json(name = "pendingActions")
|
||||
val pendingActions: List<PendingAction>,
|
||||
@Json(name = "pendingActionConstraints")
|
||||
val pendingActionConstraints: List<PendingActionConstraints>?,
|
||||
@Json(name = "token")
|
||||
val tokenDTO: TokenDTO,
|
||||
@Json(name = "validatorAddress")
|
||||
|
|
@ -99,7 +102,7 @@ data class BalanceDTO(
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class Amount(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
val required: Boolean = true,
|
||||
@Json(name = "minimum")
|
||||
val minimum: BigDecimal?,
|
||||
@Json(name = "maximum")
|
||||
|
|
@ -136,6 +139,14 @@ data class BalanceDTO(
|
|||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PendingActionConstraints(
|
||||
@Json(name = "type")
|
||||
val type: StakingActionTypeDTO,
|
||||
@Json(name = "amount")
|
||||
val amountArg: Amount,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Required(
|
||||
@Json(name = "required")
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.datasource.di
|
|||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||
|
|
@ -61,6 +62,7 @@ internal object NetworkModule {
|
|||
fun provideExpressApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): TangemExpressApi {
|
||||
|
|
@ -69,6 +71,7 @@ internal object NetworkModule {
|
|||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
|
|
@ -83,6 +86,7 @@ internal object NetworkModule {
|
|||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
appLogsStore: AppLogsStore,
|
||||
): StakeKitApi {
|
||||
return createApi(
|
||||
|
|
@ -90,6 +94,7 @@ internal object NetworkModule {
|
|||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
timeouts = Timeouts(
|
||||
callTimeoutSeconds = STAKE_KIT_API_TIMEOUT_SECONDS,
|
||||
connectTimeoutSeconds = STAKE_KIT_API_TIMEOUT_SECONDS,
|
||||
|
|
@ -109,6 +114,7 @@ internal object NetworkModule {
|
|||
fun provideOnrampApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): OnrampApi {
|
||||
|
|
@ -117,6 +123,7 @@ internal object NetworkModule {
|
|||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
|
|
@ -130,6 +137,7 @@ internal object NetworkModule {
|
|||
fun provideTangemTechApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
): TangemTechApi {
|
||||
return createApi(
|
||||
|
|
@ -137,6 +145,7 @@ internal object NetworkModule {
|
|||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = { applyTimeoutAnnotations() },
|
||||
)
|
||||
}
|
||||
|
|
@ -147,6 +156,7 @@ internal object NetworkModule {
|
|||
fun provideTangemTechApiV2(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
appVersionProvider: AppVersionProvider,
|
||||
): TangemTechApiV2 {
|
||||
return provideTangemTechApiInternal(
|
||||
|
|
@ -154,6 +164,7 @@ internal object NetworkModule {
|
|||
context = context,
|
||||
appVersionProvider = appVersionProvider,
|
||||
baseUrl = PROD_V2_TANGEM_TECH_BASE_URL,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -162,6 +173,7 @@ internal object NetworkModule {
|
|||
fun provideTangemTechMarketsApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
): TangemTechMarketsApi {
|
||||
return createApi(
|
||||
|
|
@ -169,6 +181,7 @@ internal object NetworkModule {
|
|||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
this.callTimeout(TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
.connectTimeout(TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
|
|
@ -183,6 +196,7 @@ internal object NetworkModule {
|
|||
fun provideTangemVisaAuthApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): TangemVisaAuthApi {
|
||||
|
|
@ -191,6 +205,7 @@ internal object NetworkModule {
|
|||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
|
|
@ -204,6 +219,7 @@ internal object NetworkModule {
|
|||
fun provideTangemVisaApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): TangemVisaApi {
|
||||
|
|
@ -212,6 +228,7 @@ internal object NetworkModule {
|
|||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
|
|
@ -226,6 +243,7 @@ internal object NetworkModule {
|
|||
context: Context,
|
||||
appVersionProvider: AppVersionProvider,
|
||||
baseUrl: String,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
timeouts: Timeouts = Timeouts(),
|
||||
requestHeaders: List<RequestHeader> = listOf(AppVersionPlatformHeaders(appVersionProvider)),
|
||||
): T {
|
||||
|
|
@ -257,7 +275,7 @@ internal object NetworkModule {
|
|||
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create(analyticsErrorHandler))
|
||||
.baseUrl(baseUrl)
|
||||
.client(client)
|
||||
.build()
|
||||
|
|
@ -269,6 +287,7 @@ internal object NetworkModule {
|
|||
moshi: Moshi,
|
||||
context: Context,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
timeouts: Timeouts = Timeouts(),
|
||||
clientBuilder: OkHttpClient.Builder.() -> OkHttpClient.Builder = { this },
|
||||
): T {
|
||||
|
|
@ -276,7 +295,7 @@ internal object NetworkModule {
|
|||
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create(analyticsErrorHandler))
|
||||
.baseUrl(environmentConfig.baseUrl)
|
||||
.client(
|
||||
OkHttpClient.Builder()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ internal class DefaultNFTPersistenceStore(
|
|||
private val pricesPersistenceStore: DataStore<Map<NFTAsset.Identifier, NFTAsset.SalePrice>>,
|
||||
) : NFTPersistenceStore {
|
||||
|
||||
override fun getCollections(): Flow<List<NFTCollection>> = collectionsPersistenceStore.data
|
||||
override fun getCollections(): Flow<List<NFTCollection>?> = collectionsPersistenceStore.data
|
||||
|
||||
override suspend fun getCollectionsSync(): List<NFTCollection>? = collectionsPersistenceStore
|
||||
.data
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.tangem.blockchain.nft.models.NFTCollection
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NFTPersistenceStore {
|
||||
fun getCollections(): Flow<List<NFTCollection>>
|
||||
fun getCollections(): Flow<List<NFTCollection>?>
|
||||
|
||||
suspend fun getCollectionsSync(): List<NFTCollection>?
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.local.nft
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
|
|
@ -11,10 +12,12 @@ import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
|||
import com.tangem.datasource.utils.listTypes
|
||||
import com.tangem.datasource.utils.mapWithCustomKeyTypes
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -25,41 +28,51 @@ class NFTPersistenceStoreFactory @Inject constructor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
fun provide(network: Network): NFTPersistenceStore {
|
||||
val networkStringIdentifier = listOfNotNull(
|
||||
network.id.value,
|
||||
network.derivationPath.value,
|
||||
).joinToString("_") {
|
||||
// remove all non-alphanumeric characters
|
||||
it
|
||||
.toCharArray()
|
||||
.filter(Char::isLetterOrDigit)
|
||||
.joinToString("")
|
||||
.lowercase()
|
||||
}
|
||||
fun provide(userWalletId: UserWalletId, network: Network): NFTPersistenceStore {
|
||||
// simplify network identifier so that is correct for a file name
|
||||
// e.g. eth_m4460000 or theopennetwork_m446070
|
||||
val networkStringId =
|
||||
network.id.formatted() + network.derivationPath.formatted()?.let { "_$it" }.orEmpty()
|
||||
// simplify user wallet id so that is correct for a file name
|
||||
// e.g. 9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a
|
||||
val userWalletStringId = userWalletId.formatted()
|
||||
return DefaultNFTPersistenceStore(
|
||||
collectionsPersistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = listTypes<NFTCollection>(),
|
||||
defaultValue = emptyList(),
|
||||
),
|
||||
produceFile = {
|
||||
context.dataStoreFile(fileName = "nft_${networkStringIdentifier}_collections")
|
||||
},
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
collectionsPersistenceStore = createPersistenceStore(
|
||||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_eth_m4460000_collections
|
||||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_theopennetwork_m446070_collections
|
||||
fileName = "nft_${userWalletStringId}_${networkStringId}_collections",
|
||||
types = listTypes<NFTCollection>(),
|
||||
defaultValue = emptyList(),
|
||||
),
|
||||
pricesPersistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = mapWithCustomKeyTypes<NFTAsset.Identifier, NFTAsset.SalePrice>(),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = {
|
||||
context.dataStoreFile(fileName = "nft_${networkStringIdentifier}_prices")
|
||||
},
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
pricesPersistenceStore = createPersistenceStore(
|
||||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_eth_m4460000_prices
|
||||
// result file name example: nft_9a1a178f951a7115555568c09ebad8a882f3d96de25429f0017fe570931e208a_theopennetwork_m446070_prices
|
||||
fileName = "nft_${userWalletStringId}_${networkStringId}_prices",
|
||||
types = mapWithCustomKeyTypes<NFTAsset.Identifier, NFTAsset.SalePrice>(),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun <T> createPersistenceStore(fileName: String, types: ParameterizedType, defaultValue: T): DataStore<T> =
|
||||
DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = types,
|
||||
defaultValue = defaultValue,
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = fileName) },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
)
|
||||
|
||||
private fun Network.ID.formatted(): String = value
|
||||
.filter(Char::isLetterOrDigit)
|
||||
.lowercase()
|
||||
|
||||
private fun Network.DerivationPath.formatted(): String? = value
|
||||
?.filter(Char::isLetterOrDigit)
|
||||
?.lowercase()
|
||||
|
||||
private fun UserWalletId.formatted(): String = stringValue
|
||||
.lowercase()
|
||||
}
|
||||
|
|
@ -7,14 +7,11 @@ import com.tangem.domain.tokens.model.Network
|
|||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.nft.models.NFTAsset as SdkNFTAsset
|
||||
|
||||
class NFTSdkAssetConverter(
|
||||
private val nftSdkAssetIdentifierConverter: NFTSdkAssetIdentifierConverter,
|
||||
private val nftSdkCollectionIdentifierConverter: NFTSdkCollectionIdentifierConverter,
|
||||
) : Converter<Pair<Network, SdkNFTAsset>, NFTAsset> {
|
||||
object NFTSdkAssetConverter : Converter<Pair<Network, SdkNFTAsset>, NFTAsset> {
|
||||
override fun convert(value: Pair<Network, SdkNFTAsset>): NFTAsset {
|
||||
val (network, asset) = value
|
||||
val assetId = nftSdkAssetIdentifierConverter.convert(asset.identifier)
|
||||
val collectionId = nftSdkCollectionIdentifierConverter.convert(asset.collectionIdentifier)
|
||||
val assetId = NFTSdkAssetIdentifierConverter.convert(asset.identifier)
|
||||
val collectionId = NFTSdkCollectionIdentifierConverter.convert(asset.collectionIdentifier)
|
||||
return NFTAsset.Value(
|
||||
id = assetId,
|
||||
collectionId = collectionId,
|
||||
|
|
|
|||
|
|
@ -6,13 +6,10 @@ import com.tangem.domain.tokens.model.Network
|
|||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.nft.models.NFTCollection as SdkNFTCollection
|
||||
|
||||
class NFTSdkCollectionConverter(
|
||||
private val nftSdkCollectionIdentifierConverter: NFTSdkCollectionIdentifierConverter,
|
||||
private val nftSdkAssetConverter: NFTSdkAssetConverter,
|
||||
) : Converter<Pair<Network, SdkNFTCollection>, NFTCollection> {
|
||||
object NFTSdkCollectionConverter : Converter<Pair<Network, SdkNFTCollection>, NFTCollection> {
|
||||
override fun convert(value: Pair<Network, SdkNFTCollection>): NFTCollection {
|
||||
val (network, collection) = value
|
||||
val collectionId = nftSdkCollectionIdentifierConverter.convert(collection.identifier)
|
||||
val collectionId = NFTSdkCollectionIdentifierConverter.convert(collection.identifier)
|
||||
return NFTCollection(
|
||||
id = collectionId,
|
||||
network = network,
|
||||
|
|
@ -22,7 +19,7 @@ class NFTSdkCollectionConverter(
|
|||
count = collection.count,
|
||||
assets = collection.assets
|
||||
.map { asset ->
|
||||
nftSdkAssetConverter.convert(network to asset)
|
||||
NFTSdkAssetConverter.convert(network to asset)
|
||||
}
|
||||
.filter {
|
||||
it.id !is NFTAsset.Identifier.Unknown
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.datasource.local.token.converter
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.PendingActionConstraints
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object PendingActionConstraintsConverter :
|
||||
Converter<BalanceDTO.PendingActionConstraints, PendingActionConstraints> {
|
||||
override fun convert(value: BalanceDTO.PendingActionConstraints): PendingActionConstraints {
|
||||
return PendingActionConstraints(
|
||||
type = StakingActionTypeConverter.convert(value.type),
|
||||
amountArg = PendingAction.PendingActionArgs.Amount(
|
||||
required = value.amountArg.required,
|
||||
minimum = value.amountArg.minimum,
|
||||
maximum = value.amountArg.maximum,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,8 @@ internal class YieldBalanceConverter(
|
|||
pendingActions = PendingActionConverter
|
||||
.convertList(item.pendingActions)
|
||||
.sortedBy { it.passthrough },
|
||||
pendingActionsConstraints = PendingActionConstraintsConverter
|
||||
.convertList(item.pendingActionConstraints.orEmpty()),
|
||||
isPending = false,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@
|
|||
<string name="common_fee_selector_option_market">Markt</string>
|
||||
<string name="common_fee_selector_option_slow">Langsam</string>
|
||||
<string name="common_fee_selector_title">Gebühren</string>
|
||||
<string name="common_finish">Beenden</string>
|
||||
<string name="common_generate_addresses">Adressen synchronisieren</string>
|
||||
<string name="common_go_to_provider">Zum Anbieter gehen</string>
|
||||
<string name="common_go_to_token">Zum Token</string>
|
||||
|
|
@ -158,6 +159,7 @@
|
|||
<string name="common_no_address">Keine Adresse</string>
|
||||
<string name="common_now">Jetzt</string>
|
||||
<string name="common_ok">OK</string>
|
||||
<string name="common_open_in_browser">Im Browser öffnen</string>
|
||||
<string name="common_origin_card">Hauptkarte oder Ring</string>
|
||||
<string name="common_origin_ring">Primärring</string>
|
||||
<string name="common_passphrase">Passphrase</string>
|
||||
|
|
@ -181,6 +183,7 @@
|
|||
<string name="common_send">Senden</string>
|
||||
<string name="common_server_unavailable">Der Server ist nicht verfügbar. Bitte versuche es später erneut.</string>
|
||||
<string name="common_share">Teilen</string>
|
||||
<string name="common_share_link">Link teilen</string>
|
||||
<string name="common_sign">Signieren</string>
|
||||
<string name="common_sign_and_send">Signieren und senden</string>
|
||||
<string name="common_stake">Staken</string>
|
||||
|
|
@ -495,6 +498,10 @@
|
|||
<string name="markets_tooltip_message">Rufe dies auf oder tippe auf die Suchleiste, um Token direkt vom Markt hinzuzufügen</string>
|
||||
<string name="markets_tooltip_title">Token hinzufügen</string>
|
||||
<string name="nfc_error_unavailable">NFC ist auf deinem Gerät nicht verfügbar</string>
|
||||
<string name="nft_wallet_count">%1$d NFTs in der %2$d Sammlung</string>
|
||||
<string name="nft_wallet_receive_nft">Tippe hier, um das erste NFT zu erhalten</string>
|
||||
<string name="nft_wallet_title">NFT-Sammlungen</string>
|
||||
<string name="nft_wallet_unable_to_load">Die Daten konnten nicht geladen werden</string>
|
||||
<string name="onboarding_access_code_feature_1_description">Du musst einen einzigen Zugangscode einrichten, um alle deine Geräte zu schützen</string>
|
||||
<string name="onboarding_access_code_feature_1_title">Schützen</string>
|
||||
<string name="onboarding_access_code_feature_2_description">Du kannst später auf jeder Karte oder Ring einen individuellen Zugangscode einrichten</string>
|
||||
|
|
@ -580,6 +587,7 @@
|
|||
<string name="onboarding_top_up_body_no_account_error">Um zu beginnen, lade die Wallet einfach mit mehr als %1$s %2$s auf.</string>
|
||||
<string name="onboarding_top_up_button_but_crypto">Krypto kaufen</string>
|
||||
<string name="onboarding_top_up_button_show_wallet_address">Adresse der Wallet anzeigen</string>
|
||||
<string name="onboarding_top_up_min_create_account_amount">Um zu beginnen, lade die Wallet einfach mit mehr als %1$s auf.</string>
|
||||
<string name="onboarding_topup_title">Aktivieren einer Wallet</string>
|
||||
<string name="onboarding_twin_exit_warning">Der Twinning-Prozess ist teilweise abgeschlossen. Du kannst ihn jetzt nicht mehr beenden.</string>
|
||||
<string name="onboarding_twins_interrupt_warning">Wenn der Prozess der Erstellung der Brieftasche in irgendeiner Weise unterbrochen wird, musst du diesen von vorne beginnen</string>
|
||||
|
|
@ -760,6 +768,8 @@
|
|||
<string name="send_summary_transaction_description_suffix_including">inklusive einer Netzgebühr von %1$s</string>
|
||||
<string name="send_transaction_success">Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert</string>
|
||||
<string name="send_tron_account_activation_error">%1$s ist ein Vermögenswert im Tron-Netzwerk. Um die Gebühr zu berechnen und eine Transaktion durchzuführen, musst du etwas Tron (TRX) auf deinem Konto einzahlen.</string>
|
||||
<string name="send_validation_destination_tag_required_description">Um diese Transaktion für die angegebene Adresse abzuschließen, ist ein Ziel-Tag (Memo) erforderlich.</string>
|
||||
<string name="send_validation_destination_tag_required_title">Ziel-Tag erforderlich</string>
|
||||
<string name="sent_transaction_sent_title">Transaktion gesendet</string>
|
||||
<string name="settings_card_settings_footer">Bereite das Scannen der Karte oder Ring vor, die du einrichten möchtest.</string>
|
||||
<string name="settings_forget_wallet">Entferne diese Wallet</string>
|
||||
|
|
@ -857,6 +867,7 @@
|
|||
<string name="staking_rewards">Belohnungen</string>
|
||||
<string name="staking_stake_locked">Stake gesperrt</string>
|
||||
<string name="staking_stake_more">Mehr staken</string>
|
||||
<string name="staking_stake_more_button_unavailability_reason">Beim Staking %1$s wird Dein gesamtes %2$s -Guthaben eingesetzt. Alle weiteren %2$s due Du in Deine Tangem-Wallet einzahlst, werden ebenfalls automatisch eingesetzt.</string>
|
||||
<string name="staking_staked_amount">Gestakeder Betrag</string>
|
||||
<string name="staking_summary_description_text">Du setzt %1$s ein und ethältst Deine Belohnung %2$s</string>
|
||||
<string name="staking_tap_to_unlock">Zum Entsperren antippen</string>
|
||||
|
|
@ -931,7 +942,7 @@
|
|||
<string name="token_button_unavailability_reason_custom_token">Die Aktion ist für benutzerdefinierte Token nicht verfügbar.</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_sell">Du hast kein Guthaben um dies zu Verkaufen. Lade dein Konto auf, um Geld von dort zu verkaufen.</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_send">Du hast kein Guthaben zum Versenden. Lade dein Konto auf, um Geld von dort aus senden zu können.</string>
|
||||
<string name="token_button_unavailability_reason_loading">Die Daten wurden noch nicht geladen. Dies kann einige Sekunden dauern. Bitte versuche es später erneut.</string>
|
||||
<string name="token_button_unavailability_reason_loading">Die Daten werden gerade geladen. Dies kann einige Sekunden dauern. Bitte versuche es später noch einmal.</string>
|
||||
<string name="token_button_unavailability_reason_not_exchangeable">Das Tauschen von %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen.</string>
|
||||
<string name="token_button_unavailability_reason_out_of_date_balance">Der angezeigte Kontostand kann aufgrund der Zwischenspeicherung veraltet sein.</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_sell">Der Verkauf von Geldmitteln ist möglich, sobald die ausstehende(n) Transaktion(en) im %s -Netzwerk abgeschlossen ist/sind.</string>
|
||||
|
|
@ -995,6 +1006,24 @@
|
|||
<string name="user_wallet_list_rename_popup_title">Wallet umbenennen</string>
|
||||
<string name="user_wallet_list_unlock_all">Alle freischalten</string>
|
||||
<string name="user_wallet_list_unlock_all_with">Alle mit %s freischalten</string>
|
||||
<plurals name="visa_limits_available_for_days_title">
|
||||
<item quantity="one">Verfügbar für %d Tag</item>
|
||||
<item quantity="other">Verfügbar für %d Tage</item>
|
||||
</plurals>
|
||||
<string name="visa_main_balances_and_limits">Guthaben und Limits</string>
|
||||
<string name="visa_onboarding_approve_wallet_selector_notification_message">Bitte wähle die Wallet aus, mit der Du den Registrierungsprozess gestartet hast, um die Transaktion zur Erstellung Deines Kontos auf der Blockchain zu signieren</string>
|
||||
<string name="visa_onboarding_close_alert_message">Möchtest Du den Vorgang wirklich beenden? Du kannst später dort fortfahren, wo Du aufgehört hast.</string>
|
||||
<string name="visa_onboarding_in_progress_description">Das wird nicht lange dauern. Wir richten gerade Dein Konto ein.</string>
|
||||
<string name="visa_onboarding_in_progress_issuer_description">Das wird nicht lange dauern. Wir schließen die Aktivierung ab.</string>
|
||||
<string name="visa_onboarding_in_progress_title">Alles wird vorbereitet!</string>
|
||||
<string name="visa_onboarding_pin_validation_error_message">Ungültige PIN: Sequenzen oder Wiederholungen vermeiden</string>
|
||||
<string name="visa_onboarding_success_screen_description">Schon kann es losgehen!</string>
|
||||
<string name="visa_onboarding_wallet_connect_description">Du kannst Deine Verbindung \n auf der Website des Drittanbieters \n herstellen und zurück zur Tangem-App gelangen</string>
|
||||
<string name="visa_onboarding_wallet_connect_title">Zur Website gehen</string>
|
||||
<string name="visa_onboarding_welcome_back_description">Fahren wir mit der Einrichtung Deines Kontos fort.</string>
|
||||
<string name="visa_onboarding_welcome_back_title">Willkommen zurück!</string>
|
||||
<string name="visa_onboarding_welcome_description">Folge den Schritten, um Dein Konto einzurichten.</string>
|
||||
<string name="visa_onboarding_welcome_title">Willkommen!</string>
|
||||
<string name="visa_unlock_notification_button">Freischalten</string>
|
||||
<string name="visa_unlock_notification_subtitle">Scanne Deine Karte, um den Zugriff freizuschalten</string>
|
||||
<string name="visa_unlock_notification_title">Benötigte Freischaltung</string>
|
||||
|
|
|
|||
|
|
@ -578,6 +578,7 @@
|
|||
<string name="onboarding_top_up_body_no_account_error">Para empezar, simplemente recargue la billetera con más de %1$s %2$s</string>
|
||||
<string name="onboarding_top_up_button_but_crypto">Comprar cripto</string>
|
||||
<string name="onboarding_top_up_button_show_wallet_address">Mostrar la dirección de la billetera</string>
|
||||
<string name="onboarding_top_up_min_create_account_amount">Para empezar, simplemente recargue la billetera con más de %1$s</string>
|
||||
<string name="onboarding_topup_title">Activar una billetera</string>
|
||||
<string name="onboarding_twin_exit_warning">El proceso de emparejamiento está parcialmente completo. No puede salir ahora.</string>
|
||||
<string name="onboarding_twins_interrupt_warning">Si el proceso de creación del wallet se interrumpe de alguna manera, tendrá que empezar de nuevo</string>
|
||||
|
|
|
|||
|
|
@ -580,6 +580,7 @@
|
|||
<string name="onboarding_top_up_body_no_account_error">Pour commencer, rechargez simplement le portefeuille avec plus de %1$s %2$s</string>
|
||||
<string name="onboarding_top_up_button_but_crypto">Acheter des crypto-monnaies</string>
|
||||
<string name="onboarding_top_up_button_show_wallet_address">Afficher l\'adresse du portefeuille</string>
|
||||
<string name="onboarding_top_up_min_create_account_amount">Pour commencer, rechargez simplement le portefeuille avec plus de %1$s</string>
|
||||
<string name="onboarding_topup_title">Activer un portefeuille</string>
|
||||
<string name="onboarding_twin_exit_warning">Le processus de jumelage est partiellement terminé. Vous ne pouvez pas le quitter maintenant.</string>
|
||||
<string name="onboarding_twins_interrupt_warning">Si le processus de création du portefeuille est interrompu de quelque manière que ce soit, vous devrez recommencer</string>
|
||||
|
|
@ -1038,7 +1039,7 @@
|
|||
<string name="warning_beacon_chain_retirement_title">BNB Beacon Chain va s\'arrêter de fonctionner</string>
|
||||
<string name="warning_blocked_funds_for_fee_message">Veuillez déposer environ %1$s pour couvrir les frais de réseau</string>
|
||||
<string name="warning_blocked_funds_for_fee_title">Fonds insuffisants pour couvrir les frais de réseau</string>
|
||||
<string name="warning_button_could_be_better">Pas terrible</string>
|
||||
<string name="warning_button_could_be_better">Peut mieux faire</string>
|
||||
<string name="warning_button_like_it">J\'aime</string>
|
||||
<string name="warning_button_ok">Ok, compris!</string>
|
||||
<string name="warning_button_really_cool">Vraiment cool !</string>
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@
|
|||
<string name="common_fee_selector_option_market">マーケット</string>
|
||||
<string name="common_fee_selector_option_slow">遅い</string>
|
||||
<string name="common_fee_selector_title">速度と料金</string>
|
||||
<string name="common_finish">終了</string>
|
||||
<string name="common_generate_addresses">アドレスを同期する</string>
|
||||
<string name="common_go_to_provider">プロバイダーへ移動</string>
|
||||
<string name="common_go_to_token">トークンへ移動</string>
|
||||
|
|
@ -155,6 +156,7 @@
|
|||
<string name="common_no_address">アドレスがありません</string>
|
||||
<string name="common_now">今</string>
|
||||
<string name="common_ok">わかりました</string>
|
||||
<string name="common_open_in_browser">ブラウザで開く</string>
|
||||
<string name="common_origin_card">プライマリーカード</string>
|
||||
<string name="common_origin_ring">プライマリーリング</string>
|
||||
<string name="common_passphrase">パスフレーズ</string>
|
||||
|
|
@ -178,6 +180,7 @@
|
|||
<string name="common_send">送る</string>
|
||||
<string name="common_server_unavailable">サーバーが利用できません。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="common_share">共有</string>
|
||||
<string name="common_share_link">リンクを共有</string>
|
||||
<string name="common_sign">署名</string>
|
||||
<string name="common_sign_and_send">署名して送信</string>
|
||||
<string name="common_stake">ステーキング</string>
|
||||
|
|
@ -489,6 +492,19 @@
|
|||
<string name="markets_tooltip_message">これをドラッグするか、検索窓をタップして、マーケットから直接トークンを追加します</string>
|
||||
<string name="markets_tooltip_title">トークンを追加</string>
|
||||
<string name="nfc_error_unavailable">お使いのデバイスではNFCが使用できません</string>
|
||||
<plurals name="nft_collections_count">
|
||||
<item quantity="other">アイテム</item>
|
||||
</plurals>
|
||||
<string name="nft_collections_empty_description">ウォレットに送信されたNFTがここに表示されます。</string>
|
||||
<string name="nft_collections_empty_title">コレクションはまだありません</string>
|
||||
<string name="nft_collections_receive">NFTを受け取る</string>
|
||||
<string name="nft_collections_title">NFTコレクション</string>
|
||||
<string name="nft_collections_warning_subtitle">一部のデータが読み込まれない場合があります</string>
|
||||
<string name="nft_collections_warning_title">一時的な読み込みの問題</string>
|
||||
<string name="nft_wallet_count">%1$dコレクションの%2$dNFT</string>
|
||||
<string name="nft_wallet_receive_nft">ここをタップして最初のNFTを受け取ります</string>
|
||||
<string name="nft_wallet_title">NFTコレクション</string>
|
||||
<string name="nft_wallet_unable_to_load">データを読み込めません</string>
|
||||
<string name="onboarding_access_code_feature_1_description">すべてのデバイスを保護するには、単一のアクセスコードを設定してください。</string>
|
||||
<string name="onboarding_access_code_feature_1_title">保護する</string>
|
||||
<string name="onboarding_access_code_feature_2_description">後で各カードおよびリングに個別のアクセスコードを設定できます。</string>
|
||||
|
|
@ -572,6 +588,7 @@
|
|||
<string name="onboarding_top_up_body_no_account_error">始めるには、ウォレットに%1$s %2$s以上入金するだけです</string>
|
||||
<string name="onboarding_top_up_button_but_crypto">暗号資産を購入する</string>
|
||||
<string name="onboarding_top_up_button_show_wallet_address">ウォレットのアドレスを表示する</string>
|
||||
<string name="onboarding_top_up_min_create_account_amount">始めるには、ウォレットに%1$s 以上入金するだけです</string>
|
||||
<string name="onboarding_topup_title">ウォレットを有効化する</string>
|
||||
<string name="onboarding_twin_exit_warning">ツイン化プロセスは部分的に完了しています。現在終了することはできません。</string>
|
||||
<string name="onboarding_twins_interrupt_warning">ウォレットの作成プロセスが何らかの理由で中断された場合は、最初からやり直す必要があります。</string>
|
||||
|
|
@ -750,6 +767,8 @@
|
|||
<string name="send_summary_transaction_description_suffix_including">ネットワーク手数料%1$sを含む</string>
|
||||
<string name="send_transaction_success">取引は正常に署名され、ブロックチェーンノードに送信されました。ウォレットの残高はしばらくして更新されます。</string>
|
||||
<string name="send_tron_account_activation_error">%1$sはTronネットワークのアセットです。手数料を計算して取引を行うには、アカウントにTron(TRX)を入金する必要があります。</string>
|
||||
<string name="send_validation_destination_tag_required_description">指定されたアドレスへの取引を完了するには、宛先タグ(メモ)が必要です。</string>
|
||||
<string name="send_validation_destination_tag_required_title">宛先タグが必要です</string>
|
||||
<string name="sent_transaction_sent_title">取引が送信されました</string>
|
||||
<string name="settings_card_settings_footer">設定したいカードまたはリングをスキャンするために準備してください。</string>
|
||||
<string name="settings_forget_wallet">ウォレット削除</string>
|
||||
|
|
@ -847,6 +866,7 @@
|
|||
<string name="staking_rewards">報酬</string>
|
||||
<string name="staking_stake_locked">ステーキングはロックされています</string>
|
||||
<string name="staking_stake_more">もっとステーキングする</string>
|
||||
<string name="staking_stake_more_button_unavailability_reason">%1$sをステーキングすると、 %2$s残高全体がステーキングされます。Tangemウォレットに入金した追加の%2$sも、自動的にステーキングされます。</string>
|
||||
<string name="staking_staked_amount">ステーキング中の金額</string>
|
||||
<string name="staking_summary_description_text">%1$sをステーキングし、報酬%2$sを受け取ります</string>
|
||||
<string name="staking_tap_to_unlock">タップしてロック解除</string>
|
||||
|
|
@ -985,6 +1005,20 @@
|
|||
<string name="user_wallet_list_rename_popup_title">ウォレット名の変更</string>
|
||||
<string name="user_wallet_list_unlock_all">すべてのロックを解除</string>
|
||||
<string name="user_wallet_list_unlock_all_with">%sですべてをロック解除</string>
|
||||
<plurals name="visa_limits_available_for_days_title">
|
||||
<item quantity="other">%d日間利用可能</item>
|
||||
</plurals>
|
||||
<string name="visa_main_balances_and_limits">残高と限度額</string>
|
||||
<string name="visa_onboarding_close_alert_message">本当に終了してもよろしいですか?中断したところから後で続行できます。</string>
|
||||
<string name="visa_onboarding_in_progress_description">長くはかかりません。アカウントを設定しています。</string>
|
||||
<string name="visa_onboarding_in_progress_issuer_description">長くはかかりません。アクティベーションを完了しています。</string>
|
||||
<string name="visa_onboarding_in_progress_title">準備完了です!</string>
|
||||
<string name="visa_onboarding_pin_validation_error_message">無効な暗証番号:連続や繰り返しを避けてください</string>
|
||||
<string name="visa_onboarding_wallet_connect_title">ウェブサイトに移動</string>
|
||||
<string name="visa_onboarding_welcome_back_description">アカウントの設定を続けましょう。</string>
|
||||
<string name="visa_onboarding_welcome_back_title">お帰りなさい!</string>
|
||||
<string name="visa_onboarding_welcome_description">手順に従ってアカウントを設定してください。</string>
|
||||
<string name="visa_onboarding_welcome_title">ようこそ!</string>
|
||||
<string name="visa_unlock_notification_button">ロック解除</string>
|
||||
<string name="visa_unlock_notification_subtitle">カードをスキャンしてアクセスロックを解除する</string>
|
||||
<string name="visa_unlock_notification_title">ロック解除が必要</string>
|
||||
|
|
|
|||
|
|
@ -594,6 +594,7 @@
|
|||
<string name="onboarding_top_up_body_no_account_error">Пополните кошелек более чем на %1$s %2$s, чтобы начать пользоваться картой</string>
|
||||
<string name="onboarding_top_up_button_but_crypto">Купить криптовалюту</string>
|
||||
<string name="onboarding_top_up_button_show_wallet_address">Показать адрес кошелька</string>
|
||||
<string name="onboarding_top_up_min_create_account_amount">Пополните кошелек более чем на %1$s, чтобы начать пользоваться картой</string>
|
||||
<string name="onboarding_topup_title">Активация кошелька</string>
|
||||
<string name="onboarding_twin_exit_warning">Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас.</string>
|
||||
<string name="onboarding_twins_interrupt_warning">Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала</string>
|
||||
|
|
|
|||
|
|
@ -594,6 +594,7 @@
|
|||
<string name="onboarding_top_up_body_no_account_error">Щоб почати, просто поповніть гаманець більше, ніж на %1$s %2$s</string>
|
||||
<string name="onboarding_top_up_button_but_crypto">Купити криптовалюту</string>
|
||||
<string name="onboarding_top_up_button_show_wallet_address">Показати адресу гаманця</string>
|
||||
<string name="onboarding_top_up_min_create_account_amount">Щоб почати, просто поповніть гаманець більше, ніж на %1$s</string>
|
||||
<string name="onboarding_topup_title">Активація гаманця</string>
|
||||
<string name="onboarding_twin_exit_warning">Процес поєднання карток частково завершено. Ви не можете вийти з нього зараз.</string>
|
||||
<string name="onboarding_twins_interrupt_warning">Якщо процес створення гаманця буде перервано будь-яким чином, вам доведеться почати все спочатку</string>
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@
|
|||
<string name="onboarding_top_up_body_no_account_error">要開始,只需為錢包充值超過 %1$s %2$s</string>
|
||||
<string name="onboarding_top_up_button_but_crypto">購買加密貨幣</string>
|
||||
<string name="onboarding_top_up_button_show_wallet_address">顯示錢包地址</string>
|
||||
<string name="onboarding_top_up_min_create_account_amount">要開始,只需為錢包充值超過 %1$s</string>
|
||||
<string name="onboarding_topup_title">啟動錢包</string>
|
||||
<string name="onboarding_twin_exit_warning">結對過程已部分完成。你現在不能退出</string>
|
||||
<string name="onboarding_twins_interrupt_warning">如果創建錢包的過程以任何方式中斷,您將得重新開始</string>
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@
|
|||
<string name="common_fee_selector_option_market">Market</string>
|
||||
<string name="common_fee_selector_option_slow">Slow</string>
|
||||
<string name="common_fee_selector_title">Speed and fee</string>
|
||||
<string name="common_finish">Finish</string>
|
||||
<string name="common_generate_addresses">Synchronize addresses</string>
|
||||
<string name="common_go_to_provider">Go to provider</string>
|
||||
<string name="common_go_to_token">Go to token</string>
|
||||
|
|
@ -158,6 +159,7 @@
|
|||
<string name="common_no_address">No address</string>
|
||||
<string name="common_now">Now</string>
|
||||
<string name="common_ok">OK</string>
|
||||
<string name="common_open_in_browser">Open in Browser</string>
|
||||
<string name="common_origin_card">Primary card</string>
|
||||
<string name="common_origin_ring">Primary ring</string>
|
||||
<string name="common_passphrase">Passphrase</string>
|
||||
|
|
@ -181,6 +183,7 @@
|
|||
<string name="common_send">Send</string>
|
||||
<string name="common_server_unavailable">The server is not available, please try again later</string>
|
||||
<string name="common_share">Share</string>
|
||||
<string name="common_share_link">Share Link</string>
|
||||
<string name="common_sign">Sign</string>
|
||||
<string name="common_sign_and_send">Sign and send</string>
|
||||
<string name="common_stake">Stake</string>
|
||||
|
|
@ -190,6 +193,7 @@
|
|||
<string name="common_success">Success</string>
|
||||
<string name="common_support">Support</string>
|
||||
<string name="common_swap">Swap</string>
|
||||
<string name="common_tangem_wallet">Tangem Wallet</string>
|
||||
<string name="common_terms_and_conditions">terms and conditions</string>
|
||||
<string name="common_terms_of_use">Terms of Use</string>
|
||||
<string name="common_today">Today</string>
|
||||
|
|
@ -495,9 +499,19 @@
|
|||
<string name="markets_tooltip_message">Pull this up or tap the search bar to add tokens directly from the market</string>
|
||||
<string name="markets_tooltip_title">Add tokens</string>
|
||||
<string name="nfc_error_unavailable">NFC is not available on your device</string>
|
||||
<string name="nft_wallet_title">NFT collections</string>
|
||||
<string name="nft_wallet_receive_nft">Tap here to receive first NFT</string>
|
||||
<plurals name="nft_collections_count">
|
||||
<item quantity="one">%d item</item>
|
||||
<item quantity="other">%d items</item>
|
||||
</plurals>
|
||||
<string name="nft_collections_empty_description">NFTs sent to your wallet address will show up here.</string>
|
||||
<string name="nft_collections_empty_title">No collections, yet</string>
|
||||
<string name="nft_collections_receive">Receive NFT</string>
|
||||
<string name="nft_collections_title">NFT collections</string>
|
||||
<string name="nft_collections_warning_subtitle">Some data may not load</string>
|
||||
<string name="nft_collections_warning_title">Temporary loading problems</string>
|
||||
<string name="nft_wallet_count">%1$d NFTs in %2$d collection</string>
|
||||
<string name="nft_wallet_receive_nft">Tap here to receive first NFT</string>
|
||||
<string name="nft_wallet_title">NFT collections</string>
|
||||
<string name="nft_wallet_unable_to_load">Unable to load the data</string>
|
||||
<string name="onboarding_access_code_feature_1_description">Set up a single access code to protect all your devices.</string>
|
||||
<string name="onboarding_access_code_feature_1_title">Protect</string>
|
||||
|
|
@ -584,6 +598,7 @@
|
|||
<string name="onboarding_top_up_body_no_account_error">To get started, simply top up the wallet with more than %1$s %2$s</string>
|
||||
<string name="onboarding_top_up_button_but_crypto">Buy crypto</string>
|
||||
<string name="onboarding_top_up_button_show_wallet_address">Show the wallet\'s address</string>
|
||||
<string name="onboarding_top_up_min_create_account_amount">To get started, simply top up the wallet with more than %1$s</string>
|
||||
<string name="onboarding_topup_title">Activate a wallet</string>
|
||||
<string name="onboarding_twin_exit_warning">The twinning process is partly complete. You can\'t exit it now.</string>
|
||||
<string name="onboarding_twins_interrupt_warning">If the process of creating the wallet gets interrupted in any way, you\'ll have to start over</string>
|
||||
|
|
@ -764,6 +779,8 @@
|
|||
<string name="send_summary_transaction_description_suffix_including">including a network fee of %1$s</string>
|
||||
<string name="send_transaction_success">Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while</string>
|
||||
<string name="send_tron_account_activation_error">%1$s is an asset in the Tron network. To calculate the fee and make a transaction you need to deposit some Tron (TRX) in your account.</string>
|
||||
<string name="send_validation_destination_tag_required_description">A destination tag (memo) is required to complete this transaction for the specified address.</string>
|
||||
<string name="send_validation_destination_tag_required_title">Destination Tag Required</string>
|
||||
<string name="sent_transaction_sent_title">Transaction sent</string>
|
||||
<string name="settings_card_settings_footer">Prepare to scan card or ring you want to set up.</string>
|
||||
<string name="settings_forget_wallet">Forget wallet</string>
|
||||
|
|
@ -1000,6 +1017,69 @@
|
|||
<string name="user_wallet_list_rename_popup_title">Rename wallet</string>
|
||||
<string name="user_wallet_list_unlock_all">Unlock all</string>
|
||||
<string name="user_wallet_list_unlock_all_with">Unlock all with %s</string>
|
||||
<string name="visa_balance_limits_details_aml_verified">AML verifired</string>
|
||||
<string name="visa_balance_limits_details_available">Available</string>
|
||||
<string name="visa_balance_limits_details_blocked">Blocked</string>
|
||||
<string name="visa_balance_limits_details_debt">Debt</string>
|
||||
<string name="visa_balance_limits_details_limits">Limits</string>
|
||||
<string name="visa_balance_limits_details_no_otp_limit">Other (no-otp)</string>
|
||||
<string name="visa_balance_limits_details_single_transaction">Single transaction</string>
|
||||
<string name="visa_balance_limits_details_total">Total</string>
|
||||
<plurals name="visa_limits_available_for_days_title">
|
||||
<item quantity="one">available for %d day</item>
|
||||
<item quantity="other">available for %d days</item>
|
||||
</plurals>
|
||||
<string name="visa_main_available_balance_alert_message">Available balance is actual funds available, considering pending transactions, blocked amounts, and debit balance to prevent overdrafts.</string>
|
||||
<string name="visa_main_available_till_date">Available till %1$s</string>
|
||||
<string name="visa_main_balances_and_limits">Balances & Limits</string>
|
||||
<string name="visa_main_button_alert_missing_payment_account_info_message">Missing Payment account information. Please contact support</string>
|
||||
<string name="visa_main_button_alert_missing_token_info">Unable to load information about token</string>
|
||||
<string name="visa_main_button_alert_still_loading">Information is still loading, please wait</string>
|
||||
<string name="visa_main_limits_alert_description_message">Limits are needed to control costs, improve security, manage risk. You can spend %1$s during the period for card payments in shops and %2$s for other transactions, e.g. subscriptions or debts.</string>
|
||||
<string name="visa_onboarding_access_code_description">The access code will be used manage your payment account and protect it from unauthorized access</string>
|
||||
<string name="visa_onboarding_access_code_navigation_title">Access Code</string>
|
||||
<string name="visa_onboarding_account_activation_navigation_title">Account activation</string>
|
||||
<string name="visa_onboarding_approve_wallet_selector_notification_message">Please choose the wallet you started the registration process with to sign the transaction for creating your account on the Blockchain</string>
|
||||
<string name="visa_onboarding_cancel_activation">Cancel Activation</string>
|
||||
<string name="visa_onboarding_close_alert_message">Are you sure you want to exit? You can continue later from where you left off.</string>
|
||||
<string name="visa_onboarding_in_progress_description">This won\'t take long. We\'re setting up your account.</string>
|
||||
<string name="visa_onboarding_in_progress_issuer_description">This won’t take long. We’re completing the activation.</string>
|
||||
<string name="visa_onboarding_in_progress_title">Getting everything ready!</string>
|
||||
<string name="visa_onboarding_other_wallet">Other wallet</string>
|
||||
<string name="visa_onboarding_pin_code_description">Set up a 4-digit code.
It will be used for payments.</string>
|
||||
<string name="visa_onboarding_pin_code_navigation_title">PIN code</string>
|
||||
<string name="visa_onboarding_pin_code_title">Create PIN Code</string>
|
||||
<string name="visa_onboarding_pin_validation_error_message">Invalid PIN: avoid sequences or repeats</string>
|
||||
<string name="visa_onboarding_success_screen_description">You\'re good to go!</string>
|
||||
<string name="visa_onboarding_tangem_approve_description">Prepare the Tangem card and tap to approve</string>
|
||||
<string name="visa_onboarding_tangem_approve_title">Prepare Tangem Wallet</string>
|
||||
<string name="visa_onboarding_wallet_connect_description">You will be able to complete your connection \n on the third-party web-site \n and back to the Tangem app</string>
|
||||
<string name="visa_onboarding_wallet_connect_title">Go to Website</string>
|
||||
<string name="visa_onboarding_wallet_connection_navigation_title">Wallet connection</string>
|
||||
<string name="visa_onboarding_wallet_list_header">Choose wallet</string>
|
||||
<string name="visa_onboarding_welcome_back_button_title">Continue Activation</string>
|
||||
<string name="visa_onboarding_welcome_back_description">Let\'s continue setting up your account.</string>
|
||||
<string name="visa_onboarding_welcome_back_title">Welcome back!</string>
|
||||
<string name="visa_onboarding_welcome_button_title">Start Activation</string>
|
||||
<string name="visa_onboarding_welcome_description">Follow the steps to set up your account.</string>
|
||||
<string name="visa_onboarding_welcome_title">Welcome!</string>
|
||||
<string name="visa_transaction_details_blockchain_amount">Blockchain amount</string>
|
||||
<string name="visa_transaction_details_currency_code">Currency code</string>
|
||||
<string name="visa_transaction_details_date">Date</string>
|
||||
<string name="visa_transaction_details_error_code">Error code</string>
|
||||
<string name="visa_transaction_details_header">Transaction details</string>
|
||||
<string name="visa_transaction_details_merchant_category_code">Merchant category code</string>
|
||||
<string name="visa_transaction_details_merchant_city">Merchant city</string>
|
||||
<string name="visa_transaction_details_merchant_country_code">Merchant country code</string>
|
||||
<string name="visa_transaction_details_merchant_name">Merchant name</string>
|
||||
<string name="visa_transaction_details_request_id">Request ID</string>
|
||||
<string name="visa_transaction_details_status">Status</string>
|
||||
<string name="visa_transaction_details_title">Transaction</string>
|
||||
<string name="visa_transaction_details_transaction_amount">Transaction amount</string>
|
||||
<string name="visa_transaction_details_transaction_hash">Transaction hash</string>
|
||||
<string name="visa_transaction_details_transaction_request">Transaction request</string>
|
||||
<string name="visa_transaction_details_transaction_status">Transaction status</string>
|
||||
<string name="visa_transaction_details_type">Type</string>
|
||||
<string name="visa_unlock_notification_button">Unlock</string>
|
||||
<string name="visa_unlock_notification_subtitle">Scan your card to unlock access</string>
|
||||
<string name="visa_unlock_notification_title">Needed unlock</string>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ fun Modifier.roundedShapeItemDecoration(
|
|||
val applyShape: Modifier.(shape: RoundedCornerShape?) -> Modifier = { shape ->
|
||||
if (backgroundColor != null) {
|
||||
if (shape != null) {
|
||||
background(color = backgroundColor, shape = shape)
|
||||
clip(shape).background(color = backgroundColor, shape = shape)
|
||||
} else {
|
||||
background(color = backgroundColor)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
package com.tangem.utils.transformer
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
/**
|
||||
* Transforms state to updated state.
|
||||
*/
|
||||
interface Transformer<S> {
|
||||
fun transform(prevState: S): S
|
||||
}
|
||||
|
||||
fun <T> MutableStateFlow<T>.update(transformer: Transformer<T>) {
|
||||
update { transformer.transform(this.value) }
|
||||
}
|
||||
|
|
@ -335,7 +335,7 @@ internal class DefaultMarketsTokenRepository(
|
|||
): T {
|
||||
return when (error) {
|
||||
is ApiResponseError.HttpException -> {
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Http, error.code.code, error.message.orEmpty())
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Http, error.code.numericCode, error.message.orEmpty())
|
||||
}
|
||||
is ApiResponseError.TimeoutException -> {
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Timeout, null, error.message.orEmpty())
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
package com.tangem.data.nft
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.datasource.local.nft.NFTPersistenceStore
|
||||
import com.tangem.datasource.local.nft.NFTPersistenceStoreFactory
|
||||
import com.tangem.datasource.local.nft.NFTRuntimeStore
|
||||
import com.tangem.datasource.local.nft.NFTRuntimeStoreFactory
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkAssetConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkAssetIdentifierConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionConverter
|
||||
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionIdentifierConverter
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.nft.models.NFTCollection
|
||||
import com.tangem.domain.nft.models.NFTCollections
|
||||
|
|
@ -20,8 +20,10 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import com.tangem.blockchain.nft.models.NFTCollection as SdkNFTCollection
|
||||
|
||||
|
|
@ -34,82 +36,82 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
|
||||
private val jobs = mutableMapOf<Network, JobHolder>()
|
||||
|
||||
private val nftRuntimeStores = mutableMapOf<Network, NFTRuntimeStore>()
|
||||
private val nftPersistenceStores = mutableMapOf<Network, NFTPersistenceStore>()
|
||||
|
||||
private val nftSdkAssetConverter = NFTSdkAssetConverter(
|
||||
nftSdkAssetIdentifierConverter = NFTSdkAssetIdentifierConverter,
|
||||
nftSdkCollectionIdentifierConverter = NFTSdkCollectionIdentifierConverter,
|
||||
)
|
||||
private val collectionConverter = NFTSdkCollectionConverter(
|
||||
nftSdkCollectionIdentifierConverter = NFTSdkCollectionIdentifierConverter,
|
||||
nftSdkAssetConverter = nftSdkAssetConverter,
|
||||
)
|
||||
private val nftRuntimeStores = mutableMapOf<String, NFTRuntimeStore>()
|
||||
private val nftPersistenceStores = mutableMapOf<String, NFTPersistenceStore>()
|
||||
|
||||
override fun observeCollections(userWalletId: UserWalletId, networks: List<Network>): Flow<List<NFTCollections>> =
|
||||
flow {
|
||||
emitAll(
|
||||
combine(
|
||||
networks.map { getNFTRuntimeStore(it).getCollections() },
|
||||
) { it.asList() },
|
||||
)
|
||||
}.onStart {
|
||||
refreshCollections(userWalletId, networks)
|
||||
}
|
||||
flow { emitAll(observeCollectionsInternal(userWalletId, networks)) }
|
||||
|
||||
private suspend fun observeCollectionsInternal(
|
||||
userWalletId: UserWalletId,
|
||||
networks: List<Network>,
|
||||
): Flow<List<NFTCollections>> = combine(
|
||||
networks.map { observeCollectionsInternal(userWalletId, it) },
|
||||
) { it.asList() }
|
||||
|
||||
private suspend fun observeCollectionsInternal(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Flow<NFTCollections> = if (network.canHandleNFTs()) {
|
||||
getNFTRuntimeStore(userWalletId, network).getCollections()
|
||||
} else {
|
||||
flowOf(NFTCollections.empty(network))
|
||||
}
|
||||
|
||||
override suspend fun refreshCollections(userWalletId: UserWalletId, networks: List<Network>) = coroutineScope {
|
||||
networks.map { network ->
|
||||
launch(dispatchers.io) {
|
||||
Either.catch {
|
||||
expireCollections(network)
|
||||
walletManagersFacade.getNFTCollections(userWalletId, network)
|
||||
}.onLeft {
|
||||
saveFailedStateInRuntime(
|
||||
network = network,
|
||||
error = it,
|
||||
)
|
||||
}.onRight {
|
||||
val mergedCollections = it.mergeWithStoredAssets(network)
|
||||
networks.mapNotNull { network ->
|
||||
if (network.canHandleNFTs()) {
|
||||
launch(dispatchers.io) {
|
||||
Either.catch {
|
||||
expireCollections(userWalletId, network)
|
||||
walletManagersFacade.getNFTCollections(userWalletId, network)
|
||||
}.onLeft {
|
||||
saveFailedStateInRuntime(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
error = it,
|
||||
)
|
||||
}.onRight {
|
||||
val mergedCollections = it.mergeWithStoredAssets(userWalletId, network)
|
||||
|
||||
saveCollectionsInRuntime(
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
saveCollectionsInPersistence(
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
}
|
||||
}.saveIn(getJobHolder(network))
|
||||
saveCollectionsInRuntime(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
saveCollectionsInPersistence(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
collections = mergedCollections,
|
||||
)
|
||||
}
|
||||
}.saveIn(getJobHolder(network))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.joinAll()
|
||||
}
|
||||
|
||||
private suspend fun expireCollections(network: Network) {
|
||||
val runtimeStore = getNFTRuntimeStore(network)
|
||||
|
||||
private suspend fun expireCollections(userWalletId: UserWalletId, network: Network) {
|
||||
val runtimeStore = getNFTRuntimeStore(userWalletId, network)
|
||||
val expiredCollections = runtimeStore
|
||||
.getCollectionsSync()
|
||||
.let { collections ->
|
||||
collections.copy(
|
||||
content = when (val content = collections.content) {
|
||||
is NFTCollections.Content.Collections -> content.copy(
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
is NFTCollections.Content.Error -> content
|
||||
},
|
||||
)
|
||||
}
|
||||
.changeStatusSource(StatusSource.CACHE)
|
||||
runtimeStore.saveCollections(expiredCollections)
|
||||
}
|
||||
|
||||
private suspend fun saveCollectionsInRuntime(network: Network, collections: List<SdkNFTCollection>) {
|
||||
getNFTRuntimeStore(network).saveCollections(
|
||||
private suspend fun saveCollectionsInRuntime(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collections: List<SdkNFTCollection>,
|
||||
) {
|
||||
getNFTRuntimeStore(userWalletId, network).saveCollections(
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Collections(
|
||||
collections = collections
|
||||
.map { collection ->
|
||||
collectionConverter.convert(network to collection)
|
||||
NFTSdkCollectionConverter.convert(network to collection)
|
||||
}
|
||||
.filter {
|
||||
it.id !is NFTCollection.Identifier.Unknown
|
||||
|
|
@ -120,19 +122,30 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun saveFailedStateInRuntime(network: Network, error: Throwable) {
|
||||
getNFTRuntimeStore(network).saveCollections(
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Error(
|
||||
error = error,
|
||||
),
|
||||
),
|
||||
)
|
||||
private suspend fun saveFailedStateInRuntime(userWalletId: UserWalletId, network: Network, error: Throwable) {
|
||||
getNFTRuntimeStore(userWalletId, network).let { store ->
|
||||
val storedCollections = store.getCollectionsSync()
|
||||
val content = storedCollections.content
|
||||
val updatedCollections = if (content is NFTCollections.Content.Collections && content.collections != null) {
|
||||
// if there is any cached collections in store, then mark them as not actual and emit anyway
|
||||
storedCollections.changeStatusSource(StatusSource.ONLY_CACHE)
|
||||
} else {
|
||||
// otherwise, just emit an error
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Error(error),
|
||||
)
|
||||
}
|
||||
store.saveCollections(updatedCollections)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveCollectionsInPersistence(network: Network, collections: List<SdkNFTCollection>) {
|
||||
getNFTPersistenceStore(network).saveCollections(collections)
|
||||
private suspend fun saveCollectionsInPersistence(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
collections: List<SdkNFTCollection>,
|
||||
) {
|
||||
getNFTPersistenceStore(userWalletId, network).saveCollections(collections)
|
||||
}
|
||||
|
||||
private fun getJobHolder(network: Network): JobHolder = jobs[network] ?: run {
|
||||
|
|
@ -141,65 +154,85 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getNFTPersistenceStore(network: Network): NFTPersistenceStore = nftPersistenceStores[network] ?: run {
|
||||
nftPersistenceStoreFactory.provide(network).also {
|
||||
nftPersistenceStores[network] = it
|
||||
private fun getNFTPersistenceStore(userWalletId: UserWalletId, network: Network): NFTPersistenceStore {
|
||||
val storeId = (userWalletId to network).formatted()
|
||||
return nftPersistenceStores[storeId] ?: run {
|
||||
nftPersistenceStoreFactory.provide(userWalletId, network).also {
|
||||
nftPersistenceStores[storeId] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getNFTRuntimeStore(network: Network): NFTRuntimeStore = nftRuntimeStores[network] ?: run {
|
||||
nftRuntimeStoreFactory.provide(network).also {
|
||||
nftRuntimeStores[network] = it
|
||||
val storedCollections = getStoredCollections(network)
|
||||
val storedPrices = getStoredPrices(network)
|
||||
it.initialize(
|
||||
collections = storedCollections,
|
||||
prices = storedPrices,
|
||||
)
|
||||
private suspend fun getNFTRuntimeStore(userWalletId: UserWalletId, network: Network): NFTRuntimeStore {
|
||||
val storeId = (userWalletId to network).formatted()
|
||||
return nftRuntimeStores[storeId] ?: run {
|
||||
nftRuntimeStoreFactory.provide(network).also {
|
||||
nftRuntimeStores[storeId] = it
|
||||
val storedCollections = getStoredCollections(userWalletId, network)
|
||||
val storedPrices = getStoredPrices(userWalletId, network)
|
||||
it.initialize(
|
||||
collections = storedCollections,
|
||||
prices = storedPrices,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getStoredCollections(network: Network) = getNFTPersistenceStore(network)
|
||||
.getCollectionsSync()
|
||||
.let {
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Collections(
|
||||
collections = it
|
||||
?.map { collection ->
|
||||
collectionConverter.convert(network to collection)
|
||||
}
|
||||
?.filter {
|
||||
it.id !is NFTCollection.Identifier.Unknown
|
||||
},
|
||||
source = StatusSource.CACHE,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getStoredPrices(network: Network) = getNFTPersistenceStore(network)
|
||||
.getSalePricesSync()
|
||||
.orEmpty()
|
||||
.let { prices ->
|
||||
prices
|
||||
.mapKeys {
|
||||
val (assetId, _) = it
|
||||
NFTSdkAssetIdentifierConverter.convert(assetId)
|
||||
}
|
||||
.mapValues {
|
||||
val (assetId, price) = it
|
||||
NFTSalePrice.Value(
|
||||
assetId = assetId,
|
||||
value = price.value,
|
||||
symbol = price.symbol,
|
||||
private suspend fun getStoredCollections(userWalletId: UserWalletId, network: Network) =
|
||||
getNFTPersistenceStore(userWalletId, network)
|
||||
.getCollectionsSync()
|
||||
.let {
|
||||
NFTCollections(
|
||||
network = network,
|
||||
content = NFTCollections.Content.Collections(
|
||||
collections = it
|
||||
?.map { collection ->
|
||||
NFTSdkCollectionConverter.convert(network to collection)
|
||||
}
|
||||
?.filter {
|
||||
it.id !is NFTCollection.Identifier.Unknown
|
||||
},
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun List<SdkNFTCollection>.mergeWithStoredAssets(network: Network): List<SdkNFTCollection> {
|
||||
private suspend fun getStoredPrices(userWalletId: UserWalletId, network: Network) =
|
||||
getNFTPersistenceStore(userWalletId, network)
|
||||
.getSalePricesSync()
|
||||
.orEmpty()
|
||||
.let { prices ->
|
||||
prices
|
||||
.mapKeys {
|
||||
val (assetId, _) = it
|
||||
NFTSdkAssetIdentifierConverter.convert(assetId)
|
||||
}
|
||||
.mapValues {
|
||||
val (assetId, price) = it
|
||||
NFTSalePrice.Value(
|
||||
assetId = assetId,
|
||||
value = price.value,
|
||||
symbol = price.symbol,
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NFTCollections.changeStatusSource(source: StatusSource) = copy(
|
||||
content = when (val content = content) {
|
||||
is NFTCollections.Content.Collections -> content.copy(
|
||||
source = source,
|
||||
)
|
||||
is NFTCollections.Content.Error -> content
|
||||
},
|
||||
)
|
||||
|
||||
private suspend fun List<SdkNFTCollection>.mergeWithStoredAssets(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): List<SdkNFTCollection> {
|
||||
val storedCollections =
|
||||
getNFTPersistenceStore(network)
|
||||
getNFTPersistenceStore(userWalletId, network)
|
||||
.getCollectionsSync()
|
||||
.orEmpty()
|
||||
.associateBy(SdkNFTCollection::identifier)
|
||||
|
|
@ -224,4 +257,11 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Pair<UserWalletId, Network>.formatted(): String {
|
||||
val (walletId, network) = this
|
||||
return walletId.stringValue + "_" + network.id.value + "_" + network.derivationPath.value
|
||||
}
|
||||
|
||||
private fun Network.canHandleNFTs(): Boolean = Blockchain.fromNetworkId(backendId)?.canHandleNFTs() == true
|
||||
}
|
||||
|
|
@ -131,7 +131,7 @@ internal class DefaultHotCryptoRepository(
|
|||
|
||||
analyticsEventHandler.send(
|
||||
event = MainScreenAnalyticsEvent.HotTokenError(
|
||||
errorCode = (it as? ApiResponseError.HttpException)?.code?.code?.toString().orEmpty(),
|
||||
errorCode = (it as? ApiResponseError.HttpException)?.code?.numericCode?.toString().orEmpty(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import com.tangem.data.onramp.DefaultOnrampErrorResolver
|
|||
import com.tangem.data.onramp.DefaultOnrampRepository
|
||||
import com.tangem.data.onramp.DefaultOnrampTransactionRepository
|
||||
import com.tangem.data.onramp.converters.error.OnrampErrorConverter
|
||||
import com.tangem.domain.onramp.repositories.LegacyTopUpRepository
|
||||
import com.tangem.data.onramp.legacy.MercuryoTopUpRepository
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
|
|
@ -15,6 +17,7 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
|
|||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.exchangeservice.hotcrypto.HotCryptoResponseStore
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.local.onramp.countries.OnrampCountriesStore
|
||||
import com.tangem.datasource.local.onramp.currencies.OnrampCurrenciesStore
|
||||
import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore
|
||||
|
|
@ -112,4 +115,16 @@ internal object OnrampDataModule {
|
|||
analyticsEventHandler = analyticsEventHandler,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMercuryoRepository(
|
||||
environmentConfigStorage: EnvironmentConfigStorage,
|
||||
dispatchersProvider: CoroutineDispatcherProvider,
|
||||
): LegacyTopUpRepository {
|
||||
return MercuryoTopUpRepository(
|
||||
environmentConfigStorage = environmentConfigStorage,
|
||||
dispatchersProvider = dispatchersProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
package com.tangem.tap.network.exchangeServices.mercuryo
|
||||
package com.tangem.data.onramp.legacy
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
||||
/**
|
||||
* Legacy onboarding note/twins to get top up URL from mercuryo
|
||||
* Map [Blockchain] to ids from [link](https://api.mercuryo.io/v1.6/lib/currencies) [crypto_currencies]
|
||||
*/
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
internal val Blockchain.mercuryoNetwork: String?
|
||||
public val Blockchain.mercuryoNetwork: String?
|
||||
get() {
|
||||
return when (this) {
|
||||
Blockchain.Algorand -> "ALGORAND"
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.data.onramp.legacy
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.calculateSha512
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.domain.onramp.repositories.LegacyTopUpRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class MercuryoTopUpRepository @Inject constructor(
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||
private val dispatchersProvider: CoroutineDispatcherProvider,
|
||||
) : LegacyTopUpRepository {
|
||||
|
||||
override suspend fun getTopUpUrl(cryptoCurrency: CryptoCurrency, walletAddress: String): String =
|
||||
withContext(dispatchersProvider.default) {
|
||||
val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value)
|
||||
val environmentConfig = environmentConfigStorage.getConfigSync()
|
||||
|
||||
val builder = Uri.Builder()
|
||||
.scheme(LegacyTopUpRepository.SCHEME)
|
||||
.authority("exchange.mercuryo.io")
|
||||
.appendQueryParameter("widget_id", environmentConfig.mercuryoWidgetId)
|
||||
.appendQueryParameter("type", "buy")
|
||||
.appendQueryParameter("currency", cryptoCurrency.symbol)
|
||||
.appendQueryParameter("address", walletAddress)
|
||||
.appendQueryParameter("signature", environmentConfig.signature(walletAddress))
|
||||
.appendQueryParameter("fix_currency", "true")
|
||||
.appendQueryParameter("redirect_url", LegacyTopUpRepository.SUCCESS_URL)
|
||||
|
||||
blockchain.mercuryoNetwork?.let {
|
||||
builder.appendQueryParameter("network", it)
|
||||
}
|
||||
|
||||
builder.build().toString()
|
||||
}
|
||||
|
||||
private fun EnvironmentConfig.signature(address: String) = (address + mercuryoSecret)
|
||||
.calculateSha512()
|
||||
.toHexString()
|
||||
.lowercase()
|
||||
}
|
||||
|
|
@ -738,7 +738,7 @@ internal class DefaultStakingRepository(
|
|||
private companion object {
|
||||
const val YIELDS_STORE_KEY = "yields"
|
||||
|
||||
const val TON_INTEGRATION_ID = "ton-ton-tonwhales-pools-staking"
|
||||
const val TON_INTEGRATION_ID = "ton-ton-chorus-one-pools-staking"
|
||||
const val SOLANA_INTEGRATION_ID = "solana-sol-native-multivalidator-staking"
|
||||
const val COSMOS_INTEGRATION_ID = "cosmos-atom-native-staking"
|
||||
const val ETHEREUM_POLYGON_INTEGRATION_ID = "ethereum-matic-native-staking"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ dependencies {
|
|||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
|
||||
/** Project - Utils */
|
||||
implementation(projects.core.utils)
|
||||
|
|
|
|||
|
|
@ -43,11 +43,9 @@ internal class DefaultVisaRepository @Inject constructor(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val visaApiRequestMaker: VisaApiRequestMaker,
|
||||
private val visaApi: TangemVisaApi,
|
||||
private val visaCurrencyFactory: VisaCurrencyFactory,
|
||||
) : VisaRepository {
|
||||
|
||||
private val currencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
VisaCurrencyFactory()
|
||||
}
|
||||
private val txDetailsFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
VisaTxDetailsFactory()
|
||||
}
|
||||
|
|
@ -79,20 +77,22 @@ internal class DefaultVisaRepository @Inject constructor(
|
|||
|
||||
private suspend fun fetchVisaCurrency(userWalletId: UserWalletId, address: String) {
|
||||
val contractInfoProvider = visaLibLoader.getOrCreateProvider()
|
||||
val paymentAccountAddress = getPaymentAccountAddress(userWalletId)
|
||||
val userWallet = findVisaUserWallet(userWalletId)
|
||||
|
||||
parZip(
|
||||
dispatchers.io,
|
||||
{
|
||||
contractInfoProvider.getContractInfo(
|
||||
walletAddress = address,
|
||||
paymentAccountAddress = getPaymentAccountAddress(userWalletId),
|
||||
paymentAccountAddress = paymentAccountAddress,
|
||||
)
|
||||
},
|
||||
{ getFiatRate() },
|
||||
{ contractInfo, fiatRate ->
|
||||
fetchedCurrencies.update { value ->
|
||||
value.apply {
|
||||
put(address, currencyFactory.create(contractInfo, fiatRate))
|
||||
put(address, visaCurrencyFactory.create(userWallet, contractInfo, fiatRate))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -157,7 +157,7 @@ internal class DefaultVisaRepository @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
// TODO select correct account when multiple accounts are available (will be implemented when backend is ready)
|
||||
// There will be only one payment account (backend has filtered for us), so we can take the first one
|
||||
customerInfo.paymentAccounts.firstOrNull()?.paymentAccountAddress
|
||||
}.getOrNull()
|
||||
|
||||
|
|
@ -187,14 +187,14 @@ internal class DefaultVisaRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getFiatRate(): BigDecimal? {
|
||||
private suspend fun getFiatRate(): BigDecimal {
|
||||
val fiatCurrencyId = VisaConstants.fiatCurrency.code.lowercase()
|
||||
val quotes = tangemTechApi.getQuotes(
|
||||
currencyId = fiatCurrencyId,
|
||||
coinIds = VisaConstants.TOKEN_ID,
|
||||
).getOrThrow()
|
||||
|
||||
return quotes.quotes[VisaConstants.TOKEN_ID]?.price
|
||||
return quotes.quotes[VisaConstants.TOKEN_ID]?.price ?: error("No price found")
|
||||
}
|
||||
|
||||
private fun makeWalletAddresses(userWallet: UserWallet): Set<Address> {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,28 @@
|
|||
package com.tangem.data.visa.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.getNetwork
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import com.tangem.domain.visa.model.VisaCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.lib.visa.model.VisaContractInfo
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeZone
|
||||
import org.joda.time.Instant
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class VisaCurrencyFactory {
|
||||
internal class VisaCurrencyFactory @Inject constructor(
|
||||
private val cryptoCurrencyFactory: CryptoCurrencyFactory,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
) {
|
||||
|
||||
fun create(contractInfo: VisaContractInfo, fiatRate: BigDecimal?): VisaCurrency {
|
||||
fun create(userWallet: UserWallet, contractInfo: VisaContractInfo, fiatRate: BigDecimal): VisaCurrency {
|
||||
val now = Instant.now()
|
||||
val currentLimit = if (contractInfo.limitsChangeDate > now) {
|
||||
contractInfo.oldLimits
|
||||
|
|
@ -19,11 +31,30 @@ internal class VisaCurrencyFactory {
|
|||
}
|
||||
val remainingOtpLimit = getRemainingOtp(currentLimit, now)
|
||||
|
||||
val currencyNetwork = getNetwork(
|
||||
blockchain = Blockchain.Polygon,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
canHandleTokens = true,
|
||||
) ?: error("Unable to create network for Visa currency")
|
||||
|
||||
val cryptoCurrency = cryptoCurrencyFactory.createToken(
|
||||
network = currencyNetwork,
|
||||
rawId = CryptoCurrency.RawID(VisaConstants.TOKEN_ID),
|
||||
name = contractInfo.token.name,
|
||||
symbol = contractInfo.token.symbol,
|
||||
decimals = contractInfo.token.decimals,
|
||||
contractAddress = contractInfo.token.address,
|
||||
)
|
||||
|
||||
return VisaCurrency(
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
symbol = contractInfo.token.symbol,
|
||||
networkName = VisaConstants.NETWORK_NAME,
|
||||
decimals = contractInfo.token.decimals,
|
||||
fiatRate = fiatRate,
|
||||
priceChange = BigDecimal.ZERO, // [Second Visa Iteration] pass to create() params if needed
|
||||
fiatCurrency = VisaConstants.fiatCurrency,
|
||||
balances = with(contractInfo) {
|
||||
VisaCurrency.Balances(
|
||||
|
|
@ -40,6 +71,12 @@ internal class VisaCurrencyFactory {
|
|||
singleTransaction = currentLimit.singleTransactionLimit,
|
||||
expirationDate = getLimitsExpirationDate(currentLimit, now),
|
||||
),
|
||||
paymentAccountAddress = NetworkAddress.Single(
|
||||
NetworkAddress.Address(
|
||||
value = contractInfo.paymentAccountAddress,
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -208,6 +208,10 @@ internal class DefaultWalletsRepository(
|
|||
.getObjectMap<Boolean>(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY)
|
||||
.map { it[userWalletId.stringValue] == true }
|
||||
|
||||
override fun nftEnabledStatuses(): Flow<Map<UserWalletId, Boolean>> = appPreferencesStore
|
||||
.getObjectMap<Boolean>(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY)
|
||||
.map { it.mapKeys { UserWalletId(it.key) } }
|
||||
|
||||
override suspend fun enableNFT(userWalletId: UserWalletId) {
|
||||
appPreferencesStore.editData {
|
||||
it.setObjectMap(
|
||||
|
|
|
|||
|
|
@ -682,6 +682,12 @@ class DefaultWalletManagersFacade(
|
|||
return walletManager.getAsset(collectionIdentifier, assetIdentifier)
|
||||
}
|
||||
|
||||
override suspend fun isAccountInitialized(userWalletId: UserWalletId, network: Network): Boolean {
|
||||
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = network)
|
||||
val initializableAccountWalletManger = walletManager as? InitializableAccount ?: return false
|
||||
return initializableAccountWalletManger.accountInitializationState == InitializableAccount.State.INITIALIZED
|
||||
}
|
||||
|
||||
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
|
||||
if (tokens.isEmpty()) return
|
||||
|
||||
|
|
|
|||
|
|
@ -265,4 +265,10 @@ interface WalletManagersFacade {
|
|||
collectionIdentifier: NFTCollection.Identifier,
|
||||
assetIdentifier: NFTAsset.Identifier,
|
||||
): NFTAsset?
|
||||
|
||||
/**
|
||||
* If wallet manager implements [InitializableAccount] then returns [InitializableAccount.isAccountInitialized]
|
||||
* value. Otherwise always return true
|
||||
*/
|
||||
suspend fun isAccountInitialized(userWalletId: UserWalletId, network: Network): Boolean
|
||||
}
|
||||
|
|
@ -10,9 +10,11 @@ import com.tangem.domain.tokens.model.CryptoCurrency
|
|||
import com.tangem.domain.tokens.model.Network
|
||||
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.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class SaveManagedTokensUseCase(
|
||||
private val customTokensRepository: CustomTokensRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
|
|
@ -20,6 +22,7 @@ class SaveManagedTokensUseCase(
|
|||
private val networksRepository: NetworksRepository,
|
||||
private val derivationsRepository: DerivationsRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
|
|
@ -54,6 +57,8 @@ class SaveManagedTokensUseCase(
|
|||
)
|
||||
|
||||
refreshUpdatedYieldBalances(userWalletId, existingCurrencies)
|
||||
|
||||
refreshUpdatedQuotes(addingCurrencies)
|
||||
}
|
||||
|
||||
private suspend fun removeCurrenciesFromWalletManager(
|
||||
|
|
@ -105,6 +110,13 @@ class SaveManagedTokensUseCase(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedQuotes(addedCurrencies: List<CryptoCurrency>) {
|
||||
quotesRepository.fetchQuotes(
|
||||
currenciesIds = addedCurrencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId },
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the [existingCurrencies] list contains a coin that corresponds
|
||||
* to the given [network].
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ dependencies {
|
|||
api(projects.domain.models)
|
||||
api(projects.domain.legacy)
|
||||
api(projects.domain.wallets)
|
||||
api(projects.domain.staking)
|
||||
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.tokens)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ package com.tangem.domain.markets
|
|||
import arrow.core.Either
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
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.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
|
|
@ -23,6 +25,8 @@ class SaveMarketTokensUseCase(
|
|||
private val marketsTokenRepository: MarketsTokenRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
|
|
@ -60,11 +64,37 @@ class SaveMarketTokensUseCase(
|
|||
|
||||
currenciesRepository.addCurrencies(userWalletId = userWalletId, currencies = addedCurrencies)
|
||||
|
||||
networksRepository.getNetworkStatusesSync(
|
||||
userWalletId = userWalletId,
|
||||
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
||||
refresh = true,
|
||||
)
|
||||
refreshUpdatedNetworks(userWalletId, addedCurrencies)
|
||||
|
||||
refreshUpdatedYieldBalances(userWalletId, addedCurrencies)
|
||||
|
||||
refreshUpdatedQuotes(addedCurrencies)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedNetworks(userWalletId: UserWalletId, addedCurrencies: List<CryptoCurrency>) {
|
||||
networksRepository.getNetworkStatusesSync(
|
||||
userWalletId = userWalletId,
|
||||
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedYieldBalances(
|
||||
userWalletId: UserWalletId,
|
||||
existingCurrencies: List<CryptoCurrency>,
|
||||
) {
|
||||
stakingRepository.fetchMultiYieldBalance(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencies = existingCurrencies,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedQuotes(addedCurrencies: List<CryptoCurrency>) {
|
||||
quotesRepository.fetchQuotes(
|
||||
currenciesIds = addedCurrencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId },
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,4 +17,14 @@ data class NFTCollections(
|
|||
val error: Throwable,
|
||||
) : Content()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun empty(network: Network) = NFTCollections(
|
||||
network = network,
|
||||
content = Content.Collections(
|
||||
collections = null,
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class FetchNFTCollectionsUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val nftRepository: NFTRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId) {
|
||||
val currencies = currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId)
|
||||
nftRepository.refreshCollections(userWalletId, currencies.map { it.network }.distinct())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import com.tangem.domain.nft.models.NFTCollections
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
|
||||
class GetNFTCollectionsUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val nftRepository: NFTRepository,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun launch(userWalletId: UserWalletId): Flow<List<NFTCollections>> = currenciesRepository
|
||||
.getWalletCurrenciesUpdates(userWalletId)
|
||||
.flatMapLatest {
|
||||
val networks = it
|
||||
.map { cryptoCurrency -> cryptoCurrency.network }
|
||||
.distinct()
|
||||
nftRepository.observeCollections(userWalletId, networks)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.onramp.repositories.LegacyTopUpRepository
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
||||
class GetLegacyTopUpUrlUseCase(
|
||||
private val legacyTopUpRepository: LegacyTopUpRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(cryptoCurrencyStatus: CryptoCurrencyStatus): Either<OnrampError.DomainError, String> =
|
||||
either {
|
||||
val walletAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
|
||||
?: raise(OnrampError.DomainError("Wallet address is null"))
|
||||
legacyTopUpRepository.getTopUpUrl(
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
walletAddress = walletAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.onramp.repositories
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
/**
|
||||
* Support legacy onboarding note/twins to get top up URL from mercuryo
|
||||
*
|
||||
*/
|
||||
interface LegacyTopUpRepository {
|
||||
|
||||
/**
|
||||
* Get top up URL from mercuryo
|
||||
*
|
||||
* @property cryptoCurrency crypto currency
|
||||
* @property walletAddress address of wallet
|
||||
*/
|
||||
suspend fun getTopUpUrl(cryptoCurrency: CryptoCurrency, walletAddress: String): String
|
||||
|
||||
companion object {
|
||||
const val SCHEME = "https"
|
||||
const val SUCCESS_URL = "https://tangem.com/success"
|
||||
}
|
||||
}
|
||||
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