Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-04 16:19:14 +03:00
commit 53cbe15b78
430 changed files with 12468 additions and 1527 deletions

View file

@ -25,11 +25,17 @@ dependencies {
implementation(project(":domain:legacy"))
implementation(project(":domain:models"))
implementation(project(":domain:core"))
implementation(project(":domain:card"))
implementation(projects.domain.card)
implementation(projects.domain.demo)
implementation(project(":domain:wallets"))
implementation(project(":domain:wallets:models"))
implementation(projects.domain.settings)
implementation(projects.domain.tokens)
implementation(projects.domain.txhistory)
implementation(project(":common"))
implementation(project(":core:analytics"))
implementation(projects.core.analytics.models)
implementation(project(":core:navigation"))
implementation(project(":core:featuretoggles"))
implementation(project(":core:res"))
@ -38,7 +44,13 @@ dependencies {
implementation(project(":core:utils"))
implementation(project(":libs:crypto"))
implementation(project(":libs:auth"))
implementation(project(":data:source:preferences"))
implementation(projects.data.card)
implementation(projects.data.common)
implementation(projects.data.settings)
implementation(projects.data.tokens)
implementation(projects.data.txhistory)
/** Features */
implementation(project(":features:onboarding"))
@ -119,13 +131,6 @@ dependencies {
implementation(deps.appsflyer)
implementation(deps.amplitude)
implementation(deps.kotsonGson)
//TODO: refactoring: remove it when all network services moved to the datasource module
implementation(deps.retrofit)
implementation(deps.retrofit.moshi)
implementation(deps.moshi)
implementation(deps.moshi.kotlin)
implementation(deps.okHttp)
implementation(deps.okHttp.logging)
implementation(deps.zendesk.chat)
implementation(deps.zendesk.messaging)
implementation(deps.spongecastle.core)
@ -144,6 +149,7 @@ dependencies {
implementation(deps.kotlin.serialization)
implementation(deps.walletConnectCore)
implementation(deps.walletConnectWeb3)
implementation(deps.prettyLogger)
/** Testing libraries */
testImplementation(deps.test.junit)

@ -1 +1 @@
Subproject commit b791bd4cf6c5eca9778f89e87cd62b72d24f5ce9
Subproject commit 8c1c53b73950698d4acfa4d925b8c14bf6f0da63

View file

@ -10,10 +10,11 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.snackbar.Snackbar
import com.tangem.TangemSdk
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.data.card.sdk.CardSdkLifecycleObserver
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
@ -56,7 +57,6 @@ import java.lang.ref.WeakReference
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
lateinit var tangemSdk: TangemSdk
lateinit var tangemSdkManager: TangemSdkManager
lateinit var backupService: BackupService
internal var lockUserWalletsTimer: LockUserWalletsTimer? = null
@ -88,7 +88,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
lateinit var testerRouter: TesterRouter
@Inject
lateinit var injectedTangemSdk: TangemSdk
lateinit var cardSdkLifecycleObserver: CardSdkLifecycleObserver
@Inject
lateinit var cardSdkConfigRepository: CardSdkConfigRepository
@Inject
lateinit var injectedTangemSdkManager: TangemSdkManager
@ -122,11 +125,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
systemActions()
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
tangemSdk = injectedTangemSdk
cardSdkLifecycleObserver.onCreate(context = this)
tangemSdkManager = injectedTangemSdkManager
appStateHolder.tangemSdkManager = tangemSdkManager
appStateHolder.tangemSdk = tangemSdk
backupService = BackupService.init(tangemSdk, this)
backupService = BackupService.init(cardSdkConfigRepository.sdk, this)
lockUserWalletsTimer = LockUserWalletsTimer(owner = this)
initUserWalletsListManager()
@ -144,10 +147,37 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
walletRouter = walletRouter,
walletConnectInteractor = walletConnectInteractor,
tokenDetailsRouter = tokenDetailsRouter,
cardSdkConfigRepository = cardSdkConfigRepository,
),
)
}
override fun onStart() {
super.onStart()
dialogManager.onStart(this)
}
override fun onResume() {
super.onResume()
// TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
navigateToInitialScreenIfNeededOnResume(intent)
}
override fun onStop() {
notificationsHandler = null
dialogManager.onStop()
super.onStop()
}
override fun onDestroy() {
store.dispatch(NavigationAction.ActivityDestroyed(WeakReference(this)))
intentProcessor.removeAll()
cardSdkLifecycleObserver.onDestroy()
super.onDestroy()
}
private fun initIntentHandlers() {
val hasSavedWalletsProvider = { store.state.globalState.userWalletsListManager?.hasUserWallets == true }
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider))
@ -183,13 +213,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
override fun onResume() {
super.onResume()
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
navigateToInitialScreenIfNeededOnResume(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
scope.launch {
@ -197,23 +220,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
}
override fun onStart() {
super.onStart()
dialogManager.onStart(this)
}
override fun onStop() {
notificationsHandler = null
dialogManager.onStop()
super.onStop()
}
override fun onDestroy() {
store.dispatch(NavigationAction.ActivityDestroyed(WeakReference(this)))
intentProcessor.removeAll()
super.onDestroy()
}
override fun showSnackbar(text: Int, buttonTitle: Int?, action: View.OnClickListener?) {
if (snackbar != null) return

View file

@ -5,21 +5,26 @@ import android.content.Context
import android.content.pm.PackageManager
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.orhanobut.logger.AndroidLogAdapter
import com.orhanobut.logger.Logger
import com.tangem.Log
import com.tangem.LogFormat
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.ExceptionHandler
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.core.analytics.Analytics
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.data.source.preferences.PreferencesDataSource
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.config.FeaturesLocalLoader
import com.tangem.datasource.config.models.Config
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.domain.DomainLayer
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.common.LogConfig
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor
@ -27,6 +32,7 @@ import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggle
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
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.appsFlyer.AppsFlyerAnalyticsHandler
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
@ -59,7 +65,6 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.wallet.BuildConfig
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.runBlocking
import okhttp3.logging.HttpLoggingInterceptor
import org.rekotlin.Store
import timber.log.Timber
import javax.inject.Inject
@ -151,6 +156,12 @@ class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var tokenDetailsFeatureToggles: TokenDetailsFeatureToggles
@Inject
lateinit var scanCardProcessor: ScanCardProcessor
@Inject
lateinit var blockchainExceptionHandler: BlockchainExceptionHandler
override fun onCreate() {
super.onCreate()
@ -168,12 +179,20 @@ class TapApplication : Application(), ImageLoaderFactory {
walletConnectRepository = walletConnect2Repository,
walletConnectSessionsRepository = walletConnectSessionsRepository,
tokenDetailsFeatureToggles = tokenDetailsFeatureToggles,
scanCardProcessor = scanCardProcessor,
),
),
)
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
Logger.addLogAdapter(AndroidLogAdapter())
Timber.plant(
object : Timber.DebugTree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
Logger.log(priority, tag, message, t)
}
},
)
}
foregroundActivityObserver = ForegroundActivityObserver()
@ -192,7 +211,7 @@ class TapApplication : Application(), ImageLoaderFactory {
if (LogConfig.network.blockchainSdkNetwork) {
BlockchainSdkRetrofitBuilder.interceptors = listOf(
HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY },
createNetworkLoggingInterceptor(),
)
}
@ -266,6 +285,7 @@ class TapApplication : Application(), ImageLoaderFactory {
jsonConverter = MoshiConverter.sdkMoshiConverter,
)
factory.build(Analytics, buildData)
ExceptionHandler.append(blockchainExceptionHandler)
}
private fun initFeedbackManager(

View file

@ -3,7 +3,7 @@ package com.tangem.tap.common.analytics.converters
import com.shopify.buy3.Storefront
import com.tangem.common.Converter
import com.tangem.tap.common.analytics.events.Shop
import com.tangem.tap.common.shop.data.ProductType
import com.tangem.tap.features.shop.domain.models.ProductType
/**
[REDACTED_AUTHOR]

View file

@ -157,5 +157,10 @@ sealed class AnalyticsParam {
const val ERROR_KEY = "Error Key"
const val CREATION_TYPE = "Creation type"
const val DAPP_NAME = "DApp Name"
const val DAPP_URL = "DApp Url"
const val METHOD_NAME = "Method Name"
const val VALIDATION = "Validation"
const val BLOCKCHAIN_EXCEPTION_HOST = "exception_host"
const val BLOCKCHAIN_SELECTED_HOST = "selected_host"
}
}

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
/**
[REDACTED_AUTHOR]

View file

@ -0,0 +1,17 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.models.AnalyticsEvent
class BlockchainExceptionEvent(
selectedHost: String,
exceptionHost: String,
error: String,
) : AnalyticsEvent(
category = "BlockchainSdk",
event = "Exception",
params = mapOf(
AnalyticsParam.BLOCKCHAIN_SELECTED_HOST to selectedHost,
AnalyticsParam.BLOCKCHAIN_EXCEPTION_HOST to exceptionHost,
AnalyticsParam.ERROR_DESCRIPTION to error,
),
)

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
/**
[REDACTED_AUTHOR]

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
/**
[REDACTED_AUTHOR]

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
/**
[REDACTED_AUTHOR]

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
/**
[REDACTED_AUTHOR]
@ -13,7 +13,6 @@ sealed class MainScreen(
class ScreenOpened : MainScreen("Screen opened")
class ButtonScanCard : MainScreen("Button - Scan Card")
class ButtonMyWallets : MainScreen("Button - My Wallets")
class ButtonBuy : MainScreen("Button - Buy")
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen(
event = "Enable Biometric",

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.common.extensions.filterNotNull

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
sealed class MyWallets(
event: String,

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
/**
[REDACTED_AUTHOR]
@ -72,26 +72,8 @@ sealed class Onboarding(
class SetupFinished : Twins("Twin Setup Finished")
}
class PinScreenOpened : Onboarding("Onboarding", "PIN screen opened")
class ButtonSetPinCode : Onboarding("Onboarding", "Button - Set PIN Code")
class CardConnectionScreenOpened : Onboarding("Onboarding", "Card Connection Screen Opened")
class ButtonConnect : Onboarding("Onboarding", "Button - Connect")
class PinCodeSet : Onboarding("Onboarding", "PIN Code Set")
class KYCScreenOpened : Onboarding("Onboarding", "KYC screen opened")
class KYCStarted : Onboarding("Onboarding", "KYC Started")
class KYCInProgress : Onboarding("Onboarding", "KYC In Progress")
class KYCRejected : Onboarding("Onboarding", "KYC Rejected")
class ClaimScreenOpened : Onboarding("Onboarding", "Claim Screen Opened")
class ButtonClaim : Onboarding("Onboarding", "Button - Claim")
class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim Was Successfully")
class ButtonChat : Onboarding("Onboarding", "Button - Chat")
class NotEnoughGasError : Onboarding("Onboarding", "Not Enough Gas Error")
class CardNotPassedError : Onboarding("Onboarding", "Card Not Passed Error")
class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding(
category = "Onboarding / Biometric",
event = "Enable Biometric",

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
/**
[REDACTED_AUTHOR]

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.features.details.ui.details.SocialNetwork
/**

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.common.extensions.filterNotNull
/**

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
/**
[REDACTED_AUTHOR]

View file

@ -1,6 +1,6 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.common.analytics.events.AnalyticsParam.CurrencyType
/**

View file

@ -1,6 +1,7 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.common.extensions.filterNotNull
/**
[REDACTED_AUTHOR]
@ -12,16 +13,51 @@ sealed class WalletConnect(
) : AnalyticsEvent("Wallet Connect", event, params, error) {
class ScreenOpened : WalletConnect(event = "WC Screen Opened")
class NewSessionEstablished(dAppName: String) : WalletConnect(
class NewSessionEstablished(dAppName: String, dAppUrl: String) : WalletConnect(
event = "New Session Established",
params = mapOf(
AnalyticsParam.DAPP_NAME to dAppName,
AnalyticsParam.DAPP_URL to dAppUrl,
),
)
class SessionDisconnected : WalletConnect("Session Disconnected")
class RequestSigned : WalletConnect("Request Signed")
class SessionDisconnected(dAppName: String, dAppUrl: String) : WalletConnect(
event = "Session Disconnected",
params = mapOf(
AnalyticsParam.DAPP_NAME to dAppName,
AnalyticsParam.DAPP_URL to dAppUrl,
),
)
class SignError(error: Throwable) : WalletConnect("Sign", error = error)
class TransactionError(error: Throwable) : WalletConnect("Transaction", error = error)
class RequestHandled(
params: RequestHandledParams,
) : WalletConnect(
event = "Request Handled",
params = params.toParamsMap(),
)
data class RequestHandledParams(
val dAppName: String,
val dAppUrl: String,
val methodName: String,
val blockchain: String,
val errorCode: String? = null,
) {
fun toParamsMap(): Map<String, String> {
val validation = if (errorCode == null) Validation.SUCCESS.param else Validation.FAIL.param
return mapOf(
AnalyticsParam.DAPP_NAME to dAppName,
AnalyticsParam.DAPP_URL to dAppUrl,
AnalyticsParam.METHOD_NAME to methodName,
AnalyticsParam.BLOCKCHAIN to blockchain,
AnalyticsParam.VALIDATION to validation,
if (errorCode != null) AnalyticsParam.ERROR_CODE to errorCode else null to null,
).filterNotNull()
}
}
enum class Validation(val param: String) {
SUCCESS("Success"),
FAIL("Fail"),
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.tap.common.analytics.handlers
import com.tangem.blockchain.common.ExceptionHandlerOutput
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.events.BlockchainExceptionEvent
import javax.inject.Inject
class BlockchainExceptionHandler @Inject constructor(
private val analyticsHandler: AnalyticsEventHandler,
) : ExceptionHandlerOutput {
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String) {
analyticsHandler.send(
BlockchainExceptionEvent(
selectedHost = nextHost,
exceptionHost = currentHost,
error = message,
),
)
}
}

View file

@ -1,8 +1,8 @@
package com.tangem.tap.common.analytics.handlers.appsFlyer
import com.appsflyer.AFInAppEventType
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.api.AnalyticsHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.events.Shop

View file

@ -1,9 +1,9 @@
package com.tangem.tap.common.analytics.handlers.firebase
import com.google.firebase.analytics.FirebaseAnalytics
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.api.AnalyticsHandler
import com.tangem.core.analytics.api.ErrorEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter
import com.tangem.tap.common.analytics.events.Shop

View file

@ -1,7 +1,7 @@
package com.tangem.tap.common.analytics.paramsInterceptor
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.api.ParamsInterceptor
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse

View file

@ -1,14 +1,14 @@
package com.tangem.tap.common.analytics.paramsInterceptor
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.api.ParamsInterceptor
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.models.scan.ScanResponse
/**
[REDACTED_AUTHOR]
*/
class LinkedCardContextInterceptor(
private val scanResponse: ScanResponse,
scanResponse: ScanResponse,
val parent: LinkedCardContextInterceptor? = null,
) : ParamsInterceptor {

View file

@ -9,19 +9,19 @@ import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.common.analytics.converters.TopUpEventConverter
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.extensions.copy
import com.tangem.tap.domain.model.TotalFiatBalance
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
import com.tangem.tap.domain.walletStores.WalletStoresManager
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.scope
import com.tangem.utils.extensions.copy
import kotlinx.coroutines.launch
import java.math.BigDecimal

View file

@ -12,6 +12,7 @@ import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener
import com.tangem.tap.common.chat.opener.implementation.ZendeskChatOpener
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Store
import java.io.File
class ChatManager(
private val preferencesStorage: PreferencesDataSource,
@ -20,7 +21,7 @@ class ChatManager(
) {
private val openers = mutableMapOf<ChatConfig, ChatOpener>()
fun open(config: ChatConfig, feedbackDataBuilder: (Context) -> String) {
fun open(config: ChatConfig, createLogsFile: (Context) -> File?, createFeedbackFile: (Context) -> File?) {
val opener = openers.getOrPut(config) {
when (config) {
is SprinklrConfig -> SprinklrChatOpener(getSprinklrUserId(), config, store, foregroundActivityObserver)
@ -28,7 +29,10 @@ class ChatManager(
}
}
opener.open(feedbackDataBuilder)
opener.open(
createFeedbackFile = createFeedbackFile,
createLogsFile = createLogsFile,
)
}
private fun getZendeskUserId(): String {

View file

@ -1,7 +1,8 @@
package com.tangem.tap.common.chat.opener
import android.content.Context
import java.io.File
internal interface ChatOpener {
fun open(feedbackDataBuilder: (Context) -> String)
fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?)
}

View file

@ -2,14 +2,15 @@ package com.tangem.tap.common.chat.opener.implementation
import android.content.Context
import android.content.Intent
import com.tangem.datasource.config.models.SprinklrConfig
import com.tangem.tap.ForegroundActivityObserver
import com.tangem.tap.common.chat.opener.ChatOpener
import com.tangem.datasource.config.models.SprinklrConfig
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.sprinklr.redux.SprinklrAction
import com.tangem.tap.features.sprinklr.ui.SprinklrActivity
import com.tangem.tap.withForegroundActivity
import org.rekotlin.Store
import java.io.File
internal class SprinklrChatOpener(
private val userId: String,
@ -17,7 +18,7 @@ internal class SprinklrChatOpener(
private val store: Store<AppState>,
private val foregroundActivityObserver: ForegroundActivityObserver,
) : ChatOpener {
override fun open(feedbackDataBuilder: (Context) -> String) {
override fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) {
store.dispatch(SprinklrAction.Init(userId, config))
foregroundActivityObserver.withForegroundActivity { activity ->
val intent = Intent(activity, SprinklrActivity::class.java)

View file

@ -2,34 +2,34 @@ package com.tangem.tap.common.chat.opener.implementation
import android.content.Context
import com.tangem.core.analytics.Analytics
import com.tangem.datasource.config.models.ZendeskConfig
import com.tangem.domain.common.LogConfig
import com.tangem.tap.ForegroundActivityObserver
import com.tangem.tap.common.chat.opener.ChatOpener
import com.tangem.datasource.config.models.ZendeskConfig
import com.tangem.tap.withForegroundActivity
import com.tangem.wallet.R
import com.zendesk.logger.Logger
import zendesk.chat.Chat
import zendesk.chat.ChatConfiguration
import zendesk.chat.ChatEngine
import zendesk.chat.ChatProvidersConfiguration
import zendesk.chat.VisitorInfo
import timber.log.Timber
import zendesk.chat.*
import zendesk.configurations.Configuration
import zendesk.messaging.MessagingActivity
import java.io.File
internal class ZendeskChatOpener(
private val userId: String,
private val config: ZendeskConfig,
private val foregroundActivityObserver: ForegroundActivityObserver,
) : ChatOpener {
private var isInitialized = false
override fun open(feedbackDataBuilder: (Context) -> String) {
override fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) {
foregroundActivityObserver.withForegroundActivity { activity ->
initZendeskIfNeeded(activity.applicationContext)
setChatVisitorInfo()
setChatVisitorNote(feedbackDataBuilder(activity))
showMessagingActivity(activity)
sendFeedbackFile(createFeedbackFile(activity))
sendLogsFile(createLogsFile(activity))
}
}
@ -48,8 +48,20 @@ internal class ZendeskChatOpener(
ChatProvidersConfiguration.builder().withVisitorInfo(visitorInfo).build()
}
private fun setChatVisitorNote(note: String) {
Chat.INSTANCE.providers()?.profileProvider()?.setVisitorNote(note)
private fun sendFeedbackFile(feedbackFile: File?) {
if (isInitialized && feedbackFile != null) {
Chat.INSTANCE.providers()?.chatProvider()?.sendFile(feedbackFile) { _, bytesUploaded, _ ->
Timber.d("Log file sent", "bytesUploaded: $bytesUploaded")
}
}
}
private fun sendLogsFile(logsFile: File?) {
if (isInitialized && logsFile != null) {
Chat.INSTANCE.providers()?.chatProvider()?.sendFile(logsFile) { _, bytesUploaded, _ ->
Timber.d("Log file sent", "bytesUploaded: $bytesUploaded")
}
}
}
private fun showMessagingActivity(context: Context) {

View file

@ -1,20 +0,0 @@
package com.tangem.tap.common.di.domain.wallets
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
@Module
@InstallIn(ViewModelComponent::class)
object WalletsDomainModule {
@Provides
@ViewModelScoped
fun providesGetWalletsUseCase(walletsStateHolder: WalletsStateHolder): GetWalletsUseCase {
return GetWalletsUseCase(walletsStateHolder = walletsStateHolder)
}
}

View file

@ -45,6 +45,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.Cronos -> R.drawable.ic_cronos_no_color
Blockchain.Telos, Blockchain.TelosTestnet -> R.drawable.ic_telos_no_color
Blockchain.AlephZero, Blockchain.AlephZeroTestnet -> R.drawable.ic_azero_no_color
Blockchain.OctaSpace, Blockchain.OctaSpaceTestnet -> R.drawable.ic_octaspace_no_color
else -> R.drawable.ic_tangem_logo
}
}

View file

@ -1,25 +0,0 @@
package com.tangem.tap.common.extensions
fun <T> MutableList<T>.removeBy(predicate: (T) -> Boolean): Boolean {
val toRemove = this.filter(predicate)
this.removeAll(toRemove)
return toRemove.isNotEmpty()
}
fun <T> MutableList<T>.replaceBy(item: T, predicate: (T) -> Boolean): Boolean {
val toRemove = this.filter(predicate)
if (toRemove.isEmpty()) return false
val indexes = toRemove.map { indexOf(it) }
this.removeAll(toRemove)
indexes.forEach { this.add(it, item) }
return true
}
fun <T> MutableList<T>.replaceByOrAdd(item: T, predicate: (T) -> Boolean) {
if (!replaceBy(item, predicate)) add(item)
}
fun <T> MutableList<T>.copy(): MutableList<T> {
return this.map { it }.toMutableList()
}

View file

@ -24,13 +24,17 @@ fun BigDecimal.toFormattedString(
df.decimalFormatSymbols = symbols
df.maximumFractionDigits = decimals
df.minimumFractionDigits = 0
df.isGroupingUsed = false
df.isGroupingUsed = true
df.roundingMode = roundingMode
return df.format(this)
}
/**
* To formatted crypto currency string
* Specific method because there is no crypto currency codes in Locale
*/
@Suppress("MagicNumber")
fun BigDecimal.toFormattedCurrencyString(
fun BigDecimal.toFormattedCryptoCurrencyString(
decimals: Int,
currency: String,
roundingMode: RoundingMode = RoundingMode.DOWN,
@ -41,16 +45,34 @@ fun BigDecimal.toFormattedCurrencyString(
} else {
decimals
}
try {
val locale = Locale.getDefault()
val formatter = NumberFormat.getCurrencyInstance(locale)
// first create currency instance for "USD" with Locale default to replace currency to crypto later
Currency.getInstance("USD")?.let { currencyTmp ->
formatter.currency = currencyTmp
formatter.maximumFractionDigits = decimalsForRounding
formatter.minimumFractionDigits = 0
formatter.isGroupingUsed = true
formatter.roundingMode = roundingMode
// cause formatter created for USD, replace Currency with Crypto symbol on right by Locale place
return formatter.format(this).replace(currencyTmp.getSymbol(locale), "$currency")
}
} catch (e: IllegalArgumentException) {
Timber.e(e, "can't parse currency")
}
// if something went wrong - use old way to format
val formattedAmount = this.toFormattedString(
decimals = decimalsForRounding,
roundingMode = roundingMode,
locale = Locale.getDefault(),
)
return "$formattedAmount $currency"
return "$formattedAmount $currency"
}
fun BigDecimal.toFiatRateString(fiatCurrencyName: String, fiatCode: String): String {
try {
val formatter = NumberFormat.getCurrencyInstance()
val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault())
Currency.getInstance(fiatCode)?.let { currency ->
formatter.currency = currency
formatter.maximumFractionDigits = 2
@ -91,7 +113,7 @@ fun BigDecimal.toFormattedFiatValue(
formatWithSpaces: Boolean = false,
): String {
try {
val formatter = NumberFormat.getCurrencyInstance()
val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault())
Currency.getInstance(fiatCode)?.let { currency ->
formatter.currency = currency
formatter.maximumFractionDigits = 2

View file

@ -5,9 +5,9 @@ import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.amountToCreateAccount
import com.tangem.tap.common.TestActions
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.demo.isDemoCard

View file

@ -1,17 +1,12 @@
package com.tangem.tap.common.feedback
import android.os.Build
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
class AdditionalFeedbackInfo {
class EmailWalletInfo(

View file

@ -1,9 +1,9 @@
package com.tangem.tap.common.feedback
import android.content.Context
import com.tangem.datasource.config.models.ChatConfig
import com.tangem.domain.common.TapWorkarounds
import com.tangem.tap.common.chat.ChatManager
import com.tangem.datasource.config.models.ChatConfig
import com.tangem.tap.common.extensions.sendEmail
import com.tangem.tap.common.log.TangemLogCollector
import com.tangem.tap.foregroundActivityObserver
@ -22,32 +22,61 @@ class FeedbackManager(
private val chatManager: ChatManager,
) {
private var sessionFeedbackFile: File? = null
private var sessionLogsFile: File? = null
fun sendEmail(feedbackData: FeedbackData, onFail: ((Exception) -> Unit)? = null) {
feedbackData.prepare(infoHolder)
foregroundActivityObserver.withForegroundActivity { activity ->
val fileLog = createLogFile(activity)
activity.sendEmail(
email = getSupportEmail(),
subject = activity.getString(feedbackData.subjectResId),
message = feedbackData.joinTogether(activity, infoHolder),
file = fileLog,
file = getLogFile(activity),
onFail = onFail,
)
}
}
fun openChat(config: ChatConfig, feedbackData: FeedbackData) {
chatManager.open(config) { context ->
feedbackData.run {
chatManager.open(
config = config,
createLogsFile = ::getLogFile,
createFeedbackFile = { context -> getFeedbackFile(context, feedbackData) },
)
}
private fun getFeedbackFile(context: Context, feedbackData: FeedbackData): File? {
return try {
if (sessionFeedbackFile != null) {
return sessionFeedbackFile
}
val file = File(context.filesDir, FEEDBACK_FILE)
file.delete()
file.createNewFile()
val feedback = feedbackData.run {
prepare(infoHolder)
joinTogether(context, infoHolder)
}
val fileWriter = FileWriter(file)
fileWriter.write(feedback)
fileWriter.close()
sessionFeedbackFile = file
sessionFeedbackFile
} catch (ex: Exception) {
Timber.e(ex, "Can't create the logs file")
null
}
}
private fun createLogFile(context: Context): File? {
private fun getLogFile(context: Context): File? {
return try {
val file = File(context.filesDir, "logs.txt")
if (sessionLogsFile != null) {
return sessionLogsFile
}
val file = File(context.filesDir, LOGS_FILE)
file.delete()
file.createNewFile()
@ -57,7 +86,8 @@ class FeedbackManager(
fileWriter.write(stringWriter.toString())
fileWriter.close()
logCollector.clearLogs()
file
sessionLogsFile = file
sessionLogsFile
} catch (ex: Exception) {
Timber.e(ex, "Can't create the logs file")
null
@ -75,5 +105,7 @@ class FeedbackManager(
companion object {
const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com"
const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com"
const val FEEDBACK_FILE = "feedback.txt"
const val LOGS_FILE = "logs.txt"
}
}

View file

@ -4,8 +4,8 @@ import android.content.Context
import android.util.Log
import coil.ImageLoader
import coil.util.Logger
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
private const val COIL_LOG_TAG = "COIL"
@ -18,14 +18,7 @@ fun createCoilImageLoader(context: Context, logEnabled: Boolean = false): ImageL
logger(CoilTimberLogger())
okHttpClient {
OkHttpClient.Builder()
.addNetworkInterceptor(
HttpLoggingInterceptor { message ->
Timber.tag(COIL_LOG_TAG).d(message)
}
.apply {
level = HttpLoggingInterceptor.Level.BODY
},
)
.addNetworkInterceptor(createNetworkLoggingInterceptor())
.build()
}
}

View file

@ -8,6 +8,7 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.zxing.BarcodeFormat
import com.google.zxing.Result
import com.otaliastudios.cameraview.CameraView.PERMISSION_REQUEST_CODE
import me.dm7.barcodescanner.zxing.ZXingScannerView
@ -16,11 +17,15 @@ import me.dm7.barcodescanner.zxing.ZXingScannerView
[REDACTED_AUTHOR]
*/
class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
private lateinit var mScannerView: ZXingScannerView
override fun onCreate(state: Bundle?) {
super.onCreate(state)
mScannerView = ZXingScannerView(this)
mScannerView = ZXingScannerView(this).apply {
setFormats(listOf(BarcodeFormat.QR_CODE))
}
setContentView(mScannerView)
if (!permissionIsGranted()) requestPermission()

View file

@ -3,7 +3,8 @@ package com.tangem.tap.common.redux
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.preferencesStorage
import com.tangem.tap.tangemSdkManager
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import org.rekotlin.Middleware
class AccessCodeRequestPolicyMiddleware {
@ -19,9 +20,8 @@ class AccessCodeRequestPolicyMiddleware {
}
private fun updateAccessCodeRequestPolicy(scanResponse: ScanResponse) {
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
scanResponse.card.isAccessCodeSet,
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes && scanResponse.card.isAccessCodeSet,
)
}
}

View file

@ -2,11 +2,11 @@ package com.tangem.tap.common.redux.global
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
import com.tangem.tap.common.extensions.replaceBy
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.preferencesStorage
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.utils.extensions.replaceBy
import org.rekotlin.Action
@Suppress("LongMethod", "ComplexMethod")

View file

@ -8,12 +8,13 @@ import com.tangem.core.analytics.Analytics
import com.tangem.datasource.config.models.ShopifyShop
import com.tangem.tap.common.analytics.converters.ShopOrderToEventConverter
import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.common.shop.data.ProductType
import com.tangem.tap.common.shop.data.TangemProduct
import com.tangem.tap.common.shop.data.TotalSum
import com.tangem.tap.common.shop.googlepay.GooglePayService
import com.tangem.tap.common.shop.shopify.ShopifyService
import com.tangem.tap.common.shop.shopify.data.CheckoutItem
import com.tangem.tap.features.shop.domain.models.ProductType
import com.tangem.tap.features.shop.domain.models.ProductType.Companion.SKUS_TO_DISPLAY
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
@ -206,12 +207,6 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
}
}
}
companion object {
const val TANGEM_WALLET_2_CARDS_SKU = "TG115X2-S"
const val TANGEM_WALLET_3_CARDS_SKU = "TG115X3-S"
val SKUS_TO_DISPLAY = listOf(TANGEM_WALLET_2_CARDS_SKU, TANGEM_WALLET_3_CARDS_SKU)
}
}
private fun Storefront.MoneyV2.format(): String {

View file

@ -1,19 +0,0 @@
package com.tangem.tap.common.shop.data
import com.tangem.tap.common.shop.TangemShopService
enum class ProductType(val sku: String) {
WALLET_2_CARDS(TangemShopService.TANGEM_WALLET_2_CARDS_SKU),
WALLET_3_CARDS(TangemShopService.TANGEM_WALLET_3_CARDS_SKU),
;
companion object {
fun fromSku(sku: String): ProductType? {
return when (sku) {
WALLET_2_CARDS.sku -> WALLET_2_CARDS
WALLET_3_CARDS.sku -> WALLET_3_CARDS
else -> null
}
}
}
}

View file

@ -1,5 +1,7 @@
package com.tangem.tap.common.shop.data
import com.tangem.tap.features.shop.domain.models.ProductType
data class TangemProduct(
val type: ProductType,
val totalSum: TotalSum? = null,

View file

@ -0,0 +1,21 @@
package com.tangem.tap.data
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.firstOrNull
// FIXME: Workaround, remove it once the normal UserWalletsStore has been implemented
// [REDACTED_JIRA]
internal class RuntimeUserWalletsStore(
private val walletsStateHolder: WalletsStateHolder,
) : UserWalletsStore {
override suspend fun getSyncOrNull(key: UserWalletId): UserWallet? {
return walletsStateHolder.userWalletsListManager
?.userWallets
?.firstOrNull()
?.singleOrNull { it.walletId == key }
}
}

View file

@ -1,42 +1,43 @@
package com.tangem.tap.di
import android.content.Context
import androidx.fragment.app.FragmentActivity
import com.tangem.TangemSdk
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.sdk.extensions.initWithBiometrics
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
import com.tangem.tap.userTokensRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(ActivityComponent::class)
@InstallIn(SingletonComponent::class)
internal object ActivityModule {
@Provides
@ActivityScoped
fun provideTangemSdk(@ActivityContext context: Context): TangemSdk {
return TangemSdk.initWithBiometrics(context as FragmentActivity, TangemSdkManager.config)
@Singleton
fun provideTangemSdkManager(
@ApplicationContext context: Context,
cardSdkConfigRepository: CardSdkConfigRepository,
): TangemSdkManager {
return TangemSdkManager(cardSdkConfigRepository = cardSdkConfigRepository, resources = context.resources)
}
@Provides
@ActivityScoped
fun provideTangemSdkManager(@ActivityContext context: Context, tangemSdk: TangemSdk): TangemSdkManager {
return TangemSdkManager(tangemSdk, context)
}
@Provides
@ActivityScoped
fun provideScanCardUseCase(tangemSdk: TangemSdk, tangemSdkManager: TangemSdkManager): ScanCardUseCase {
@Singleton
fun provideScanCardUseCase(
tangemSdkManager: TangemSdkManager,
cardSdkConfigRepository: CardSdkConfigRepository,
): ScanCardUseCase {
return ScanCardUseCase(
tangemSdk = tangemSdk,
scanCardRepository = DefaultScanCardRepository(userTokensRepository, tangemSdkManager),
cardSdkConfigRepository = cardSdkConfigRepository,
scanCardRepository = DefaultScanCardRepository(
userTokensRepository = userTokensRepository,
tangemSdkManager = tangemSdkManager,
),
)
}
}

View file

@ -0,0 +1,21 @@
package com.tangem.tap.di.data
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.tap.data.RuntimeUserWalletsStore
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 UserWalletsStoreModule {
@Provides
@Singleton
fun provideUserWalletsStore(walletsStateHolder: WalletsStateHolder): UserWalletsStore {
return RuntimeUserWalletsStore(walletsStateHolder)
}
}

View file

@ -0,0 +1,51 @@
package com.tangem.tap.di.domain
import com.tangem.domain.card.*
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.demo.IsDemoCardUseCase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
@Module
@InstallIn(ViewModelComponent::class)
internal object CardDomainModule {
@Provides
@ViewModelScoped
fun provideGetBiometricsStatusUseCase(
cardSdkConfigRepository: CardSdkConfigRepository,
): GetBiometricsStatusUseCase {
return GetBiometricsStatusUseCase(cardSdkConfigRepository = cardSdkConfigRepository)
}
@Provides
@ViewModelScoped
fun provideSetAccessCodeRequestPolicyUseCase(
cardSdkConfigRepository: CardSdkConfigRepository,
): SetAccessCodeRequestPolicyUseCase {
return SetAccessCodeRequestPolicyUseCase(cardSdkConfigRepository = cardSdkConfigRepository)
}
@Provides
@ViewModelScoped
fun provideGetAccessCodeSavingStatusUseCase(
cardSdkConfigRepository: CardSdkConfigRepository,
): GetAccessCodeSavingStatusUseCase {
return GetAccessCodeSavingStatusUseCase(cardSdkConfigRepository = cardSdkConfigRepository)
}
@Provides
@ViewModelScoped
fun provideGetCardWasScannedUseCase(cardRepository: CardRepository): GetCardWasScannedUseCase {
return GetCardWasScannedUseCase(cardRepository = cardRepository)
}
@Provides
@ViewModelScoped
fun provideIsDemoCardUseCase(): IsDemoCardUseCase = IsDemoCardUseCase(config = DemoConfig())
}

View file

@ -0,0 +1,18 @@
package com.tangem.tap.di.domain
import com.tangem.domain.card.*
import com.tangem.tap.domain.scanCard.DefaultScanCardProcessor
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 CardLegacyDomainModule {
@Provides
@Singleton
fun provideScanCardUseCase(): ScanCardProcessor = DefaultScanCardProcessor()
}

View file

@ -0,0 +1,20 @@
package com.tangem.tap.di.domain
import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
@Module
@InstallIn(ViewModelComponent::class)
internal object SettingsDomainModule {
@Provides
@ViewModelScoped
fun providesGetWalletsUseCase(settingsRepository: SettingsRepository): IsUserAlreadyRateAppUseCase {
return IsUserAlreadyRateAppUseCase(settingsRepository = settingsRepository)
}
}

View file

@ -0,0 +1,63 @@
package com.tangem.tap.di.domain
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.tokens.repository.TokensRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
@Module
@InstallIn(ViewModelComponent::class)
internal object TokensDomainModule {
@Provides
@ViewModelScoped
fun provideGetTokenListUseCase(
tokensRepository: TokensRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetTokenListUseCase {
return GetTokenListUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers)
}
@Provides
@ViewModelScoped
fun provideGetPrimaryCurrencyUseCase(
tokensRepository: TokensRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetPrimaryCurrencyUseCase {
return GetPrimaryCurrencyUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers)
}
@Provides
@ViewModelScoped
fun provideToggleTokenListGroupingUseCase(
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): ToggleTokenListGroupingUseCase {
return ToggleTokenListGroupingUseCase(networksRepository, dispatchers)
}
@Provides
@ViewModelScoped
fun provideToggleTokenListSortingUseCase(dispatchers: CoroutineDispatcherProvider): ToggleTokenListSortingUseCase {
return ToggleTokenListSortingUseCase(dispatchers)
}
@Provides
@ViewModelScoped
fun provideApplyTokenListSortingUseCase(
tokensRepository: TokensRepository,
dispatchers: CoroutineDispatcherProvider,
): ApplyTokenListSortingUseCase {
return ApplyTokenListSortingUseCase(tokensRepository, dispatchers)
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.tap.di.domain
import com.tangem.domain.tokens.*
import com.tangem.domain.txhistory.repository.TxHistoryRepository
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
@Module
@InstallIn(ViewModelComponent::class)
internal object TxHistoryDomainModule {
@Provides
@ViewModelScoped
fun provideGetTxHistoryItemsCountUseCase(txHistoryRepository: TxHistoryRepository): GetTxHistoryItemsCountUseCase {
return GetTxHistoryItemsCountUseCase(repository = txHistoryRepository)
}
@Provides
@ViewModelScoped
fun provideGetTxHistoryItemsUseCase(txHistoryRepository: TxHistoryRepository): GetTxHistoryItemsUseCase {
return GetTxHistoryItemsUseCase(repository = txHistoryRepository)
}
}

View file

@ -0,0 +1,27 @@
package com.tangem.tap.di.domain
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.walletmanager.DefaultWalletManagersFacade
import com.tangem.domain.walletmanager.WalletManagersFacade
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 WalletManagersFacadeModule {
@Provides
@Singleton
fun provideWalletManagersFacade(
walletManagersStore: WalletManagersStore,
userWalletsStore: UserWalletsStore,
configManager: ConfigManager,
): WalletManagersFacade {
return DefaultWalletManagersFacade(walletManagersStore, userWalletsStore, configManager)
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.tap.di.domain
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
@Module
@InstallIn(ViewModelComponent::class)
internal object WalletsDomainModule {
@Provides
@ViewModelScoped
fun providesGetWalletsUseCase(walletsStateHolder: WalletsStateHolder): GetWalletsUseCase {
return GetWalletsUseCase(walletsStateHolder = walletsStateHolder)
}
@Provides
@ViewModelScoped
fun providesSaveWalletUseCase(walletsStateHolder: WalletsStateHolder): SaveWalletUseCase {
return SaveWalletUseCase(walletsStateHolder = walletsStateHolder)
}
@Provides
@ViewModelScoped
fun providesGetExploreUrlUseCase(walletsManagersFacade: WalletManagersFacade): GetExploreUrlUseCase {
return GetExploreUrlUseCase(walletsManagersFacade = walletsManagersFacade)
}
}

View file

@ -1,6 +1,6 @@
package com.tangem.tap.domain
import android.content.Context
import android.content.res.Resources
import androidx.annotation.StringRes
import com.tangem.Message
import com.tangem.TangemSdk
@ -14,14 +14,13 @@ import com.tangem.common.usersCode.UserCodeRepository
import com.tangem.core.analytics.Analytics
import com.tangem.crypto.bip39.DefaultMnemonic
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.ScanTask
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.pins.CheckUserCodesCommand
import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
import com.tangem.tap.common.analytics.events.Basic
@ -38,7 +37,10 @@ import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
@Suppress("TooManyFunctions")
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigRepository, private val resources: Resources) {
private val tangemSdk: TangemSdk
get() = cardSdkConfigRepository.sdk
private val userCodeRepository by lazy {
UserCodeRepository(
@ -63,7 +65,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
messageRes: Int? = null,
allowsRequestAccessCodeFromRepository: Boolean = false,
): CompletionResult<ScanResponse> {
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
val message = Message(resources.getString(messageRes ?: R.string.initial_message_scan_header))
return runTaskAsyncReturnOnMain(
runnable = ScanProductTask(
card = null,
@ -80,7 +82,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsync(
CreateProductWalletTask(scanResponse.cardTypesResolver),
scanResponse.card.cardId,
Message(context.getString(R.string.initial_message_create_wallet_body)),
Message(resources.getString(R.string.initial_message_create_wallet_body)),
)
}
@ -92,7 +94,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
is CompletionResult.Success -> runTaskAsync(
CreateProductWalletTask(scanResponse.cardTypesResolver, seedResult.data),
scanResponse.card.cardId,
Message(context.getString(R.string.initial_message_create_wallet_body)),
Message(resources.getString(R.string.initial_message_create_wallet_body)),
)
is CompletionResult.Failure -> CompletionResult.Failure(seedResult.error)
@ -111,7 +113,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
CreateWalletAndRescanTask(),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)),
initialMessage = Message(resources.getString(R.string.initial_message_create_wallet_body)),
)
.map { CardDTO(it) }
}
@ -127,7 +129,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
runnable = ResetToFactorySettingsTask(),
cardId = cardId,
initialMessage = Message(context.getString(R.string.card_settings_reset_card_to_factory)),
initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)),
)
.map { CardDTO(it) }
}
@ -154,7 +156,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changePasscode(null),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_change_passcode_body)),
initialMessage = Message(resources.getString(R.string.initial_message_change_passcode_body)),
)
}
@ -162,7 +164,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.changeAccessCode(null),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_change_access_code_body)),
initialMessage = Message(resources.getString(R.string.initial_message_change_access_code_body)),
)
}
@ -170,15 +172,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
SetUserCodeCommand.resetUserCodes(),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_tap_header)),
)
}
suspend fun checkUserCodes(cardId: String?): CompletionResult<CheckUserCodesResponse> {
return runTaskAsyncReturnOnMain(
CheckUserCodesCommand(),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_tap_header)),
initialMessage = Message(resources.getString(R.string.initial_message_tap_header)),
)
}
@ -186,7 +180,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
SetUserCodeRecoveryAllowedTask(enabled),
cardId,
initialMessage = Message(context.getString(R.string.initial_message_tap_header)),
initialMessage = Message(resources.getString(R.string.initial_message_tap_header)),
)
}
@ -197,7 +191,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return runTaskAsyncReturnOnMain(
runnable = ScanTask(allowRequestAccessCodeFromRepository),
cardId = cardId,
initialMessage = Message(context.getString(R.string.initial_message_tap_header)),
initialMessage = Message(resources.getString(R.string.initial_message_tap_header)),
)
.map { CardDTO(it) }
}
@ -233,8 +227,9 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
}
}
@Deprecated("TangemSdkManager shouldn't returns a string from resources")
fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
return context.getString(stringResId, *formatArgs)
return resources.getString(stringResId, *formatArgs)
}
fun setAccessCodeRequestPolicy(useBiometricsForAccessCode: Boolean) {

View file

@ -1,7 +1,7 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.extensions.removeBy
import com.tangem.utils.extensions.removeByReplace
import com.tangem.wallet.R
import java.util.concurrent.CopyOnWriteArrayList
@ -44,12 +44,12 @@ class WarningMessagesManager {
}
fun removeWarnings(origin: WarningMessage.Origin) {
warningsList.removeBy { it.origin == origin }
warningsList.removeByReplace { it.origin == origin }
sortByPriority()
}
fun removeWarnings(messageRes: Int) {
warningsList.removeBy { it.messageResId == messageRes }
warningsList.removeByReplace { it.messageResId == messageRes }
}
fun containsWarning(warning: WarningMessage) = warning in warningsList

View file

@ -1,35 +0,0 @@
package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.common.card.EllipticCurve
import java.math.BigDecimal
@Suppress("MagicNumber")
fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? {
return when (this) {
Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(1.5) else BigDecimal.ONE
Blockchain.XRP -> BigDecimal(10)
else -> null
}
}
fun Blockchain.minimalAmount(): BigDecimal {
return 1.toBigDecimal().movePointLeft(decimals())
}
fun Blockchain.getPrimaryCurve(): EllipticCurve? {
return when {
getSupportedCurves().contains(EllipticCurve.Secp256k1) -> {
EllipticCurve.Secp256k1
}
getSupportedCurves().contains(EllipticCurve.Ed25519) -> {
EllipticCurve.Ed25519
}
else -> {
null
}
}
}
private const val NODL = "NODL"

View file

@ -1,24 +1,17 @@
package com.tangem.tap.domain.extensions
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.common.TwinCardNumber
import com.tangem.domain.common.getTwinCardNumber
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.userwallets.Artwork
import com.tangem.operations.attestation.CardVerifyAndGetInfo
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.features.wallet.redux.Artwork
val CardDTO.remainingSignatures: Int?
get() = this.wallets.firstOrNull()?.remainingSignatures
val CardDTO.isWalletDataSupported: Boolean
get() = this.firmwareVersion.major >= 4
val CardDTO.isFirmwareMultiwalletAllowed: Boolean
get() = firmwareVersion >= FirmwareVersion.MultiWalletAvailable && settings.maxWalletsCount > 1
val CardDTO.isHdWalletAllowedByApp: Boolean
get() = settings.isHDWalletAllowed
@ -58,16 +51,4 @@ suspend fun CardDTO.getOrLoadCardArtworkUrl(cardInfo: Result<CardVerifyAndGetInf
is Result.Failure -> ifAnyError()
}
}
fun CardDTO.getArtworkUrl(artworkId: String?): String? {
return when {
artworkId != null -> {
OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
}
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
else -> null
}
}

View file

@ -2,21 +2,22 @@ package com.tangem.tap.domain.scanCard
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
// TODO: Remove this object after feature toggle was removed and use ScanCardUseCase instead
internal object ScanCardProcessor {
internal class DefaultScanCardProcessor : ScanCardProcessor {
private val isNewCardScanningEnabled: Boolean
get() = store.state.daggerGraphState
.get(DaggerGraphState::customTokenFeatureToggles)
.isNewCardScanningEnabled
suspend fun scan(
cardId: String? = null,
allowsRequestAccessCodeFromRepository: Boolean = false,
override suspend fun scan(
cardId: String?,
allowsRequestAccessCodeFromRepository: Boolean,
): CompletionResult<ScanResponse> {
return if (isNewCardScanningEnabled) {
UseCaseScanProcessor.scan(cardId, allowsRequestAccessCodeFromRepository)
@ -26,15 +27,15 @@ internal object ScanCardProcessor {
}
@Suppress("LongParameterList")
suspend fun scan(
analyticsEvent: AnalyticsEvent? = null,
cardId: String? = null,
onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {},
onScanStateChange: suspend (scanInProgress: Boolean) -> Unit = {},
onWalletNotCreated: suspend () -> Unit = {},
disclaimerWillShow: () -> Unit = {},
onFailure: suspend (error: TangemError) -> Unit = {},
onSuccess: suspend (scanResponse: ScanResponse) -> Unit = {},
override suspend fun scan(
analyticsEvent: AnalyticsEvent?,
cardId: String?,
onProgressStateChange: suspend (showProgress: Boolean) -> Unit,
onScanStateChange: suspend (scanInProgress: Boolean) -> Unit,
onWalletNotCreated: suspend () -> Unit,
disclaimerWillShow: () -> Unit,
onFailure: suspend (error: TangemError) -> Unit,
onSuccess: suspend (scanResponse: ScanResponse) -> Unit,
) {
if (isNewCardScanningEnabled) {
UseCaseScanProcessor.scan(

View file

@ -6,7 +6,7 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.TapWorkarounds.canSkipBackup

View file

@ -3,7 +3,7 @@ package com.tangem.tap.domain.scanCard
import arrow.fx.coroutines.resourceScope
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.card.ScanCardException

View file

@ -3,7 +3,7 @@ package com.tangem.tap.domain.scanCard.chains
import arrow.core.Either
import arrow.core.right
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.core.chain.Chain
import com.tangem.domain.models.scan.ScanResponse

View file

@ -31,8 +31,7 @@ class ResetToFactorySettingsTask : CardSessionRunnable<Card> {
}
private fun resetBackup(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
val backupStatus = session.environment.card?.backupStatus
if (backupStatus == null || backupStatus == Card.BackupStatus.NoBackup) {
if (session.environment.card?.backupStatus?.isActive != true) {
callback(CompletionResult.Success(session.environment.card!!))
return
}

View file

@ -20,6 +20,7 @@ import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.TwinsHelper
import com.tangem.domain.common.extensions.getPrimaryCurve
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
@ -30,7 +31,6 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.files.ReadFilesTask
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
import com.tangem.tap.domain.TapSdkError
import com.tangem.tap.domain.extensions.getPrimaryCurve
import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope

View file

@ -10,7 +10,7 @@ import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.datasource.files.AndroidFileReader
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.models.scan.CardDTO
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.tap.domain.tokens.converters.CurrencyConverter
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.wallet.models.Currency
@ -79,8 +79,8 @@ class UserTokensRepository(
private fun List<Currency>.toUserTokensResponse(): UserTokensResponse {
return UserTokensResponse(
tokens = CurrencyConverter.convertList(input = this),
group = GROUP_DEFAULT_VALUE,
sort = SORT_DEFAULT_VALUE,
group = UserTokensResponse.GroupType.NONE,
sort = UserTokensResponse.SortType.MANUAL,
)
}
@ -121,8 +121,6 @@ class UserTokensRepository(
private fun getUserWalletId(card: CardDTO): String? = UserWalletIdBuilder.card(card).build()?.stringValue
companion object {
private const val GROUP_DEFAULT_VALUE = "none"
private const val SORT_DEFAULT_VALUE = "manual"
private const val NOT_FOUND_HTTP_CODE = "404"
// TODO("After adding DI") get dependencies by DI

View file

@ -7,6 +7,7 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.files.FileReader
import com.tangem.tap.features.wallet.models.Currency
@Deprecated("Use [com.tangem.datasource.local.token.UserTokensStore] instead.")
class UserTokensStorageService(private val fileReader: FileReader) {
private val userTokensAdapter: JsonAdapter<UserTokensResponse> =
MoshiConverter.networkMoshi.adapter(UserTokensResponse::class.java)

View file

@ -9,10 +9,10 @@ import com.tangem.common.flatMap
import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.common.extensions.replaceByOrAdd
import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository
import com.tangem.tap.domain.userWalletList.utils.publicInformation
import com.tangem.utils.extensions.plusOrReplace
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -29,12 +29,9 @@ internal class DefaultUserWalletsPublicInformationRepository(
getAll()
.flatMap { savedInformation ->
val infoToSave = withContext(Dispatchers.Default) {
savedInformation.toMutableList()
.apply {
replaceByOrAdd(userWallet.publicInformation) {
userWallet.walletId == it.walletId
}
}
savedInformation.plusOrReplace(userWallet.publicInformation) {
userWallet.walletId == it.walletId
}
}
save(infoToSave)

View file

@ -16,7 +16,6 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.common.TestActions
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.replaceByOrAdd
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.domain.walletStores.WalletStoresError
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
@ -32,6 +31,7 @@ import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.plusOrReplace
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.firstOrNull
import timber.log.Timber
@ -428,11 +428,10 @@ internal class DefaultWalletAmountsRepository(
private suspend fun updateWalletManagerInStorage(userWalletId: UserWalletId, walletManager: WalletManager) =
withContext(Dispatchers.Default) {
WalletManagerStorage.update { prevManagers ->
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty().toMutableList().apply {
replaceByOrAdd(walletManager) {
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty()
.plusOrReplace(walletManager) {
it.wallet.blockchain == walletManager.wallet.blockchain
}
}
prevManagers.apply {
set(userWalletId, newManagersForUserWallet)

View file

@ -9,13 +9,13 @@ import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.extensions.makeWalletManagerForApp
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.walletStores.WalletStoresError
import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage
import kotlinx.coroutines.Dispatchers

View file

@ -5,7 +5,7 @@ import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.core.TangemError
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.domain.common.extensions.amountToCreateAccount
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.demo.DemoHelper

View file

@ -3,10 +3,9 @@ package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
@ -36,7 +35,6 @@ import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.util.*
import java.util.concurrent.TimeUnit
@ -56,8 +54,9 @@ class WalletConnectManager {
.addInterceptor(RetryInterceptor())
.build()
}
private val interceptor by lazy {
HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }
createNetworkLoggingInterceptor()
}
private var sessions: MutableMap<Topic, WalletConnectActiveData> = mutableMapOf()
@ -226,7 +225,6 @@ class WalletConnectManager {
}
private fun onSessionClosed(session: WCSession) {
Analytics.send(WalletConnect.SessionDisconnected())
sessions.remove(session.topic)
walletConnectRepository.removeSession(session)
store.dispatchOnMain(WalletConnectAction.RemoveSession(session))
@ -394,7 +392,6 @@ class WalletConnectManager {
),
)
}
Analytics.send(WalletConnect.NewSessionEstablished(""))
}
}
}

View file

@ -6,12 +6,12 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.Companion.toKeccak
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.extensions.hexToBigDecimal
import com.tangem.blockchain.extensions.isAscii
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toDecompressedPublicKey
import com.tangem.common.extensions.toHexString
@ -22,7 +22,6 @@ import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.analytics.events.Basic.TransactionSent.MemoType
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTradeOrder
@ -35,8 +34,8 @@ import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTrans
import com.tangem.tap.features.details.redux.walletconnect.*
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import com.tangem.tap.tangemSdkManager
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.*
import timber.log.Timber
@ -76,7 +75,8 @@ class WalletConnectSdkHelper {
val transactionData = TransactionData(
amount = Amount(value, wallet.blockchain),
fee = Amount(fee, wallet.blockchain),
// TODO refactoring
fee = Fee.Common(Amount(fee, wallet.blockchain)),
sourceAddress = transaction.from,
destinationAddress = transaction.to!!,
extras = EthereumTransactionExtras(
@ -161,7 +161,10 @@ class WalletConnectSdkHelper {
private suspend fun sendTransaction(data: WcTransactionData, cardId: String?): String? {
val result = (data.walletManager as TransactionSender).send(
transactionData = data.transaction,
signer = CommonSigner(tangemSdk, cardId),
signer = CommonSigner(
tangemSdk = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk,
cardId = cardId,
),
)
return when (result) {
SimpleResult.Success -> {
@ -170,7 +173,6 @@ class WalletConnectSdkHelper {
HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash
}
is SimpleResult.Failure -> {
(result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.TransactionError(it)) }
Timber.e(result.error as BlockchainSdkError)
null
}
@ -182,7 +184,6 @@ class WalletConnectSdkHelper {
transactionData = data.transaction,
nonce = null,
blockchain = data.walletManager.wallet.blockchain,
gasLimit = null,
) ?: return null
val command = SignHashCommand(
@ -201,7 +202,6 @@ class WalletConnectSdkHelper {
).toHexString()
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.SignError(it)) }
Timber.e(result.error.customMessage)
null
}
@ -240,7 +240,6 @@ class WalletConnectSdkHelper {
)
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.TransactionError(it)) }
Timber.e(result.error.customMessage)
null
}
@ -334,7 +333,6 @@ class WalletConnectSdkHelper {
)
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.SignError(it)) }
Timber.e(result.error.customMessage)
null
}

View file

@ -2,9 +2,10 @@ package com.tangem.tap.domain.walletconnect2.data
import android.app.Application
import arrow.core.flatten
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WcJrpcMethods
import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer
import com.tangem.tap.domain.walletconnect2.domain.WcRequest
import com.tangem.tap.domain.walletconnect2.domain.models.*
@ -25,6 +26,7 @@ import javax.inject.Inject
class WalletConnectRepositoryImpl @Inject constructor(
private val application: Application,
private val wcRequestDeserializer: WcJrpcRequestsDeserializer,
private val analyticsHandler: AnalyticsEventHandler,
) : WalletConnectRepository {
private var sessionProposal: Wallet.Model.SessionProposal? = null
@ -37,6 +39,8 @@ class WalletConnectRepositoryImpl @Inject constructor(
private val _activeSessions: MutableSharedFlow<List<WalletConnectSession>> = MutableSharedFlow()
override val activeSessions: Flow<List<WalletConnectSession>> = _activeSessions
private var currentSessions: List<WalletConnectSession> = emptyList()
/**
* @param projectId Project ID at https://cloud.walletconnect.com/
*/
@ -127,8 +131,12 @@ class WalletConnectRepositoryImpl @Inject constructor(
// we can send approval automatically, because in WC 2.0 the list of chains is approved when
// initial connection is established
sendRequest(
topic = sessionRequest.topic,
id = sessionRequest.request.id,
RequestData(
topic = sessionRequest.topic,
requestId = sessionRequest.request.id,
blockchain = sessionRequest.chainId.toString(),
method = WcJrpcMethods.WALLET_ADD_ETHEREUM_CHAIN.code,
),
result = "",
)
}
@ -142,6 +150,7 @@ class WalletConnectRepositoryImpl @Inject constructor(
id = sessionRequest.request.id,
metaUrl = sessionRequest.peerMetaData?.url ?: "",
metaName = sessionRequest.peerMetaData?.name ?: "",
method = sessionRequest.request.method,
),
)
}
@ -263,7 +272,12 @@ class WalletConnectRepositoryImpl @Inject constructor(
params = sessionApproval,
onSuccess = {
Timber.d("Approved successfully: $it")
Analytics.send(WalletConnect.NewSessionEstablished(sessionProposal.name))
analyticsHandler.send(
WalletConnect.NewSessionEstablished(
dAppName = sessionProposal.name,
dAppUrl = sessionProposal.url,
),
)
},
onError = {
Timber.d("Error while approving: $it")
@ -278,23 +292,56 @@ class WalletConnectRepositoryImpl @Inject constructor(
)
}
override fun sendRequest(topic: String, id: Long, result: String) {
override fun sendRequest(requestData: RequestData, result: String) {
val session = currentSessions.find { it.topic == requestData.topic }
analyticsHandler.send(
WalletConnect.RequestHandled(
WalletConnect.RequestHandledParams(
dAppName = session?.name ?: "",
dAppUrl = session?.url ?: "",
methodName = requestData.method,
blockchain = requestData.blockchain,
),
),
)
Web3Wallet.respondSessionRequest(
params = Wallet.Params.SessionRequestResponse(
sessionTopic = topic,
sessionTopic = requestData.topic,
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcResult(
id = id,
id = requestData.requestId,
result = result,
),
),
onSuccess = {},
onError = {
Analytics.send(WalletConnect.TransactionError(it.throwable))
WalletConnect.RequestHandledParams(
dAppName = session?.name ?: "",
dAppUrl = session?.url ?: "",
methodName = requestData.method,
blockchain = requestData.blockchain,
errorCode = WalletConnectError.ValidationError.toString(),
)
},
)
}
override fun rejectRequest(topic: String, id: Long) {
override fun rejectRequest(requestData: RequestData, error: WalletConnectError) {
val session = currentSessions.find { it.topic == requestData.topic }
analyticsHandler.send(
WalletConnect.RequestHandled(
WalletConnect.RequestHandledParams(
dAppName = session?.name ?: "",
dAppUrl = session?.url ?: "",
methodName = requestData.method,
blockchain = requestData.blockchain,
errorCode = error.toString(),
),
),
)
cancelRequest(requestData.topic, requestData.requestId)
}
override fun cancelRequest(topic: String, id: Long) {
Web3Wallet.respondSessionRequest(
params = Wallet.Params.SessionRequestResponse(
sessionTopic = topic,
@ -325,10 +372,16 @@ class WalletConnectRepositoryImpl @Inject constructor(
}
override fun disconnect(topic: String) {
val session = currentSessions.find { it.topic == topic }
Web3Wallet.disconnectSession(
params = Wallet.Params.SessionDisconnect(topic),
onSuccess = {
Analytics.send(WalletConnect.SessionDisconnected())
analyticsHandler.send(
WalletConnect.SessionDisconnected(
dAppName = session?.name ?: "",
dAppUrl = session?.url ?: "",
),
)
updateSessions()
Timber.d("Disconnected successfully: $it")
},
@ -364,6 +417,7 @@ class WalletConnectRepositoryImpl @Inject constructor(
)
}
Timber.d("Available sessions: $availableSessions")
currentSessions = availableSessions
_activeSessions.emit(availableSessions)
}
}

View file

@ -1,7 +1,5 @@
package com.tangem.tap.domain.walletconnect2.domain
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
import com.tangem.tap.domain.walletconnect2.domain.models.*
@ -148,8 +146,8 @@ class WalletConnectInteractor(
walletConnectRepository.disconnect(topic)
}
fun rejectRequest(topic: String, id: Long) {
walletConnectRepository.rejectRequest(topic, id)
fun cancelRequest(topic: String, id: Long) {
walletConnectRepository.cancelRequest(topic, id)
}
private suspend fun handleRequest(sessionRequest: WalletConnectEvents.SessionRequest) {
@ -164,16 +162,23 @@ class WalletConnectInteractor(
null
}
}
val networkId = sessionRequest.chainId?.let { blockchainHelper.chainIdToNetworkIdOrNull(it) } ?: ""
val requestData = RequestData(
topic = sessionRequest.topic,
requestId = sessionRequest.id,
blockchain = networkId,
method = sessionRequest.method,
)
if (error != null) {
walletConnectRepository.rejectRequest(sessionRequest.topic, sessionRequest.id)
walletConnectRepository.rejectRequest(requestData, error)
return
}
when (sessionRequest.request) {
is WcRequest.BnbCancel -> Unit
is WcRequest.BnbTxConfirm -> walletConnectRepository.sendRequest(
topic = sessionRequest.topic,
id = sessionRequest.id,
requestData = requestData,
result = "",
)
else -> {
@ -212,16 +217,18 @@ class WalletConnectInteractor(
Timber.d("Signed hash: $signedHash")
val requestData = RequestData(
topic = request.topic,
requestId = request.requestId,
blockchain = networkId,
method = currentRequest.method,
)
if (signedHash == null) {
walletConnectRepository.rejectRequest(
topic = request.topic,
id = request.requestId,
)
walletConnectRepository.rejectRequest(requestData, WalletConnectError.SigningError)
} else {
Analytics.send(WalletConnect.RequestSigned())
walletConnectRepository.sendRequest(
topic = request.topic,
id = request.requestId,
requestData = requestData,
result = signedHash,
)
}

View file

@ -1,9 +1,6 @@
package com.tangem.tap.domain.walletconnect2.domain
import com.tangem.tap.domain.walletconnect2.domain.models.Account
import com.tangem.tap.domain.walletconnect2.domain.models.NetworkNamespace
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectSession
import com.tangem.tap.domain.walletconnect2.domain.models.*
import kotlinx.coroutines.flow.Flow
interface WalletConnectRepository {
@ -24,7 +21,9 @@ interface WalletConnectRepository {
fun reject()
fun sendRequest(topic: String, id: Long, result: String)
fun sendRequest(requestData: RequestData, result: String)
fun rejectRequest(topic: String, id: Long)
fun rejectRequest(requestData: RequestData, error: WalletConnectError)
fun cancelRequest(topic: String, id: Long)
}

View file

@ -0,0 +1,8 @@
package com.tangem.tap.domain.walletconnect2.domain.models
data class RequestData(
val topic: String,
val requestId: Long,
val method: String,
val blockchain: String,
)

View file

@ -7,4 +7,6 @@ sealed class WalletConnectError : Exception() {
data class ExternalApprovalError(override val message: String?) : WalletConnectError()
object WrongUserWallet : WalletConnectError()
object UnsupportedMethod : WalletConnectError()
object SigningError : WalletConnectError()
object ValidationError : WalletConnectError()
}

View file

@ -24,5 +24,6 @@ sealed interface WalletConnectEvents {
val id: Long,
val metaName: String,
val metaUrl: String,
val method: String,
) : WalletConnectEvents
}

View file

@ -1,16 +1,7 @@
package com.tangem.tap.features.demo
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.toBlockchainSdkError
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.CompletionResult
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.common.demo.DemoConfig
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.details.redux.DetailsAction
@ -21,13 +12,6 @@ import com.tangem.tap.store
import com.tangem.wallet.R
import org.rekotlin.Action
/**
[REDACTED_AUTHOR]
*/
interface DemoMiddleware {
fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean
}
object DemoHelper {
val config = DemoConfig()
@ -66,59 +50,10 @@ object DemoHelper {
return false
}
fun injectDemoBalance(walletManager: WalletManager?) {
val manager = walletManager ?: return
val blockchain = walletManager.wallet.blockchain
val amount = config.getBalance(blockchain)
manager.wallet.setAmount(amount)
}
private fun getScanResponse(appState: () -> AppState?): ScanResponse? {
val state = appState() ?: return null
return state.globalState.onboardingState.onboardingManager?.scanResponse
?: state.globalState.scanResponse
}
}
class DemoTransactionSender(private val walletManager: WalletManager) : TransactionSender {
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
val blockchain = walletManager.wallet.blockchain
return Result.Success(
listOf(
Amount(0.0001.toBigDecimal(), blockchain),
Amount(0.0002.toBigDecimal(), blockchain),
Amount(0.0003.toBigDecimal(), blockchain),
),
)
}
@Suppress("MagicNumber")
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
val dataToSign = randomString(32).toByteArray()
val signerResponse = signer.sign(
hash = dataToSign,
publicKey = walletManager.wallet.publicKey,
)
return when (signerResponse) {
is CompletionResult.Success -> SimpleResult.Failure(Exception(ID).toBlockchainSdkError())
is CompletionResult.Failure -> SimpleResult.fromTangemSdkError(signerResponse.error)
}
}
private fun randomInt(from: Int, to: Int): Int = kotlin.random.Random.nextInt(from, to)
private fun randomString(length: Int): String {
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
return (1..length)
.map { randomInt(0, charPool.size) }
.map(charPool::get)
.joinToString("")
}
companion object {
val ID = DemoTransactionSender::class.java.simpleName
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.tap.features.demo
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.ScanResponse
import org.rekotlin.Action
/**
[REDACTED_AUTHOR]
*/
interface DemoMiddleware {
fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean
}

View file

@ -1,10 +1,10 @@
package com.tangem.tap.features.demo
import com.tangem.common.extensions.guard
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.common.demo.DemoConfig
import com.tangem.domain.common.extensions.makePrimaryWalletManager
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.ProgressState

View file

@ -0,0 +1,51 @@
package com.tangem.tap.features.demo
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.CompletionResult
import kotlin.random.Random
class DemoTransactionSender(private val walletManager: WalletManager) : TransactionSender {
override suspend fun getFee(amount: Amount, destination: String): Result<TransactionFee> {
val blockchain = walletManager.wallet.blockchain
return Result.Success(
TransactionFee.Choosable(
minimum = Fee.Common(Amount(minimumFee, blockchain)),
normal = Fee.Common(Amount(normalFee, blockchain)),
priority = Fee.Common(Amount(priorityFee, blockchain)),
),
)
}
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
val signerResponse = signer.sign(
hash = getDataToSign(),
publicKey = walletManager.wallet.publicKey,
)
return when (signerResponse) {
is CompletionResult.Success -> SimpleResult.Failure(Exception(ID).toBlockchainSdkError())
is CompletionResult.Failure -> SimpleResult.fromTangemSdkError(signerResponse.error)
}
}
private fun getDataToSign(): ByteArray {
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
return IntRange(start = 1, endInclusive = 32)
.map { Random.nextInt(from = 0, until = charPool.size) }
.map(charPool::get)
.joinToString(separator = "")
.toByteArray()
}
companion object {
val ID: String = DemoTransactionSender::class.java.simpleName
private val minimumFee = 0.0001.toBigDecimal()
private val normalFee = 0.0002.toBigDecimal()
private val priorityFee = 0.0003.toBigDecimal()
}
}

View file

@ -12,6 +12,8 @@ import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
@ -23,9 +25,6 @@ import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.model.builders.UserWalletBuilder
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
import com.tangem.tap.domain.scanCard.ScanCardProcessor
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
import com.tangem.tap.domain.userWalletList.isLockedSync
@ -33,6 +32,7 @@ import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.tangemSdkManager
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
@ -74,7 +74,8 @@ class DetailsMiddleware {
is DetailsAction.AccessCodeRecovery -> accessCodeRecoveryMiddleware.handle(state, action)
DetailsAction.ScanCard -> {
scope.launch {
ScanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true)
store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor)
.scan(allowsRequestAccessCodeFromRepository = true)
.doOnSuccess { scanResponse ->
// if we use biometric, scanResponse in GlobalState is null, and crashes NPE on twin cards
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
@ -371,9 +372,9 @@ class DetailsMiddleware {
Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.On))
preferencesStorage.shouldSaveAccessCodes = true
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = scanResponse?.card?.isAccessCodeSet == true,
)
store.state.daggerGraphState
.get(DaggerGraphState::cardSdkConfigRepository)
.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = scanResponse?.card?.isAccessCodeSet == true)
return CompletionResult.Success(Unit)
}
@ -384,9 +385,9 @@ class DetailsMiddleware {
Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.Off))
preferencesStorage.shouldSaveAccessCodes = false
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = false,
)
store.state.daggerGraphState
.get(DaggerGraphState::cardSdkConfigRepository)
.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = false)
}
.doOnFailure { error ->
Timber.e(error, "Unable to delete saved access codes")

View file

@ -182,7 +182,7 @@ class WalletConnectMiddleware {
}
is WalletConnectAction.RejectRequest -> {
walletConnectManager.rejectRequest(action.topic, action.id)
walletConnectInteractor.rejectRequest(action.topic, action.id)
walletConnectInteractor.cancelRequest(action.topic, action.id)
}
is WalletConnectAction.SendTransaction -> {
walletConnectManager.completeTransaction(action.topic)

View file

@ -11,6 +11,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import com.google.zxing.BarcodeFormat
import com.google.zxing.Result
import com.otaliastudios.cameraview.CameraView
import com.tangem.core.navigation.NavigationAction
@ -38,10 +39,19 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
if (!permissionIsGranted()) requestPermission()
scannerView = ZXingScannerView(activity)
scannerView = ZXingScannerView(activity).apply {
setFormats(listOf(BarcodeFormat.QR_CODE))
}
return scannerView
}
override fun onResume() {
super.onResume()
scannerView?.setResultHandler(this)
scannerView?.startCamera()
}
override fun onPause() {
super.onPause()
scannerView?.stopCamera()
@ -52,12 +62,6 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler {
setFitSystemWindows(fit = false)
}
override fun onResume() {
super.onResume()
scannerView?.setResultHandler(this)
scannerView?.startCamera()
}
override fun handleResult(result: Result) {
store.dispatch(NavigationAction.PopBackTo())
setFitSystemWindows(fit = false)

View file

@ -1,6 +1,6 @@
package com.tangem.tap.features.home.redux
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.entities.IndeterminateProgressButton

View file

@ -5,11 +5,10 @@ import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.analytics.events.Shop
@ -21,10 +20,14 @@ import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.model.builders.UserWalletBuilder
import com.tangem.tap.domain.scanCard.ScanCardProcessor
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
import com.tangem.tap.features.send.redux.states.ButtonState
import com.tangem.tap.features.signin.redux.SignInAction
import com.tangem.tap.preferencesStorage
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.userWalletsListManager
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Action
@ -76,11 +79,11 @@ private fun handleHomeAction(action: Action) {
private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch {
delay(timeMillis = 200)
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes,
)
ScanCardProcessor.scan(
store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan(
analyticsEvent = analyticsEvent,
onProgressStateChange = { showProgress ->
if (showProgress) {

View file

@ -4,6 +4,7 @@ import android.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.os.Build
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.intentHandler.IntentHandler
import com.tangem.tap.features.welcome.redux.WelcomeAction
@ -41,10 +42,10 @@ class BackgroundScanIntentHandler(
// TODO: Remove delay after [REDACTED_JIRA]
scope.launch {
delay(timeMillis = 200)
store.dispatch(WelcomeAction.ProceedWithCard)
store.dispatchWithMain(WelcomeAction.ProceedWithCard)
}
} else {
store.dispatch(HomeAction.ReadCard())
store.dispatchWithMain(HomeAction.ReadCard())
}
return true

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.intentHandler.handlers
import android.content.Intent
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.features.intentHandler.IntentHandler
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
@ -20,7 +21,7 @@ class SellCurrencyIntentHandler : IntentHandler {
val destinationAddress = intentData.getQueryParameter(DEPOSIT_WALLET_ADDRESS_PARAM) ?: return false
Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
store.dispatch(
store.dispatchWithMain(
WalletAction.TradeCryptoAction.SendCrypto(
currencyId = currency,
amount = amount,

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.intentHandler.handlers
import android.content.Intent
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.removePrefixOrNull
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
@ -25,7 +26,7 @@ class WalletConnectLinkIntentHandler : IntentHandler {
return if (wcUri == null) {
false
} else {
store.dispatch(WalletConnectAction.HandleDeepLink(wcUri))
store.dispatchWithMain(WalletConnectAction.HandleDeepLink(wcUri))
true
}
}

View file

@ -11,12 +11,12 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.twinsIsTwinned
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.extensions.removeContext
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.domain.model.builders.UserWalletBuilder
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

View file

@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.extensions.makePrimaryWalletManager
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.analytics.events.AnalyticsParam
@ -14,7 +15,6 @@ import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.onboarding.OnboardingDialog

View file

@ -6,9 +6,11 @@ import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.extensions.makePrimaryWalletManager
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.twinsIsTwinned
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding
@ -18,8 +20,6 @@ import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.domain.userWalletList.isLockedSync
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE

View file

@ -17,15 +17,12 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.ShareElement
import com.tangem.core.ui.fragments.setStatusBarColor
import com.tangem.datasource.asset.AssetReader
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.common.TwinCardNumber
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.Artwork
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
import com.tangem.tap.domain.twins.TwinsCardWidget
@ -35,7 +32,6 @@ import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletM
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding

View file

@ -14,6 +14,7 @@ import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.Artwork
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
import com.tangem.operations.backup.BackupService
@ -30,10 +31,9 @@ import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.wallet.models.toCurrencies
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.proxy.redux.DaggerGraphState
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
object OnboardingWalletMiddleware {
@ -44,8 +44,8 @@ private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state
{ next ->
{ action ->
when (action) {
is OnboardingWallet2Action -> handleWallet2Action(action, state)
else -> handleWalletAction(action, state, dispatch)
is OnboardingWallet2Action -> handleWallet2Action(action)
else -> handleWalletAction(action)
}
next(action)
}
@ -53,7 +53,7 @@ private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state
}
@Suppress("LongMethod", "ComplexMethod")
private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: DispatchFunction) {
private fun handleWalletAction(action: Action) {
if (action !is OnboardingWalletAction) return
val globalState = store.state.globalState
@ -179,7 +179,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
}
@Suppress("LongMethod", "ComplexMethod")
private fun handleWallet2Action(action: OnboardingWallet2Action, state: () -> AppState?) {
private fun handleWallet2Action(action: OnboardingWallet2Action) {
val globalState = store.state.globalState
val onboardingManager = globalState.onboardingState.onboardingManager
@ -330,7 +330,9 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
is BackupAction.AddBackupCard -> {
backupService.addBackupCard { result ->
backupService.skipCompatibilityChecks = false
tangemSdk.config.filter.cardIdFilter = null
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk
.config.filter.cardIdFilter = null
when (result) {
is CompletionResult.Success -> {
store.dispatchOnMain(BackupAction.AddBackupCard.Success)

View file

@ -9,6 +9,7 @@ import com.tangem.common.flatMap
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
@ -18,10 +19,10 @@ import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.model.builders.UserWalletBuilder
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.domain.userWalletList.isLockable
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
import timber.log.Timber
@ -110,9 +111,10 @@ internal class SaveWalletMiddleware {
// Enable saving access codes only if this is the first time user save the wallet
if (isFirstSavedWallet) {
preferencesStorage.shouldSaveAccessCodes = true
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = userWallet.hasAccessCode,
)
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository)
.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
}
val savedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {

View file

@ -4,6 +4,7 @@ import com.tangem.Message
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered
import com.tangem.tap.common.redux.StateDialog
@ -114,7 +115,7 @@ sealed class FeeAction : SendScreenAction {
object RequestFee : FeeAction()
sealed class FeeCalculation : FeeAction() {
data class SetFeeResult(val fee: List<Amount>) : FeeCalculation()
data class SetFeeResult(val fee: TransactionFee) : FeeCalculation()
object ClearResult : FeeCalculation()
}

View file

@ -67,7 +67,7 @@ class AmountMiddleware {
}
val amountToSend = Amount(typedAmount, sendState.getTotalAmountToSend(inputCrypto))
val transactionErrors = walletManager.validateTransaction(amountToSend, sendState.feeState.currentFee)
val transactionErrors = walletManager.validateTransaction(amountToSend, sendState.feeState.currentFee?.amount)
val amountFieldErrors = filterErrorsForAmountField(transactionErrors)
if (amountFieldErrors.isEmpty()) {
dispatch(AmountAction.SetAmountError(null))

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.send.redux.middlewares
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.AppState
@ -53,15 +54,18 @@ class RequestFeeMiddleware {
val result = feeResult.data
// val result = FeeMock.getFee(walletManager.wallet.blockchain)
dispatch(FeeAction.FeeCalculation.SetFeeResult(result))
if (result.size == 1) {
val fee = result[0].value ?: BigDecimal.ZERO
if (fee.isZero()) {
dispatch(FeeAction.ChangeLayoutVisibility(main = false))
} else {
dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = false))
when (result) {
is TransactionFee.Single -> {
val fee = result.normal.amount.value ?: BigDecimal.ZERO
if (fee.isZero()) {
dispatch(FeeAction.ChangeLayoutVisibility(main = false))
} else {
dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = false))
}
}
is TransactionFee.Choosable -> {
dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = true))
}
} else {
dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = true))
}
}
is Result.Failure -> {

Some files were not shown because too many files have changed in this diff Show more