Updated on 2026-08-14
1
.idea/codeStyles/Project.xml
generated
|
|
@ -14,6 +14,7 @@
|
|||
</value>
|
||||
</option>
|
||||
<option name="LINE_BREAK_AFTER_MULTILINE_WHEN_ENTRY" value="false" />
|
||||
<option name="INDENT_BEFORE_ARROW_ON_NEW_LINE" value="false" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="5" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="3" />
|
||||
<option name="ALLOW_TRAILING_COMMA" value="true" />
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ plugins {
|
|||
alias(deps.plugins.google.services)
|
||||
alias(deps.plugins.hilt.android)
|
||||
alias(deps.plugins.firebase.crashlytics)
|
||||
alias(deps.plugins.firebase.perf)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +214,10 @@ dependencies {
|
|||
implementation(deps.firebase.analytics)
|
||||
implementation(deps.firebase.crashlytics)
|
||||
implementation(deps.firebase.messaging)
|
||||
|
||||
implementation(deps.firebase.perf) {
|
||||
exclude(group = "com.google.firebase", module = "protolite-well-known-types")
|
||||
exclude(group = "com.google.protobuf", module = "protobuf-javalite")
|
||||
}
|
||||
/** Tangem libraries */
|
||||
implementation(deps.tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import com.tangem.common.routing.AppRouter
|
|||
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
||||
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.navigation.settings.SettingsManager
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
|
|
@ -134,4 +136,7 @@ interface ApplicationEntryPoint {
|
|||
fun getSettingsManager(): SettingsManager
|
||||
|
||||
fun getBlockchainExceptionHandler(): BlockchainExceptionHandler
|
||||
|
||||
@GlobalUiMessageSender
|
||||
fun getUiMessageSender(): UiMessageSender
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import coil.ImageLoaderFactory
|
|||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.tangem.Log
|
||||
import com.tangem.TangemSdkLogger
|
||||
import com.tangem.blockchain.common.ExceptionHandler
|
||||
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.blockchainsdk.signer.TransactionSignerFactory
|
||||
|
|
@ -21,6 +20,7 @@ import com.tangem.core.analytics.models.Basic
|
|||
import com.tangem.core.analytics.models.Basic.TransactionSent.WalletForm
|
||||
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.navigation.settings.SettingsManager
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
|
|
@ -53,7 +53,6 @@ import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
|||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
|
|
@ -208,8 +207,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val settingsManager: SettingsManager
|
||||
get() = entryPoint.getSettingsManager()
|
||||
|
||||
private val blockchainExceptionHandler: BlockchainExceptionHandler
|
||||
get() = entryPoint.getBlockchainExceptionHandler()
|
||||
private val uiMessageSender: UiMessageSender
|
||||
get() = entryPoint.getUiMessageSender()
|
||||
|
||||
// endregion
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
@ -252,7 +252,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
}
|
||||
|
||||
loadNativeLibraries()
|
||||
ExceptionHandler.append(blockchainExceptionHandler)
|
||||
// ExceptionHandler.append(blockchainExceptionHandler) // TODO [REDACTED_TASK_KEY] Send only to Firebase
|
||||
if (LogConfig.network.blockchainSdkNetwork) {
|
||||
BlockchainSdkRetrofitBuilder.interceptors = listOf(
|
||||
createNetworkLoggingInterceptor(),
|
||||
|
|
@ -308,6 +308,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
appPreferencesStore = appPreferencesStore,
|
||||
clipboardManager = clipboardManager,
|
||||
settingsManager = settingsManager,
|
||||
uiMessageSender = uiMessageSender,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -58,10 +58,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
)
|
||||
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
|
||||
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
|
||||
is AppDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog(
|
||||
context,
|
||||
state.dialog.data,
|
||||
)
|
||||
is OnboardingDialog.TwinningProcessNotCompleted -> TwinningProcessNotCompletedDialog.create(context)
|
||||
is OnboardingDialog.InterruptOnboarding -> InterruptOnboardingDialog.create(context, state.dialog)
|
||||
is OnboardingDialog.WalletActivationError -> WalletActivationErrorDialog.create(context, state.dialog)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ sealed class Onboarding(
|
|||
|
||||
class Started : Onboarding("Onboarding", "Onboarding Started")
|
||||
class Finished : Onboarding("Onboarding", "Onboarding Finished")
|
||||
data object OfflineAttestationFailed : Onboarding(category = "Onboarding", event = "Offline Attestation Failed")
|
||||
|
||||
sealed class CreateWallet(
|
||||
event: String,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ internal class TangemPushNotificationService : FirebaseMessagingService() {
|
|||
|
||||
// TODO refactoring: [REDACTED_JIRA]
|
||||
val intent = Intent(applicationContext, MainActivity::class.java)
|
||||
intent.putExtra(OnPushClickedIntentHandler.IS_OPENED_FROM_PUSH, true)
|
||||
intent.putExtra(OnPushClickedIntentHandler.OPENED_FROM_GCM_PUSH, true)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
/* context = */ this,
|
||||
|
|
|
|||
|
|
@ -27,10 +27,6 @@ sealed class AppDialog : StateDialog {
|
|||
val actionsList: List<TestAction>,
|
||||
) : AppDialog()
|
||||
|
||||
data class RussianCardholdersWarningDialog(val data: Data?) : AppDialog() {
|
||||
data class Data(val topUpUrl: String)
|
||||
}
|
||||
|
||||
data class RemoveWalletDialog(
|
||||
val currencyTitle: String,
|
||||
val onOk: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ internal class AddressInfoBottomSheetDialog(
|
|||
val blockchain = stateDialog.currency.blockchain
|
||||
tvReceiveMessage.text = tvReceiveMessage.getString(
|
||||
id = R.string.address_qr_code_message_format,
|
||||
blockchain.fullName,
|
||||
blockchain.getCoinName(),
|
||||
blockchain.currency,
|
||||
blockchain.fullName,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
package com.tangem.tap.common.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.databinding.DialogRussiansCardholdersWarningBinding
|
||||
|
||||
class RussianCardholdersWarningBottomSheetDialog(
|
||||
context: Context,
|
||||
private val dialogData: AppDialog.RussianCardholdersWarningDialog.Data?,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
private var binding: DialogRussiansCardholdersWarningBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Analytics.send(Token.Topup.P2PScreenOpened())
|
||||
binding = DialogRussiansCardholdersWarningBinding
|
||||
.inflate(LayoutInflater.from(context))
|
||||
.also { setContentView(it.root) }
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
setOnDismissListener {
|
||||
binding = null
|
||||
store.dispatchDialogHide()
|
||||
}
|
||||
|
||||
binding?.btnYes?.setOnClickListener {
|
||||
if (dialogData != null) {
|
||||
store.dispatchOpenUrl(dialogData.topUpUrl)
|
||||
Analytics.send(Token.Topup.ScreenOpened())
|
||||
}
|
||||
|
||||
dismiss()
|
||||
}
|
||||
binding?.btnNo?.setOnClickListener {
|
||||
store.dispatchOpenUrl(INSTRUCTION_URL)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val INSTRUCTION_URL = "https://tangem.com/howtobuy.html"
|
||||
}
|
||||
}
|
||||
|
|
@ -19,11 +19,13 @@ internal class DefaultScanCardProcessor(
|
|||
override suspend fun scan(
|
||||
cardId: String?,
|
||||
allowsRequestAccessCodeFromRepository: Boolean,
|
||||
analyticsSource: AnalyticsParam.ScreensSources,
|
||||
): CompletionResult<ScanResponse> {
|
||||
return if (isNewCardScanningEnabled) {
|
||||
UseCaseScanProcessor.scan(cardId, allowsRequestAccessCodeFromRepository)
|
||||
} else {
|
||||
legacyScanProcessor.scan(
|
||||
analyticsSource = analyticsSource,
|
||||
cardId = cardId,
|
||||
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.toWrappedList
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
|
|
@ -28,7 +28,6 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.sdk.extensions.localizedDescriptionRes
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
|
|
@ -60,13 +59,14 @@ internal class LegacyScanProcessor @Inject constructor(
|
|||
suspend fun scan(
|
||||
cardId: String? = null,
|
||||
allowsRequestAccessCodeFromRepository: Boolean = false,
|
||||
analyticsSource: AnalyticsParam.ScreensSources,
|
||||
): CompletionResult<ScanResponse> {
|
||||
return tangemSdkManager.scanProduct(
|
||||
cardId = cardId,
|
||||
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
|
||||
)
|
||||
.doOnFailure { error ->
|
||||
onScanFailure(error = error, onFailure = {}, onCancel = {})
|
||||
onScanFailure(analyticsSource = analyticsSource, error = error, onFailure = {}, onCancel = {})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -93,6 +93,7 @@ internal class LegacyScanProcessor @Inject constructor(
|
|||
result
|
||||
.doOnFailure { error ->
|
||||
onScanFailure(
|
||||
analyticsSource = analyticsSource,
|
||||
error = error,
|
||||
onFailure = onFailure,
|
||||
onCancel = {
|
||||
|
|
@ -163,41 +164,35 @@ internal class LegacyScanProcessor @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend inline fun onScanFailure(
|
||||
analyticsSource: AnalyticsParam.ScreensSources,
|
||||
error: TangemError,
|
||||
crossinline onFailure: suspend (TangemError) -> Unit,
|
||||
crossinline onCancel: () -> Unit,
|
||||
) {
|
||||
if (error is TangemSdkError.CardVerificationFailed) {
|
||||
analyticsEventHandler.send(event = Onboarding.OfflineAttestationFailed)
|
||||
analyticsEventHandler.send(
|
||||
event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed(
|
||||
analyticsSource,
|
||||
),
|
||||
)
|
||||
|
||||
val resource = error.localizedDescriptionRes()
|
||||
val resId = resource.resId ?: R.string.common_unknown_error
|
||||
val resArgs = resource.args.map { it.value }
|
||||
|
||||
uiMessageSender.send(
|
||||
message = DialogMessage(
|
||||
message = resourceReference(id = resId, resArgs.toWrappedList()),
|
||||
title = resourceReference(id = R.string.security_alert_title),
|
||||
isDismissable = false,
|
||||
firstActionBuilder = {
|
||||
EventMessageAction(
|
||||
title = resourceReference(id = R.string.alert_button_request_support),
|
||||
onClick = {
|
||||
mainScope.launch {
|
||||
onCancel()
|
||||
message = Dialogs.cardVerificationFailed(
|
||||
errorDescription = resourceReference(id = resId, resArgs.toWrappedList()),
|
||||
onRequestSupport = {
|
||||
mainScope.launch {
|
||||
onCancel()
|
||||
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase).invoke(
|
||||
type = FeedbackEmailType.CardAttestationFailed,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
secondActionBuilder = {
|
||||
cancelAction {
|
||||
mainScope.launch { onCancel() }
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase).invoke(
|
||||
type = FeedbackEmailType.CardAttestationFailed,
|
||||
)
|
||||
}
|
||||
},
|
||||
onCancelClick = { onCancel() },
|
||||
),
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.domain.common.TwinsHelper
|
||||
import com.tangem.operations.attestation.AttestationTask
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import com.tangem.operations.wallet.PurgeWalletCommand
|
||||
|
|
@ -13,7 +15,7 @@ class CreateFirstTwinWalletTask(private val firstCardId: String) : CardSessionRu
|
|||
|
||||
override val allowsRequestAccessCodeFromRepository: Boolean = false
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
override fun run(session: CardSession, callback: CompletionCallback<CreateWalletResponse>) {
|
||||
val card = session.environment.card
|
||||
val publicKey = card?.wallets?.firstOrNull()?.publicKey
|
||||
if (publicKey != null) {
|
||||
|
|
@ -29,13 +31,40 @@ class CreateFirstTwinWalletTask(private val firstCardId: String) : CardSessionRu
|
|||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
session.environment.card = session.environment.card?.setWallets(emptyList())
|
||||
CreateWalletTask(EllipticCurve.Secp256k1).run(session) { callback(it) }
|
||||
createWallet(session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CreateWalletTask(EllipticCurve.Secp256k1).run(session) { callback(it) }
|
||||
createWallet(session, callback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createWallet(session: CardSession, callback: CompletionCallback<CreateWalletResponse>) {
|
||||
CreateWalletTask(EllipticCurve.Secp256k1).run(session) { createWalletResponse ->
|
||||
when (createWalletResponse) {
|
||||
is CompletionResult.Success -> {
|
||||
runAttestation(session) { attestationResponse ->
|
||||
when (attestationResponse) {
|
||||
is CompletionResult.Success -> callback(createWalletResponse)
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(attestationResponse.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(createWalletResponse.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
internal fun runAttestation(session: CardSession, callback: CompletionCallback<*>) {
|
||||
val mode = session.environment.config.attestationMode
|
||||
val secureStorage = session.environment.secureStorage
|
||||
val attestationTask = AttestationTask(mode, secureStorage)
|
||||
|
||||
attestationTask.run(session, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,13 @@ import com.tangem.common.KeyPair
|
|||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.domain.common.TwinsHelper
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import com.tangem.operations.wallet.PurgeWalletCommand
|
||||
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask.Companion.runAttestation
|
||||
|
||||
class CreateSecondTwinWalletTask(
|
||||
private val firstPublicKey: String,
|
||||
|
|
@ -59,12 +61,10 @@ class CreateSecondTwinWalletTask(
|
|||
CreateWalletTask(EllipticCurve.Secp256k1).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
session.environment.card = session.environment.card?.updateWallet(result.data.wallet)
|
||||
|
||||
WriteProtectedIssuerDataTask(firstPublicKey.hexToBytes(), issuerKeys).run(session) { writeResult ->
|
||||
when (writeResult) {
|
||||
is CompletionResult.Success -> callback(result)
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(writeResult.error))
|
||||
runAttestation(session) { attestationResponse ->
|
||||
when (attestationResponse) {
|
||||
is CompletionResult.Success -> writeProtectedIssuerData(session, result, callback)
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(attestationResponse.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,4 +72,19 @@ class CreateSecondTwinWalletTask(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeProtectedIssuerData(
|
||||
session: CardSession,
|
||||
result: CompletionResult.Success<CreateWalletResponse>,
|
||||
callback: CompletionCallback<CreateWalletResponse>,
|
||||
) {
|
||||
session.environment.card = session.environment.card?.updateWallet(result.data.wallet)
|
||||
|
||||
WriteProtectedIssuerDataTask(firstPublicKey.hexToBytes(), issuerKeys).run(session) { writeResult ->
|
||||
when (writeResult) {
|
||||
is CompletionResult.Success -> callback(result)
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(writeResult.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.domain.twins
|
||||
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.KeyPair
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
|
|
@ -57,7 +56,7 @@ class TwinCardsManager(card: CardDTO) {
|
|||
return response
|
||||
}
|
||||
|
||||
suspend fun complete(message: Message): Result<ScanResponse> {
|
||||
suspend fun complete(message: Message): CompletionResult<ScanResponse> {
|
||||
val response = tangemSdkManager.finalizeTwin(
|
||||
secondCardPublicKey = secondCardPublicKey!!.hexToBytes(),
|
||||
issuerKeyPair = getIssuerKeys(),
|
||||
|
|
@ -65,10 +64,7 @@ class TwinCardsManager(card: CardDTO) {
|
|||
initialMessage = message,
|
||||
)
|
||||
|
||||
return when (response) {
|
||||
is CompletionResult.Success -> Result.Success(response.data)
|
||||
is CompletionResult.Failure -> Result.fromTangemSdkError(response.error)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
private suspend fun getIssuerKeys(): KeyPair {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LargeClass")
|
||||
internal class DefaultLegacyWalletConnectRepository(
|
||||
private val application: Application,
|
||||
private val wcRequestDeserializer: WcJrpcRequestsDeserializer,
|
||||
|
|
@ -72,19 +73,23 @@ internal class DefaultLegacyWalletConnectRepository(
|
|||
}
|
||||
}
|
||||
|
||||
WalletKit.initialize(Wallet.Params.Init(core = CoreClient)) { error ->
|
||||
Timber.e("Error while initializing Web3Wallet: $error")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ExternalApprovalError(error.throwable.message),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val walletDelegate = defineWalletDelegate()
|
||||
WalletKit.setWalletDelegate(walletDelegate)
|
||||
WalletKit.initialize(
|
||||
Wallet.Params.Init(core = CoreClient),
|
||||
onSuccess = {
|
||||
val walletDelegate = defineWalletDelegate()
|
||||
WalletKit.setWalletDelegate(walletDelegate)
|
||||
},
|
||||
onError = { error ->
|
||||
Timber.e("Error while initializing Web3Wallet: $error")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.SessionApprovalError(
|
||||
WalletConnectError.ExternalApprovalError(error.throwable.message),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun defineWalletDelegate(): WalletKit.WalletDelegate {
|
||||
|
|
@ -333,7 +338,9 @@ internal class DefaultLegacyWalletConnectRepository(
|
|||
optionalNamespace.key to Wallet.Model.Namespace.Session(
|
||||
accounts = accountsOptional.distinct(),
|
||||
methods = methods,
|
||||
chains = userChains.keys.toList(),
|
||||
chains = userChains.keys
|
||||
.filter { it.startsWith(optionalNamespace.key) }
|
||||
.toList(),
|
||||
events = optionalNamespace.value.events,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,8 +155,8 @@ class WalletConnectInteractor(
|
|||
return@onEach
|
||||
}
|
||||
val networksFormatted = (wcEvent.requiredChainIds + wcEvent.optionalChainIds)
|
||||
.distinct()
|
||||
.mapNotNull { blockchainHelper.chainIdToFullNameOrNull(it) }
|
||||
.distinct()
|
||||
.toString()
|
||||
handler.onProposalReceived(proposal = wcEvent, networksFormatted = networksFormatted)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,10 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
)
|
||||
|
||||
private fun scanCard() = viewModelScope.launch {
|
||||
scanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true)
|
||||
scanCardProcessor.scan(
|
||||
analyticsSource = com.tangem.core.analytics.models.AnalyticsParam.ScreensSources.Settings,
|
||||
allowsRequestAccessCodeFromRepository = true,
|
||||
)
|
||||
.doOnSuccess { scanResponse ->
|
||||
val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
if (userWalletId == scannedUserWalletId || scannedUserWalletId == null) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.tap.features.intentHandler.IntentHandler
|
|||
internal class OnPushClickedIntentHandler(val analyticsEventHandler: AnalyticsEventHandler) : IntentHandler {
|
||||
|
||||
override fun handleIntent(intent: Intent?, isFromForeground: Boolean): Boolean {
|
||||
val fromPush = intent?.extras?.getBoolean(IS_OPENED_FROM_PUSH) ?: false
|
||||
val fromPush = intent?.extras?.containsKey(OPENED_FROM_GCM_PUSH) ?: false
|
||||
|
||||
return if (fromPush) {
|
||||
analyticsEventHandler.send(Push.PushNotificationOpened)
|
||||
|
|
@ -19,6 +19,6 @@ internal class OnPushClickedIntentHandler(val analyticsEventHandler: AnalyticsEv
|
|||
}
|
||||
|
||||
companion object {
|
||||
const val IS_OPENED_FROM_PUSH = "IS_OPENED_FROM_PUSH"
|
||||
const val OPENED_FROM_GCM_PUSH = "google.sent_time" // every bundle from FCM contains this key
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,6 @@ import com.tangem.domain.common.util.twinsIsTwinned
|
|||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
|
|
@ -22,7 +21,6 @@ import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
|||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
|
||||
|
|
@ -109,7 +107,7 @@ object OnboardingHelper {
|
|||
}
|
||||
}
|
||||
|
||||
fun saveWallet(
|
||||
suspend fun saveWallet(
|
||||
alreadyCreatedWallet: UserWallet?,
|
||||
scanResponse: ScanResponse,
|
||||
accessCode: String? = null,
|
||||
|
|
@ -117,46 +115,42 @@ object OnboardingHelper {
|
|||
hasBackupError: Boolean = false,
|
||||
) {
|
||||
Analytics.setContext(scanResponse)
|
||||
scope.launch {
|
||||
val settingsRepository = store.inject(DaggerGraphState::settingsRepository)
|
||||
when {
|
||||
// When should save user wallets, then save card without navigate to save wallet screen
|
||||
store.inject(DaggerGraphState::walletsRepository).shouldSaveUserWalletsSync() -> {
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
val settingsRepository = store.inject(DaggerGraphState::settingsRepository)
|
||||
when {
|
||||
// When should save user wallets, then save card without navigate to save wallet screen
|
||||
store.inject(DaggerGraphState::walletsRepository).shouldSaveUserWalletsSync() -> {
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.SaveWalletAfterBackup(
|
||||
hasBackupError = hasBackupError,
|
||||
shouldNavigateToWallet = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
|
||||
// then open save wallet screen
|
||||
tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||
|
||||
delay(timeMillis = 1_200)
|
||||
|
||||
store.dispatchOnMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
}
|
||||
// If device has no biometry and save wallet screen has been shown, then go through old scenario
|
||||
else -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||
}
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.SaveWalletAfterBackup(
|
||||
hasBackupError = hasBackupError,
|
||||
shouldNavigateToWallet = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
|
||||
// then open save wallet screen
|
||||
tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||
|
||||
delay(timeMillis = 1_200)
|
||||
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
}
|
||||
// If device has no biometry and save wallet screen has been shown, then go through old scenario
|
||||
else -> proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,18 +237,7 @@ object OnboardingHelper {
|
|||
val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain)
|
||||
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
|
||||
|
||||
scope.launch {
|
||||
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
|
||||
val isRussia = getUserCountryCodeUseCase.invokeSync().isRight { it is UserCountry.Russia }
|
||||
|
||||
val onrampFeatureToggles = store.inject(DaggerGraphState::onrampFeatureToggles)
|
||||
if (isRussia && !onrampFeatureToggles.isFeatureEnabled) {
|
||||
val dialogData = AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
|
||||
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(dialogData))
|
||||
} else {
|
||||
store.dispatchOpenUrl(topUpUrl)
|
||||
}
|
||||
}
|
||||
store.dispatchOpenUrl(topUpUrl)
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(
|
||||
|
|
|
|||
|
|
@ -1,17 +1,26 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.toWrappedList
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
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.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.sdk.extensions.localizedDescriptionRes
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
|
|
@ -185,8 +194,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateSecondWallet))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
}
|
||||
is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -208,8 +216,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateThirdWallet))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
}
|
||||
is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -218,7 +225,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
|
||||
scope.launch {
|
||||
when (val result = manager.complete(action.message)) {
|
||||
is Result.Success -> {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.Twins.SetupFinished())
|
||||
updateScanResponse(result.data)
|
||||
|
||||
|
|
@ -234,8 +241,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
}
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
}
|
||||
is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -349,6 +355,31 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun showCardVerificationFailedDialog(error: TangemError) {
|
||||
if (error is TangemSdkError.CardVerificationFailed) {
|
||||
Analytics.send(
|
||||
event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed(AnalyticsParam.ScreensSources.Backup),
|
||||
)
|
||||
|
||||
val resource = error.localizedDescriptionRes()
|
||||
val resId = resource.resId ?: R.string.common_unknown_error
|
||||
val resArgs = resource.args.map { it.value }
|
||||
|
||||
store.inject(DaggerGraphState::uiMessageSender).send(
|
||||
message = Dialogs.cardVerificationFailed(
|
||||
errorDescription = resourceReference(id = resId, resArgs.toWrappedList()),
|
||||
onRequestSupport = {
|
||||
mainScope.launch {
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase).invoke(
|
||||
type = FeedbackEmailType.CardAttestationFailed,
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPopBackScreen(): KClass<out AppRoute> {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import android.net.Uri
|
|||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.common.extensions.toHexString
|
||||
|
|
@ -12,9 +11,15 @@ import com.tangem.common.routing.AppRoute
|
|||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.ScreensSources
|
||||
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.toWrappedList
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.domain.common.extensions.withIOContext
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_IDS
|
||||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_PREFIX
|
||||
|
|
@ -30,6 +35,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator
|
|||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import com.tangem.sdk.extensions.localizedDescriptionRes
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
|
|
@ -37,7 +43,6 @@ import com.tangem.tap.common.extensions.*
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
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.proxy.redux.DaggerGraphState
|
||||
|
|
@ -46,7 +51,6 @@ import kotlinx.coroutines.delay
|
|||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
object OnboardingWalletMiddleware {
|
||||
val handler = onboardingWalletMiddleware
|
||||
|
|
@ -197,7 +201,7 @@ private fun handleWalletAction(action: Action) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleFinishBackup(scanResponse: ScanResponse, userWallet: UserWallet? = null) {
|
||||
private suspend fun handleFinishBackup(scanResponse: ScanResponse, userWallet: UserWallet? = null) {
|
||||
val backupState = store.state.onboardingWalletState.backupState
|
||||
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
|
||||
|
||||
|
|
@ -228,31 +232,14 @@ private fun navigateToWalletScreen() {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun readCard(onSuccess: suspend (ScanResponse) -> Unit, onFailure: (TangemError) -> Unit) {
|
||||
private suspend fun readCard(): CompletionResult<ScanResponse> {
|
||||
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
|
||||
|
||||
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = shouldSaveAccessCodes,
|
||||
)
|
||||
|
||||
store.inject(DaggerGraphState::scanCardProcessor).scan(
|
||||
analyticsSource = com.tangem.core.analytics.models.AnalyticsParam.ScreensSources.Intro,
|
||||
onProgressStateChange = { showProgress ->
|
||||
if (showProgress) {
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = true))
|
||||
} else {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Unable to scan card")
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
onFailure(it)
|
||||
},
|
||||
onSuccess = onSuccess,
|
||||
)
|
||||
return store.inject(DaggerGraphState::scanCardProcessor).scan(analyticsSource = ScreensSources.Intro)
|
||||
}
|
||||
|
||||
private suspend fun loadArtworkForCard(cardId: String, cardPublicKey: ByteArray, defaultArtwork: Uri?): Uri {
|
||||
|
|
@ -469,6 +456,29 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
val crashlytics = FirebaseCrashlytics.getInstance()
|
||||
|
||||
when (val error = result.error) {
|
||||
is TangemSdkError.CardVerificationFailed -> {
|
||||
Analytics.send(
|
||||
event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed(
|
||||
ScreensSources.Backup,
|
||||
),
|
||||
)
|
||||
|
||||
val resource = error.localizedDescriptionRes()
|
||||
val resId = resource.resId ?: com.tangem.core.ui.R.string.common_unknown_error
|
||||
val resArgs = resource.args.map { it.value }
|
||||
|
||||
store.inject(DaggerGraphState::uiMessageSender).send(
|
||||
message = Dialogs.cardVerificationFailed(
|
||||
errorDescription = resourceReference(id = resId, resArgs.toWrappedList()),
|
||||
onRequestSupport = {
|
||||
mainScope.launch {
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase)
|
||||
.invoke(type = FeedbackEmailType.CardAttestationFailed)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
is TangemSdkError.BackupFailedNotEmptyWallets -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
|
|
@ -631,23 +641,22 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
Analytics.send(Onboarding.Finished())
|
||||
|
||||
scope.launch {
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(
|
||||
cardIds = gatherCardIds(backupState, card),
|
||||
)
|
||||
}
|
||||
if (scanResponse == null) {
|
||||
scope.launch {
|
||||
readCard(
|
||||
onSuccess = { newScanResponse ->
|
||||
handleFinishBackup(newScanResponse)
|
||||
},
|
||||
onFailure = {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
launch {
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(
|
||||
cardIds = gatherCardIds(backupState, card),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
handleFinishBackup(scanResponse)
|
||||
|
||||
with(scanResponse) {
|
||||
if (this == null) {
|
||||
when (val result = readCard()) {
|
||||
is CompletionResult.Success -> handleFinishBackup(result.data)
|
||||
is CompletionResult.Failure -> store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
} else {
|
||||
handleFinishBackup(scanResponse = this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.FinishBackup -> {
|
||||
|
|
@ -663,40 +672,39 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
scanResponse = requireNotNull(value = scanResponse, lazyMessage = { "ScanResponse is null" }),
|
||||
backupState = backupState,
|
||||
)
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
when (backupState.startedSource) {
|
||||
BackupStartedSource.Onboarding -> saveWallet(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWallet = userWallet,
|
||||
scanResponse = scanResponse,
|
||||
backupState = backupState,
|
||||
)
|
||||
BackupStartedSource.CreateBackup -> updateWallet(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWallet = userWallet,
|
||||
backupState = backupState,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
|
||||
readCard(
|
||||
onSuccess = { newScanResponse ->
|
||||
scanResponse = newScanResponse
|
||||
userWallet = createUserWallet(newScanResponse, backupState)
|
||||
},
|
||||
onFailure = {
|
||||
when (val result = readCard()) {
|
||||
is CompletionResult.Failure -> {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
return@launch
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
scanResponse = result.data
|
||||
userWallet = createUserWallet(scanResponse = result.data, backupState = backupState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
when (backupState.startedSource) {
|
||||
BackupStartedSource.Onboarding -> saveWallet(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWallet = userWallet,
|
||||
scanResponse = scanResponse,
|
||||
backupState = backupState,
|
||||
)
|
||||
BackupStartedSource.CreateBackup -> updateWallet(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWallet = userWallet,
|
||||
backupState = backupState,
|
||||
)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
userWallet?.let {
|
||||
if (it.scanResponse.cardTypesResolver.isWallet2() && it.isImported) {
|
||||
store.inject(DaggerGraphState::walletsRepository).markWallet2WasCreated(it.walletId)
|
||||
}
|
||||
if (userWallet.scanResponse.cardTypesResolver.isWallet2() && userWallet.isImported) {
|
||||
store.inject(DaggerGraphState::walletsRepository).markWallet2WasCreated(userWallet.walletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -711,7 +719,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
// All cardIds may already be activated if the backup was skipped before.
|
||||
if (notActivatedCardIds.isEmpty()) {
|
||||
delay(1000)
|
||||
store.dispatchWithMain(BackupAction.BackupFinished(userWallet?.walletId))
|
||||
store.dispatchWithMain(BackupAction.BackupFinished(userWallet.walletId))
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
|
@ -719,8 +727,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(notActivatedCardIds)
|
||||
handleFinishBackup(requireNotNull(scanResponse), userWallet)
|
||||
delay(1000)
|
||||
store.dispatchWithMain(BackupAction.BackupFinished(userWalletId = userWallet?.walletId))
|
||||
store.dispatchWithMain(BackupAction.BackupFinished(userWalletId = userWallet.walletId))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,15 +6,16 @@ import com.tangem.common.routing.AppRoute
|
|||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
|
|
@ -89,19 +90,6 @@ object TradeCryptoMiddleware {
|
|||
)
|
||||
|
||||
scope.launch {
|
||||
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
|
||||
val onrampFeatureToggles = store.inject(DaggerGraphState::onrampFeatureToggles)
|
||||
|
||||
val isRussia = getUserCountryCodeUseCase.invokeSync().isRight { it is UserCountry.Russia }
|
||||
|
||||
if (action.checkUserLocation && isRussia && !onrampFeatureToggles.isFeatureEnabled) {
|
||||
val dialogData = topUrl?.let {
|
||||
AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl = it)
|
||||
}
|
||||
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(data = dialogData))
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (currency is CryptoCurrency.Token && currency.network.isTestnet) {
|
||||
val walletManager = store.inject(DaggerGraphState::walletManagersFacade)
|
||||
.getOrCreateWalletManager(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchainsdk.BlockchainSDKFactory
|
|||
import com.tangem.blockchainsdk.signer.TransactionSignerFactory
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.navigation.settings.SettingsManager
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
|
|
@ -79,4 +80,5 @@ data class DaggerGraphState(
|
|||
val appPreferencesStore: AppPreferencesStore? = null,
|
||||
val clipboardManager: ClipboardManager? = null,
|
||||
val settingsManager: SettingsManager? = null,
|
||||
val uiMessageSender: UiMessageSender? = null,
|
||||
) : StateType
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/background_primary"
|
||||
android:minHeight="420dp"
|
||||
tools:layout_gravity="bottom">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_flag"
|
||||
android:layout_width="68dp"
|
||||
android:layout_height="68dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/img_flag_ru_24"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<View
|
||||
android:id="@+id/view_cross_outline"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:background="@drawable/shape_circle"
|
||||
android:backgroundTint="@color/background_primary"
|
||||
app:layout_constraintBottom_toBottomOf="@id/iv_cross"
|
||||
app:layout_constraintEnd_toEndOf="@id/iv_cross"
|
||||
app:layout_constraintStart_toStartOf="@id/iv_cross"
|
||||
app:layout_constraintTop_toTopOf="@id/iv_cross" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_cross"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginEnd="-4dp"
|
||||
android:layout_marginBottom="-4dp"
|
||||
android:background="@drawable/shape_circle"
|
||||
android:backgroundTint="#FFEBEE"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_cross_rounded_24"
|
||||
app:layout_constraintBottom_toBottomOf="@id/iv_flag"
|
||||
app:layout_constraintEnd_toEndOf="@id/iv_flag"
|
||||
app:tint="#D80027" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="38dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="38dp"
|
||||
android:text="@string/russian_bank_card_warning_title"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/text_primary_1"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/iv_flag" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="38dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="38dp"
|
||||
android:layout_marginBottom="38dp"
|
||||
android:text="@string/russian_bank_card_warning_subtitle"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/text_primary_1"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/btn_yes"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_yes"
|
||||
style="@style/TapPrimaryButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginBottom="38dp"
|
||||
android:backgroundTint="@color/button_primary"
|
||||
android:text="@string/common_yes"
|
||||
android:textColor="@color/text_primary_2"
|
||||
app:cornerRadius="14dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/btn_no"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_no"
|
||||
style="@style/TapPrimaryButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:backgroundTint="@color/button_secondary"
|
||||
android:text="@string/common_no"
|
||||
android:textColor="@color/text_primary_1"
|
||||
app:cornerRadius="14dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/btn_yes"
|
||||
app:layout_constraintTop_toTopOf="@id/btn_yes" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -48,7 +48,7 @@ internal class CryptoCurrenciesMocks(private val scanResponse: ScanResponse) {
|
|||
val network = Network(
|
||||
id = Network.ID(blockchain.id),
|
||||
backendId = blockchain.toNetworkId(),
|
||||
name = blockchain.getNetworkName(),
|
||||
name = blockchain.fullName,
|
||||
isTestnet = blockchain.isTestnet(),
|
||||
derivationPath = getNetworkDerivationPath(
|
||||
blockchain,
|
||||
|
|
@ -76,7 +76,7 @@ internal class CryptoCurrenciesMocks(private val scanResponse: ScanResponse) {
|
|||
network = Network(
|
||||
id = Network.ID(value = blockchain.id),
|
||||
backendId = "NEVER-MIND",
|
||||
name = blockchain.getNetworkName(),
|
||||
name = blockchain.fullName,
|
||||
currencySymbol = "NEVER-MIND",
|
||||
derivationPath = Network.DerivationPath.Custom(
|
||||
value = derivationBlockchain.derivationPath(
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ plugins {
|
|||
alias(deps.plugins.hilt.android) apply false
|
||||
alias(deps.plugins.google.services) apply false
|
||||
alias(deps.plugins.firebase.crashlytics) apply false
|
||||
alias(deps.plugins.firebase.perf) apply false
|
||||
alias(deps.plugins.room) apply false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ object NotificationsFactory {
|
|||
val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) {
|
||||
feeAmount
|
||||
} else {
|
||||
sendingAmount
|
||||
sendingAmount + feeAmount
|
||||
}
|
||||
val diff = balance.minus(spendingAmount)
|
||||
if (existentialDeposit != null && diff >= BigDecimal.ZERO && existentialDeposit > diff) {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ sealed class AnalyticsParam {
|
|||
data object Buy : ScreensSources("Buy")
|
||||
data object Swap : ScreensSources("Swap")
|
||||
data object Sell : ScreensSources("Sell")
|
||||
data object Backup : ScreensSources("Backup")
|
||||
data object Onboarding : ScreensSources("Onboarding")
|
||||
}
|
||||
|
||||
sealed class TxSentFrom(val value: String) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.core.analytics.models.event
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
|
||||
sealed class OnboardingAnalyticsEvent(
|
||||
category: String,
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(category, event, params) {
|
||||
|
||||
sealed class Onboarding(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : OnboardingAnalyticsEvent(category = "Error", event = event, params = params) {
|
||||
|
||||
data class OfflineAttestationFailed(
|
||||
val source: AnalyticsParam.ScreensSources,
|
||||
) : Onboarding(
|
||||
event = "Offline Attestation Failed",
|
||||
params = mapOf(AnalyticsParam.SOURCE to source.value),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,22 +11,10 @@
|
|||
"name": "NEXA/test",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "fact0rn",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "vanar-chain",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "bitrock",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "odyssey",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "sonic",
|
||||
"version": "undefined"
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ internal object NetworkModule {
|
|||
|
||||
private const val PROD_V2_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v2/"
|
||||
private const val TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS = 60L
|
||||
private const val STAKE_KIT_API_TIMEOUT_SECONDS = 60L
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
@ -89,6 +90,12 @@ internal object NetworkModule {
|
|||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
timeouts = Timeouts(
|
||||
callTimeoutSeconds = STAKE_KIT_API_TIMEOUT_SECONDS,
|
||||
connectTimeoutSeconds = STAKE_KIT_API_TIMEOUT_SECONDS,
|
||||
readTimeoutSeconds = STAKE_KIT_API_TIMEOUT_SECONDS,
|
||||
writeTimeoutSeconds = STAKE_KIT_API_TIMEOUT_SECONDS,
|
||||
),
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
|
|
@ -262,6 +269,7 @@ internal object NetworkModule {
|
|||
moshi: Moshi,
|
||||
context: Context,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
timeouts: Timeouts = Timeouts(),
|
||||
clientBuilder: OkHttpClient.Builder.() -> OkHttpClient.Builder = { this },
|
||||
): T {
|
||||
val environmentConfig = apiConfigsManager.getEnvironmentConfig(id)
|
||||
|
|
@ -273,6 +281,23 @@ internal object NetworkModule {
|
|||
.client(
|
||||
OkHttpClient.Builder()
|
||||
.applyApiConfig(id, apiConfigsManager)
|
||||
.applyTimeoutAnnotations()
|
||||
.let { builder ->
|
||||
var b = builder
|
||||
if (timeouts.callTimeoutSeconds != null) {
|
||||
b = b.callTimeout(timeouts.callTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.connectTimeoutSeconds != null) {
|
||||
b = b.connectTimeout(timeouts.connectTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.readTimeoutSeconds != null) {
|
||||
b = b.readTimeout(timeouts.readTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.writeTimeoutSeconds != null) {
|
||||
b = b.writeTimeout(timeouts.writeTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
b
|
||||
}
|
||||
.addLoggers(context)
|
||||
.clientBuilder()
|
||||
.build(),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.datasource.local.datastore
|
|||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
/**
|
||||
* Runtime store
|
||||
|
|
@ -16,6 +17,8 @@ interface RuntimeStateStore<T> {
|
|||
/** Store [value] */
|
||||
suspend fun store(value: T)
|
||||
|
||||
suspend fun update(function: (T) -> T)
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
|
|
@ -32,6 +35,10 @@ interface RuntimeStateStore<T> {
|
|||
override suspend fun store(value: T) {
|
||||
flow.value = value
|
||||
}
|
||||
|
||||
override suspend fun update(function: (T) -> T) {
|
||||
flow.update(function)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -658,9 +658,7 @@
|
|||
<string name="reset_card_with_backup_to_factory_message">Durch das Zurücksetzen auf Werkseinstellungen wird die Wallet vollständig von der ausgewählten Karte oder Ring gelöscht. Du kannst die aktuelle Wallet nicht wiederherstellen oder die Karte oder Ring verwenden, um den Zugangscode wiederherzustellen.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">Beim Zurücksetzen auf die Werkseinstellungen wird die Wallet der ausgewählten Karte oder Ring vollständig gelöscht und aus der App entfernt. Es ist nicht möglich, die aktuelle Wallet wiederherzustellen.</string>
|
||||
<string name="ring_promo_text">Ringbesitzer erhalten bis zum 15.11. 3 provisionsfreie Swaps auf Changelly!</string>
|
||||
<string name="ring_promo_title">Jetzt mit 0 %% Gebühren tauschen!</string>
|
||||
<string name="russian_bank_card_warning_subtitle">Besitzt du eine Bankkarte aus einem anderen Land und eine Aufenthaltserlaubnis oder Registrierung außerhalb der Russischen Föderation?</string>
|
||||
<string name="russian_bank_card_warning_title">Russische Bankkarten werden derzeit nicht akzeptiert</string>
|
||||
<string name="ring_promo_title">Jetzt mit 0 % Gebühren tauschen!</string>
|
||||
<string name="save_user_wallet_agreement_access_description">Melde dich bei der App an und überprüfe dein Guthaben, ohne die Karte oder Ring zu scannen</string>
|
||||
<string name="save_user_wallet_agreement_access_title">Zugriff auf die App</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">Nutzung biometrischer Daten zulassen</string>
|
||||
|
|
@ -1000,7 +998,7 @@
|
|||
<string name="wallet_network_group_title">%s Netzwerk</string>
|
||||
<string name="wallet_notification_address_copied">Die Adresse wurde erfolgreich kopiert</string>
|
||||
<string name="wallet_notification_no_internet">Keine Internetverbindung</string>
|
||||
<string name="wallet_promo_banner_button_title">Jetzt mit 10 %% Rabatt kaufen</string>
|
||||
<string name="wallet_promo_banner_button_title">Jetzt mit 10 % Rabatt kaufen</string>
|
||||
<string name="wallet_promo_banner_description">Greife auf 13.000+ Kryptowährungen zu. Kaufe, verkaufe, tausche und stake Sie mit einem einzigen Fingertipp.\nVerbinde bis zu drei Karten für ein Backup.</string>
|
||||
<string name="wallet_promo_banner_title">Entdecke die Tangem Wallet</string>
|
||||
<string name="wallet_settings_title">Wallet-Einstellungen</string>
|
||||
|
|
|
|||
|
|
@ -654,9 +654,7 @@
|
|||
<string name="reset_card_with_backup_to_factory_message">El restablecimiento de fábrica eliminará completamente la billetera de la tarjeta seleccionada. No podrá restaurar la billetera actual ni usar la tarjeta para recuperar el código de acceso.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">El reseteo a valores de fábrica eliminará completamente la billetera de la tarjeta/anillo seleccionado y lo eliminará de la app. No podrá restaurar la billetera actual.</string>
|
||||
<string name="ring_promo_text">Si tienes un Anillo Tangem, ¡3 swaps sin comisión en Changelly hasta el 15/11!</string>
|
||||
<string name="ring_promo_title">¡Intercambia con 0%% de comisión!</string>
|
||||
<string name="russian_bank_card_warning_subtitle">¿Tiene una tarjeta bancaria de otro país y un permiso de residencia o registro fuera de la Federación Rusa?</string>
|
||||
<string name="russian_bank_card_warning_title">Las tarjetas bancarias rusas no se aceptan actualmente</string>
|
||||
<string name="ring_promo_title">¡Intercambia con 0% de comisión!</string>
|
||||
<string name="save_user_wallet_agreement_access_description">Inicie sesión en la app y comprueba su saldo sin escanear la tarjeta o el anillo</string>
|
||||
<string name="save_user_wallet_agreement_access_title">Acceder a la app</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">Permitir el uso de biometría</string>
|
||||
|
|
@ -996,7 +994,7 @@
|
|||
<string name="wallet_network_group_title">%s red</string>
|
||||
<string name="wallet_notification_address_copied">La dirección se copió correctamente</string>
|
||||
<string name="wallet_notification_no_internet">Sin conexión a internet</string>
|
||||
<string name="wallet_promo_banner_button_title">Consíguelo ahora con un 10 %% de descuento</string>
|
||||
<string name="wallet_promo_banner_button_title">Consíguelo ahora con un 10 % de descuento</string>
|
||||
<string name="wallet_promo_banner_description">Accede a más de 13 000 criptomonedas. Compra, vende, intercambia y realiza staking con un solo toque.\nVincula hasta tres tarjetas para hacer copias de seguridad.</string>
|
||||
<string name="wallet_promo_banner_title">Descubre Tangem Wallet</string>
|
||||
<string name="wallet_settings_title">Ajustes de la wallet</string>
|
||||
|
|
|
|||
|
|
@ -654,9 +654,7 @@
|
|||
<string name="reset_card_with_backup_to_factory_message">La réinitialisation aux paramètres d\'usine supprimera complètement le portefeuille de la carte sélectionnée. Vous ne pourrez pas restaurer le portefeuille actuel ni utiliser la carte pour récupérer le code d\'accès.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">La réinitialisation aux paramètres d\'usine supprimera complètement le portefeuille de la carte sélectionnée et le supprimera de l\'application. Vous ne pourrez pas restaurer le portefeuille actuel.</string>
|
||||
<string name="ring_promo_text">Les propriétaires du Tangem Ring ont droit à 3 swap gratis sur Changelly jusqu\'au 15/11 !</string>
|
||||
<string name="ring_promo_title">Échangez avec 0 %% de frais !</string>
|
||||
<string name="russian_bank_card_warning_subtitle">Avez-vous une carte bancaire d\'un autre pays et un permis de séjour ou une inscription en dehors de la Fédération de Russie ?</string>
|
||||
<string name="russian_bank_card_warning_title">Les cartes bancaires russes ne sont actuellement pas acceptées</string>
|
||||
<string name="ring_promo_title">Échangez avec 0 % de frais !</string>
|
||||
<string name="save_user_wallet_agreement_access_description">Connectez-vous à l\'application et vérifiez votre solde sans scanner la carte</string>
|
||||
<string name="save_user_wallet_agreement_access_title">Accéder à l\'application</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">Autoriser l\'utilisation de la biométrie</string>
|
||||
|
|
@ -996,7 +994,7 @@
|
|||
<string name="wallet_network_group_title">%s réseau</string>
|
||||
<string name="wallet_notification_address_copied">L\'adresse a été copiée avec succès</string>
|
||||
<string name="wallet_notification_no_internet">Pas de connexion internet</string>
|
||||
<string name="wallet_promo_banner_button_title">Obtenez-le maintenant avec 10 %% de réduction</string>
|
||||
<string name="wallet_promo_banner_button_title">Obtenez-le maintenant avec 10 % de réduction</string>
|
||||
<string name="wallet_promo_banner_description">Accédez à plus de 13 000 cryptomonnaies. Achetez, vendez, échangez et stakez en un seul clic.\nAssociez jusqu\'à trois cartes pour une sauvegarde.</string>
|
||||
<string name="wallet_promo_banner_title">Découvrez le Portefeuille Tangem</string>
|
||||
<string name="wallet_settings_title">Paramètres du portefeuille</string>
|
||||
|
|
|
|||
|
|
@ -648,9 +648,7 @@
|
|||
<string name="reset_card_with_backup_to_factory_message">工場出荷時設定にリセットすると、選択したカードやリングからウォレットが完全に削除されます。現在のウォレットを復元したり、カードやリングを使用してアクセスコードを復元することはできません。</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">工場出荷時の状態にリセットすると、選択したカードやリングからウォレットが完全に削除され、アプリから削除されます。現在のウォレットを復元することはできません。</string>
|
||||
<string name="ring_promo_text">Tangem Ringユーザーは、11/15日までChangelly経由でスワップを3回手数料ゼロで行えます!</string>
|
||||
<string name="ring_promo_title">今すぐ手数料0%%でスワップしましょう!</string>
|
||||
<string name="russian_bank_card_warning_subtitle">他の国の銀行カードと、ロシア連邦外での居住許可証または登録をお持ちですか?</string>
|
||||
<string name="russian_bank_card_warning_title">ロシアの銀行カードは現在ご利用いただけません</string>
|
||||
<string name="ring_promo_title">今すぐ手数料0%でスワップしましょう!</string>
|
||||
<string name="save_user_wallet_agreement_access_description">アプリにログインして、カードまたはリングをスキャンせずに残高を確認できます</string>
|
||||
<string name="save_user_wallet_agreement_access_title">アプリにアクセスする</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">生体認証の使用を許可する</string>
|
||||
|
|
@ -990,7 +988,7 @@
|
|||
<string name="wallet_network_group_title">%sネットワーク</string>
|
||||
<string name="wallet_notification_address_copied">アドレスがクリップボードにコピーされました</string>
|
||||
<string name="wallet_notification_no_internet">インターネット接続がありません</string>
|
||||
<string name="wallet_promo_banner_button_title">今すぐ10 %%オフで購入</string>
|
||||
<string name="wallet_promo_banner_button_title">今すぐ10 %オフで購入</string>
|
||||
<string name="wallet_promo_banner_description">1.3万種類以上の暗号資産にアクセス。ワンタップで買付、売却、スワップ、ステーキングが可能です。\nバックアップ用に最大3枚のカードを連携できます。</string>
|
||||
<string name="wallet_promo_banner_title">Tangemウォレットを見る</string>
|
||||
<string name="wallet_settings_title">ウォレット設定</string>
|
||||
|
|
|
|||
|
|
@ -672,9 +672,7 @@
|
|||
<string name="reset_card_with_backup_to_factory_message">Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты или кольца. Вы не сможете восстановить текущий кошелек или использовать данную карту или кольцо для восстановления кода доступа.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты или кольца. Вы не сможете восстановить текущий кошелек.</string>
|
||||
<string name="ring_promo_text">Владельцам колец — 3 обмена без комиссии на Changelly до 15.11!</string>
|
||||
<string name="ring_promo_title">Обмен с 0%% комиссией!</string>
|
||||
<string name="russian_bank_card_warning_subtitle">У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ?</string>
|
||||
<string name="russian_bank_card_warning_title">Карты банков РФ в данный момент не принимаются</string>
|
||||
<string name="ring_promo_title">Обмен с 0% комиссией!</string>
|
||||
<string name="save_user_wallet_agreement_access_description">Войдите в приложение и следите за своим балансом без сканирования карты или кольца</string>
|
||||
<string name="save_user_wallet_agreement_access_title">Доступ в приложение</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">Использовать биометрию</string>
|
||||
|
|
@ -1013,7 +1011,7 @@
|
|||
<string name="wallet_network_group_title">Сеть %s</string>
|
||||
<string name="wallet_notification_address_copied">Адрес скопирован в буфер обмена</string>
|
||||
<string name="wallet_notification_no_internet">Нет соединения с интернетом</string>
|
||||
<string name="wallet_promo_banner_button_title">Получить с 10%% скидкой</string>
|
||||
<string name="wallet_promo_banner_button_title">Получить с 10% скидкой</string>
|
||||
<string name="wallet_promo_banner_description">Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменивайте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. </string>
|
||||
<string name="wallet_promo_banner_title">Откройте Tangem Wallet</string>
|
||||
<string name="wallet_settings_title">Настройки кошелька</string>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@
|
|||
<string name="balance_hidden_do_not_show_button">Більше не показувати</string>
|
||||
<string name="balance_hidden_got_it_button">Зрозуміло</string>
|
||||
<string name="balance_hidden_title">Баланси приховані</string>
|
||||
<string name="beta_mode_warning_message">Згідно інформації від розробників мережі, токени Kaspa знаходяться у режимі бета. Слідкуйте за оновленнями!</string>
|
||||
<string name="beta_mode_warning_title">Бета режим</string>
|
||||
<string name="biometric_lockout_permanent_warning_description">Будь ласка, відскануйте картку або кільце</string>
|
||||
<string name="biometric_lockout_warning_description">Будь ласка, спробуйте знову через 30 секунд або відскануйте картку або кільце</string>
|
||||
<string name="biometric_lockout_warning_title">Забагато спроб</string>
|
||||
|
|
@ -672,9 +674,7 @@
|
|||
<string name="reset_card_with_backup_to_factory_message">Скидання до заводських налаштувань призведе до повного видалення гаманця з обраної картки або кільця. Ви не зможете відновити поточний гаманець або використати цю картку або кільце для відновлення коду доступу.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">Скидання до заводських налаштувань призведе до повного видалення гаманця з обраної картки або кільця. Ви не зможете відновити поточний гаманець.</string>
|
||||
<string name="ring_promo_text">Власникам кілець - 3 обміни без комісії на Changelly до 15.11!</string>
|
||||
<string name="ring_promo_title">Обмін з 0%% комісією!</string>
|
||||
<string name="russian_bank_card_warning_subtitle">У вас є банківська картка іншої країни та посвідка на проживання або реєстрація за межами Російської Федерації?</string>
|
||||
<string name="russian_bank_card_warning_title">Російські банківські картки наразі не приймаються</string>
|
||||
<string name="ring_promo_title">Обмін з 0% комісією!</string>
|
||||
<string name="save_user_wallet_agreement_access_description">Увійдіть у додаток та слідкуйте за своїм балансом без сканування картки чи кільця</string>
|
||||
<string name="save_user_wallet_agreement_access_title">Доступ до додатку</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">Використовувати біометрію</string>
|
||||
|
|
@ -1014,7 +1014,7 @@
|
|||
<string name="wallet_network_group_title">Мережа %s</string>
|
||||
<string name="wallet_notification_address_copied">Адреса скопійована в буфер обміну</string>
|
||||
<string name="wallet_notification_no_internet">Немає підключення до інтернету</string>
|
||||
<string name="wallet_promo_banner_button_title">Отримати з 10%% знижкою</string>
|
||||
<string name="wallet_promo_banner_button_title">Отримати з 10% знижкою</string>
|
||||
<string name="wallet_promo_banner_description">Доступ до 13,000+ криптовалют. Купуйте, продавайте, обмінюйте та стейкайте одним дотиком.\nЗ’єднайте до трьох карток для бекапу.</string>
|
||||
<string name="wallet_promo_banner_title">Відкрийте Tangem Wallet</string>
|
||||
<string name="wallet_settings_title">Налаштування гаманця</string>
|
||||
|
|
|
|||
|
|
@ -237,7 +237,6 @@
|
|||
<string name="reset_card_to_factory_condition_1">我了解執行此操作後,我將無法再訪問當前錢包</string>
|
||||
<string name="reset_card_with_backup_to_factory_message">恢復原廠設置將從所選卡中完全刪除錢包。您將無法恢復當前錢包或使用卡恢復訪問密碼</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">恢復原廠設置將從所選卡中完全刪除錢包並將其從應用程序中刪除。您將無法恢復當前錢包</string>
|
||||
<string name="russian_bank_card_warning_title">目前不接受俄羅斯銀行卡</string>
|
||||
<string name="save_user_wallet_agreement_access_description">登錄應用程序並在不掃描卡片的情況下檢查您的資產</string>
|
||||
<string name="save_user_wallet_agreement_access_title">訪問應用程序</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">允許使用生物辨識</string>
|
||||
|
|
|
|||
|
|
@ -658,9 +658,7 @@
|
|||
<string name="reset_card_with_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card or ring. You will not be able to restore the current wallet or use the card or ring to recover the access code.</string>
|
||||
<string name="reset_card_without_backup_to_factory_message">Factory Reset will completely delete the wallet from the selected card or ring and remove it from the app. You will not be able to restore the current wallet.</string>
|
||||
<string name="ring_promo_text">Ring owners get 3 commission-free swaps on Changelly until 15.11!</string>
|
||||
<string name="ring_promo_title">Swap With 0%% Fees Now!</string>
|
||||
<string name="russian_bank_card_warning_subtitle">Do you have a bank card from another country and a residence permit or registration outside the Russian Federation?</string>
|
||||
<string name="russian_bank_card_warning_title">Russian bank cards are not currently accepted</string>
|
||||
<string name="ring_promo_title">Swap With 0% Fees Now!</string>
|
||||
<string name="save_user_wallet_agreement_access_description">Log into the app and check your balance without scanning the card or ring</string>
|
||||
<string name="save_user_wallet_agreement_access_title">Access the app</string>
|
||||
<string name="save_user_wallet_agreement_allow_biometrics">Allow to use biometrics</string>
|
||||
|
|
@ -1000,7 +998,7 @@
|
|||
<string name="wallet_network_group_title">%s network</string>
|
||||
<string name="wallet_notification_address_copied">Address was copied to clipboard</string>
|
||||
<string name="wallet_notification_no_internet">No internet connection</string>
|
||||
<string name="wallet_promo_banner_button_title">Get now with 10%% off</string>
|
||||
<string name="wallet_promo_banner_button_title">Get now with 10% off</string>
|
||||
<string name="wallet_promo_banner_description">Access 13,000+ cryptocurrencies. Buy, sell, swap, and stake with a single tap.\nLink up to three cards for a backup.</string>
|
||||
<string name="wallet_promo_banner_title">Discover Tangem Wallet</string>
|
||||
<string name="wallet_settings_title">Wallet settings</string>
|
||||
|
|
@ -1084,7 +1082,7 @@
|
|||
<string name="warning_solana_rent_fee_message">Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free.</string>
|
||||
<string name="warning_some_networks_unreachable_message">Swipe down to refresh or try again later.</string>
|
||||
<string name="warning_some_networks_unreachable_title">Some networks are unreachable</string>
|
||||
<string name="warning_some_token_balances_not_updated">Some token balances could not updated</string>
|
||||
<string name="warning_some_token_balances_not_updated">Some token balances could not be updated</string>
|
||||
<string name="warning_testnet_card_message">This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes.</string>
|
||||
<string name="warning_testnet_card_title">For testing purposes only</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">Discard</string>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.core.ui.components.transactions
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -119,13 +118,12 @@ private fun PendingTxsBlock(pendingTxs: ImmutableList<TransactionState>, isBalan
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun LazyListScope.nonContentItem(state: EmptyTransactionsBlockState, modifier: Modifier = Modifier) {
|
||||
item(key = state::class.java, contentType = state::class.java) {
|
||||
EmptyTransactionBlock(
|
||||
state = state,
|
||||
modifier = modifier
|
||||
.animateItemPlacement()
|
||||
.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16, vertical = TangemTheme.dimens.spacing12)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.core.ui.message.dialog
|
||||
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object Dialogs {
|
||||
|
||||
/**
|
||||
* Card verification failed dialog
|
||||
*
|
||||
* @param errorDescription sdk error description
|
||||
* @param onRequestSupport lambda be invoked when request support action is clicked
|
||||
* @param onCancelClick lambda be invoked when cancel action is clicked
|
||||
*/
|
||||
fun cardVerificationFailed(
|
||||
errorDescription: TextReference,
|
||||
onRequestSupport: () -> Unit,
|
||||
onCancelClick: () -> Unit = {},
|
||||
): DialogMessage {
|
||||
return DialogMessage(
|
||||
message = errorDescription,
|
||||
title = resourceReference(id = R.string.security_alert_title),
|
||||
isDismissable = false,
|
||||
firstActionBuilder = {
|
||||
EventMessageAction(
|
||||
title = resourceReference(id = R.string.alert_button_request_support),
|
||||
onClick = onRequestSupport,
|
||||
)
|
||||
},
|
||||
secondActionBuilder = { cancelAction(onClick = onCancelClick) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ class CryptoCurrencyFactory(
|
|||
return CryptoCurrency.Coin(
|
||||
id = getCoinId(network, blockchain.toCoinId()),
|
||||
network = network,
|
||||
name = blockchain.fullName,
|
||||
name = blockchain.getCoinName(),
|
||||
symbol = blockchain.currency,
|
||||
iconUrl = getCoinIconUrl(blockchain),
|
||||
decimals = blockchain.decimals(),
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ fun getNetwork(
|
|||
return Network(
|
||||
id = Network.ID(blockchain.id),
|
||||
backendId = blockchain.toNetworkId(),
|
||||
name = blockchain.getNetworkName(),
|
||||
name = blockchain.fullName,
|
||||
isTestnet = blockchain.isTestnet(),
|
||||
derivationPath = getNetworkDerivationPath(
|
||||
blockchain = blockchain,
|
||||
|
|
@ -59,7 +59,7 @@ fun getNetwork(
|
|||
return Network(
|
||||
id = networkId,
|
||||
backendId = blockchain.toNetworkId(),
|
||||
name = blockchain.getNetworkName(),
|
||||
name = blockchain.fullName,
|
||||
isTestnet = blockchain.isTestnet(),
|
||||
derivationPath = derivationPath,
|
||||
currencySymbol = blockchain.currency,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.data.common.currency
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchainsdk.compatibility.l2BlockchainsList
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
|
|
@ -78,7 +77,7 @@ class ResponseCryptoCurrenciesFactory(
|
|||
return CryptoCurrency.Coin(
|
||||
id = getCoinId(network, blockchain.toCoinId()),
|
||||
network = network,
|
||||
name = blockchain.getNameForCoin(responseToken),
|
||||
name = blockchain.getCoinName(),
|
||||
symbol = blockchain.getSymbolForCoin(responseToken),
|
||||
decimals = responseToken.decimals,
|
||||
iconUrl = getCoinIconUrl(blockchain),
|
||||
|
|
@ -86,21 +85,6 @@ class ResponseCryptoCurrenciesFactory(
|
|||
)
|
||||
}
|
||||
|
||||
private fun Blockchain.getNameForCoin(responseToken: UserTokensResponse.Token): String {
|
||||
return when (this) {
|
||||
// workaround: for Blockchains full name different than backend name,
|
||||
// get name and symbol from enum Blockchain until backend renamed
|
||||
// [REDACTED_JIRA]
|
||||
Blockchain.Dischain,
|
||||
Blockchain.Telos,
|
||||
Blockchain.Cronos,
|
||||
Blockchain.TON,
|
||||
in l2BlockchainsList,
|
||||
-> this.fullName
|
||||
else -> responseToken.name
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.getSymbolForCoin(responseToken: UserTokensResponse.Token): String {
|
||||
return when (this) {
|
||||
// workaround: Dischain was renamed but backend still returns the old name,
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ internal class DefaultManageTokensRepository(
|
|||
)
|
||||
|
||||
return if (!canHandleBlockchain) {
|
||||
CurrencyUnsupportedState.UnsupportedNetwork(networkName = blockchain.getNetworkName())
|
||||
CurrencyUnsupportedState.UnsupportedNetwork(networkName = blockchain.fullName)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
@ -314,10 +314,10 @@ internal class DefaultManageTokensRepository(
|
|||
return when {
|
||||
// refactor this later by moving all this logic in card config
|
||||
blockchain == Blockchain.Solana && !supportedTokens.contains(Blockchain.Solana) -> {
|
||||
CurrencyUnsupportedState.Token.NetworkTokensUnsupported(networkName = blockchain.getNetworkName())
|
||||
CurrencyUnsupportedState.Token.NetworkTokensUnsupported(networkName = blockchain.fullName)
|
||||
}
|
||||
!userWallet.scanResponse.card.canHandleToken(supportedTokens, blockchain, cardTypesResolver) -> {
|
||||
CurrencyUnsupportedState.Token.UnsupportedCurve(networkName = blockchain.getNetworkName())
|
||||
CurrencyUnsupportedState.Token.UnsupportedCurve(networkName = blockchain.fullName)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ internal object CoinsResponseConverter : Converter<CoinsData, List<Token>> {
|
|||
Token.Network(
|
||||
networkId = network.networkId,
|
||||
standardType = getNetworkStandardType(blockchain).name,
|
||||
name = blockchain.getNetworkName(),
|
||||
name = blockchain.fullName,
|
||||
address = network.contractAddress,
|
||||
iconUrl = getIconUrl(network.networkId, value.imageHost),
|
||||
decimalCount = network.decimalCount?.toInt(),
|
||||
|
|
|
|||
|
|
@ -417,16 +417,16 @@ internal class DefaultCurrenciesRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getFeePaidCurrency(userWalletId: UserWalletId, currency: CryptoCurrency): FeePaidCurrency {
|
||||
override suspend fun getFeePaidCurrency(userWalletId: UserWalletId, network: Network): FeePaidCurrency {
|
||||
return withContext(dispatchers.io) {
|
||||
val blockchain = Blockchain.fromId(currency.network.id.value)
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
when (val feePaidCurrency = blockchain.feePaidCurrency()) {
|
||||
FeePaidSdkCurrency.Coin -> FeePaidCurrency.Coin
|
||||
FeePaidSdkCurrency.SameCurrency -> FeePaidCurrency.SameCurrency
|
||||
is FeePaidSdkCurrency.Token -> {
|
||||
val balance = walletManagersFacade.tokenBalance(
|
||||
userWalletId = userWalletId,
|
||||
network = currency.network,
|
||||
network = network,
|
||||
name = feePaidCurrency.token.name,
|
||||
symbol = feePaidCurrency.token.symbol,
|
||||
contractAddress = feePaidCurrency.token.contractAddress,
|
||||
|
|
@ -434,7 +434,7 @@ internal class DefaultCurrenciesRepository(
|
|||
id = feePaidCurrency.token.id,
|
||||
)
|
||||
FeePaidCurrency.Token(
|
||||
tokenId = getTokenId(network = currency.network, sdkToken = feePaidCurrency.token),
|
||||
tokenId = getTokenId(network = network, sdkToken = feePaidCurrency.token),
|
||||
name = feePaidCurrency.token.name,
|
||||
symbol = feePaidCurrency.token.symbol,
|
||||
contractAddress = feePaidCurrency.token.contractAddress,
|
||||
|
|
|
|||
|
|
@ -20,13 +20,14 @@ import com.tangem.utils.coroutines.runCatching
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class DefaultWalletsRepository(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val seedPhraseNotificationVisibilityStore: RuntimeStateStore<Boolean>,
|
||||
private val seedPhraseNotificationVisibilityStore: RuntimeStateStore<Map<UserWalletId, Boolean>>,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : WalletsRepository {
|
||||
|
||||
|
|
@ -59,7 +60,9 @@ internal class DefaultWalletsRepository(
|
|||
override fun seedPhraseNotificationStatus(userWalletId: UserWalletId): Flow<Boolean> {
|
||||
return channelFlow {
|
||||
launch {
|
||||
seedPhraseNotificationVisibilityStore.get().collectLatest(::send)
|
||||
seedPhraseNotificationVisibilityStore.get()
|
||||
.map { it.getOrDefault(key = userWalletId, defaultValue = false) }
|
||||
.collectLatest(::send)
|
||||
}
|
||||
|
||||
fetchSeedPhraseNotificationStatus(userWalletId)
|
||||
|
|
@ -81,7 +84,7 @@ internal class DefaultWalletsRepository(
|
|||
)
|
||||
}
|
||||
|
||||
seedPhraseNotificationVisibilityStore.store(value = status)
|
||||
updateNotificationVisibility(id = userWalletId, value = status)
|
||||
}
|
||||
|
||||
override suspend fun notifiedSeedPhraseNotification(userWalletId: UserWalletId) {
|
||||
|
|
@ -101,7 +104,7 @@ internal class DefaultWalletsRepository(
|
|||
).getOrThrow()
|
||||
}
|
||||
|
||||
seedPhraseNotificationVisibilityStore.store(value = false)
|
||||
updateNotificationVisibility(id = userWalletId, value = false)
|
||||
}
|
||||
|
||||
override suspend fun declineSeedPhraseNotification(userWalletId: UserWalletId) {
|
||||
|
|
@ -112,7 +115,7 @@ internal class DefaultWalletsRepository(
|
|||
).getOrThrow()
|
||||
}
|
||||
|
||||
seedPhraseNotificationVisibilityStore.store(value = false)
|
||||
updateNotificationVisibility(id = userWalletId, value = false)
|
||||
}
|
||||
|
||||
override suspend fun markWallet2WasCreated(userWalletId: UserWalletId) {
|
||||
|
|
@ -122,4 +125,12 @@ internal class DefaultWalletsRepository(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateNotificationVisibility(id: UserWalletId, value: Boolean) {
|
||||
return seedPhraseNotificationVisibilityStore.update {
|
||||
it.toMutableMap().apply {
|
||||
this[id] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ internal object WalletsDataModule {
|
|||
appPreferencesStore = appPreferencesStore,
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsStore = userWalletsStore,
|
||||
seedPhraseNotificationVisibilityStore = RuntimeStateStore(defaultValue = false),
|
||||
seedPhraseNotificationVisibilityStore = RuntimeStateStore(defaultValue = emptyMap()),
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ interface ScanCardProcessor {
|
|||
suspend fun scan(
|
||||
cardId: String? = null,
|
||||
allowsRequestAccessCodeFromRepository: Boolean = false,
|
||||
analyticsSource: AnalyticsParam.ScreensSources,
|
||||
): CompletionResult<ScanResponse>
|
||||
|
||||
suspend fun scan(
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class GetBalanceNotEnoughForFeeWarningUseCase(
|
|||
coinStatus: CryptoCurrencyStatus,
|
||||
): Either<Throwable, CryptoCurrencyWarning?> = Either.catch {
|
||||
withContext(dispatchers.io) {
|
||||
val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, tokenStatus.currency)
|
||||
val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, tokenStatus.currency.network)
|
||||
val coinBalance = coinStatus.value.amount ?: BigDecimal.ZERO
|
||||
|
||||
val isFeePaidByCoin = tokenStatus.currency is CryptoCurrency.Token
|
||||
|
|
|
|||
|
|
@ -7,12 +7,35 @@ import arrow.core.raise.either
|
|||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class GetCryptoCurrencyUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Returns specific cryptocurrency for a given user wallet.
|
||||
*
|
||||
* !!! Important Use only [CryptoCurrency.ID.value] as cryptoCurrencyId
|
||||
*
|
||||
* @param userWallet The user's wallet.
|
||||
* @param cryptoCurrencyId The ID of the cryptocurrency.
|
||||
* @return An [Either] representing success (Right) or an error (Left) in fetching the status.
|
||||
*/
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrencyId: String,
|
||||
): Either<CurrencyStatusError, CryptoCurrency> {
|
||||
return either {
|
||||
if (userWallet.isMultiCurrency) {
|
||||
getCurrency(userWallet.walletId, cryptoCurrencyId)
|
||||
} else {
|
||||
getPrimaryCurrency(userWallet.walletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns specific cryptocurrency for a given user wallet.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class GetCurrencyCheckUseCase(
|
|||
currencyStatus: CryptoCurrencyStatus,
|
||||
amount: BigDecimal?,
|
||||
fee: BigDecimal?,
|
||||
balanceAfterTransaction: BigDecimal?,
|
||||
feeCurrencyBalanceAfterTransaction: BigDecimal?,
|
||||
recipientAddress: String? = null,
|
||||
): CryptoCurrencyCheck {
|
||||
return withContext(dispatchers.io) {
|
||||
|
|
@ -32,7 +32,7 @@ class GetCurrencyCheckUseCase(
|
|||
val rentWarning = currencyChecksRepository.getRentExemptionError(
|
||||
userWalletId = userWalletId,
|
||||
currencyStatus = currencyStatus,
|
||||
balanceAfterTransaction = balanceAfterTransaction ?: BigDecimal.ZERO,
|
||||
balanceAfterTransaction = feeCurrencyBalanceAfterTransaction ?: BigDecimal.ZERO,
|
||||
)
|
||||
val isAccountFunded = recipientAddress?.let {
|
||||
currencyChecksRepository.checkIfAccountFunded(
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ class GetCurrencyWarningsUseCase(
|
|||
coinStatus: CryptoCurrencyStatus,
|
||||
tokenStatus: CryptoCurrencyStatus,
|
||||
): CryptoCurrencyWarning? {
|
||||
val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, tokenStatus.currency)
|
||||
val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, tokenStatus.currency.network)
|
||||
val isNetworkFeeZero = currenciesRepository.isNetworkFeeZero(userWalletId, tokenStatus.currency.network)
|
||||
return when {
|
||||
feePaidCurrency is FeePaidCurrency.Coin &&
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ class GetFeePaidCryptoCurrencyStatusSyncUseCase(
|
|||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): Either<TokenListError, CryptoCurrencyStatus?> {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, cryptoCurrency)
|
||||
val network = cryptoCurrency.network
|
||||
val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, network)
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
userWalletId = userWalletId,
|
||||
currenciesRepository = currenciesRepository,
|
||||
|
|
@ -39,7 +40,7 @@ class GetFeePaidCryptoCurrencyStatusSyncUseCase(
|
|||
when (feePaidCurrency) {
|
||||
FeePaidCurrency.Coin ->
|
||||
operations
|
||||
.getNetworkCoinSync(cryptoCurrency.network.id, cryptoCurrency.network.derivationPath)
|
||||
.getNetworkCoinSync(network.id, network.derivationPath)
|
||||
.getOrNull()
|
||||
FeePaidCurrency.SameCurrency,
|
||||
is FeePaidCurrency.FeeResource,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class IsAmountSubtractAvailableUseCase(
|
|||
suspend operator fun invoke(userWalletId: UserWalletId, currency: CryptoCurrency): Either<Throwable, Boolean> =
|
||||
Either.catch {
|
||||
withContext(dispatchers.io) {
|
||||
when (val feeCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, currency)) {
|
||||
when (val feeCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, currency.network)) {
|
||||
is FeePaidCurrency.Coin -> currency is CryptoCurrency.Coin
|
||||
is FeePaidCurrency.SameCurrency -> true
|
||||
is FeePaidCurrency.Token -> currency.id == feeCurrency.tokenId
|
||||
|
|
|
|||
|
|
@ -231,9 +231,9 @@ interface CurrenciesRepository {
|
|||
): Boolean
|
||||
|
||||
/**
|
||||
* Retrieves fee paid currency for specific [currency].
|
||||
* Retrieves fee paid currency for specific [network].
|
||||
*/
|
||||
suspend fun getFeePaidCurrency(userWalletId: UserWalletId, currency: CryptoCurrency): FeePaidCurrency
|
||||
suspend fun getFeePaidCurrency(userWalletId: UserWalletId, network: Network): FeePaidCurrency
|
||||
|
||||
/**
|
||||
* Creates token [cryptoCurrency] based on current token and [network] it`s will be added
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ internal class MockCurrenciesRepository(
|
|||
return false
|
||||
}
|
||||
|
||||
override suspend fun getFeePaidCurrency(userWalletId: UserWalletId, currency: CryptoCurrency): FeePaidCurrency {
|
||||
override suspend fun getFeePaidCurrency(userWalletId: UserWalletId, network: Network): FeePaidCurrency {
|
||||
return FeePaidCurrency.Coin
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,18 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.toWrappedList
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.analytics.OnboardingEvent
|
||||
|
|
@ -21,6 +28,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.resetB
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.state.MultiWalletBackupUM
|
||||
import com.tangem.sdk.api.BackupServiceHolder
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.sdk.extensions.localizedDescriptionRes
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -31,13 +39,16 @@ import javax.inject.Inject
|
|||
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
@Suppress("LongParameterList")
|
||||
class MultiWalletBackupModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val backupServiceHolder: BackupServiceHolder,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val analyticsHandler: AnalyticsEventHandler,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
) : Model() {
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
|
|
@ -65,10 +76,10 @@ class MultiWalletBackupModel @Inject constructor(
|
|||
init {
|
||||
// for wallet 1 this event is sent in Wallet1ChooseOptionModel
|
||||
if (scanResponse.productType == ProductType.Wallet2 || scanResponse.productType == ProductType.Ring) {
|
||||
analyticsHandler.send(OnboardingEvent.Backup.ScreenOpened)
|
||||
analyticsEventHandler.send(OnboardingEvent.Backup.ScreenOpened)
|
||||
}
|
||||
|
||||
analyticsHandler.send(OnboardingEvent.Backup.Started)
|
||||
analyticsEventHandler.send(OnboardingEvent.Backup.Started)
|
||||
}
|
||||
|
||||
private fun getInitState(): MultiWalletBackupUM {
|
||||
|
|
@ -153,7 +164,7 @@ class MultiWalletBackupModel @Inject constructor(
|
|||
|
||||
modelScope.launch { eventFlow.emit(MultiWalletBackupComponent.Event.Done) }
|
||||
|
||||
analyticsHandler.send(OnboardingEvent.Backup.Finished(cardsCount = state.value.numberOfBackupCards + 1))
|
||||
analyticsEventHandler.send(OnboardingEvent.Backup.Finished(cardsCount = state.value.numberOfBackupCards + 1))
|
||||
}
|
||||
|
||||
private fun showOnlyOneBackupWarningDialog() {
|
||||
|
|
@ -203,6 +214,7 @@ class MultiWalletBackupModel @Inject constructor(
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
when (val error = result.error) {
|
||||
is TangemSdkError.CardVerificationFailed -> showCardVerificationFailedDialog(error)
|
||||
is TangemSdkError.BackupFailedNotEmptyWallets -> {
|
||||
_uiState.update { st ->
|
||||
st.copy(
|
||||
|
|
@ -212,7 +224,7 @@ class MultiWalletBackupModel @Inject constructor(
|
|||
_uiState.update { it.copy(dialog = null) }
|
||||
},
|
||||
onDismissClick = {
|
||||
analyticsHandler.send(OnboardingEvent.Backup.ResetCancelEvent)
|
||||
analyticsEventHandler.send(OnboardingEvent.Backup.ResetCancelEvent)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
@ -234,8 +246,29 @@ class MultiWalletBackupModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun showCardVerificationFailedDialog(error: TangemSdkError.CardVerificationFailed) {
|
||||
analyticsEventHandler.send(
|
||||
event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed(AnalyticsParam.ScreensSources.Backup),
|
||||
)
|
||||
|
||||
val resource = error.localizedDescriptionRes()
|
||||
val resId = resource.resId ?: com.tangem.core.ui.R.string.common_unknown_error
|
||||
val resArgs = resource.args.map { it.value }
|
||||
|
||||
uiMessageSender.send(
|
||||
message = Dialogs.cardVerificationFailed(
|
||||
errorDescription = resourceReference(id = resId, resArgs.toWrappedList()),
|
||||
onRequestSupport = {
|
||||
modelScope.launch {
|
||||
sendFeedbackEmailUseCase(type = FeedbackEmailType.CardAttestationFailed)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun resetBackupCard(cardId: String) {
|
||||
analyticsHandler.send(OnboardingEvent.Backup.ResetPerformEvent)
|
||||
analyticsEventHandler.send(OnboardingEvent.Backup.ResetPerformEvent)
|
||||
|
||||
modelScope.launch {
|
||||
tangemSdkManager.resetToFactorySettings(
|
||||
|
|
|
|||
|
|
@ -95,15 +95,19 @@ internal class SendNotificationFactory(
|
|||
val feeError = (feeState.feeSelectorState as? FeeSelectorState.Error)?.error
|
||||
|
||||
val recipientAddress = state.recipientState?.addressTextField?.value
|
||||
val statusValue = cryptoCurrencyStatus.value as? CryptoCurrencyStatus.Loaded
|
||||
val balanceAfterTransaction = statusValue?.let { it.amount - sendingAmount - feeValue }
|
||||
val feeCurrencyBalanceAfterTransaction = getFeeCurrencyBalanceAfterTx(
|
||||
feeCurrencyStatus = feeCryptoCurrencyStatusProvider(),
|
||||
sendingCurrencyStatus = cryptoCurrencyStatus,
|
||||
sendingAmount = sendingAmount,
|
||||
feeValue = feeValue,
|
||||
)
|
||||
val currencyCheck = getCurrencyCheckUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currencyStatus = cryptoCurrencyStatus,
|
||||
amount = sendingAmount,
|
||||
fee = feeValue,
|
||||
recipientAddress = recipientAddress,
|
||||
balanceAfterTransaction = balanceAfterTransaction,
|
||||
feeCurrencyBalanceAfterTransaction = feeCurrencyBalanceAfterTransaction,
|
||||
)
|
||||
buildList {
|
||||
addErrorNotifications(
|
||||
|
|
@ -138,6 +142,23 @@ internal class SendNotificationFactory(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getFeeCurrencyBalanceAfterTx(
|
||||
feeCurrencyStatus: CryptoCurrencyStatus?,
|
||||
sendingCurrencyStatus: CryptoCurrencyStatus,
|
||||
sendingAmount: BigDecimal,
|
||||
feeValue: BigDecimal,
|
||||
): BigDecimal? {
|
||||
val sendingCurrencyBalance = sendingCurrencyStatus.value as? CryptoCurrencyStatus.Loaded
|
||||
val feeCurrencyBalance = feeCurrencyStatus?.value as? CryptoCurrencyStatus.Loaded
|
||||
if (feeCurrencyStatus?.value !is CryptoCurrencyStatus.Loaded) return null
|
||||
return when {
|
||||
feeCurrencyStatus == sendingCurrencyStatus -> sendingCurrencyBalance?.let {
|
||||
it.amount - sendingAmount - feeValue
|
||||
}
|
||||
else -> feeCurrencyBalance?.let { it.amount - feeValue }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableList<NotificationUM>.addErrorNotifications(
|
||||
feeError: GetFeeError?,
|
||||
sendingAmount: BigDecimal,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.common
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
|
|
@ -10,7 +9,6 @@ import com.tangem.core.ui.components.notifications.Notification
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
internal fun LazyListScope.notifications(
|
||||
notifications: ImmutableList<NotificationUM>,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -31,7 +29,7 @@ internal fun LazyListScope.notifications(
|
|||
config = item.config,
|
||||
modifier = modifier
|
||||
.padding(top = topPadding)
|
||||
.animateItemPlacement(),
|
||||
.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||
containerColor = when (item) {
|
||||
is NotificationUM.Error.TokenExceedsBalance,
|
||||
is NotificationUM.Warning.NetworkFeeUnreachable,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
|||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTron
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
|
@ -41,8 +42,16 @@ internal class AmountRequirementStateTransformer(
|
|||
val isIntegerOnlyError = isIntegerOnlyError(amountState, actionType)
|
||||
|
||||
val cryptoAmount = amountState.amountTextField.cryptoAmount
|
||||
val roundedDownCrypto = cryptoAmount.value?.setScale(0, RoundingMode.DOWN)
|
||||
val value = roundedDownCrypto?.parseBigDecimal(0).orEmpty()
|
||||
val roundedDownCrypto = cryptoAmount.value
|
||||
?.setScale(0, RoundingMode.DOWN)
|
||||
?.parseBigDecimal(0)
|
||||
.orEmpty()
|
||||
|
||||
val isAmountZeroOrNull = if (amountState.amountTextField.isFiatValue) {
|
||||
amountState.amountTextField.fiatAmount.value.isNullOrZero()
|
||||
} else {
|
||||
amountState.amountTextField.cryptoAmount.value.isNullOrZero()
|
||||
}
|
||||
|
||||
val errorText = when {
|
||||
amountState.amountTextField.isError -> amountState.amountTextField.error
|
||||
|
|
@ -50,11 +59,11 @@ internal class AmountRequirementStateTransformer(
|
|||
isIntegerOnlyError -> when (actionType) {
|
||||
StakingActionCommonType.Enter -> resourceReference(
|
||||
R.string.staking_amount_tron_integer_error,
|
||||
wrappedList(value),
|
||||
wrappedList(roundedDownCrypto),
|
||||
)
|
||||
StakingActionCommonType.Exit -> resourceReference(
|
||||
R.string.staking_amount_tron_integer_error_unstaking,
|
||||
wrappedList(value),
|
||||
wrappedList(roundedDownCrypto),
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
|
|
@ -62,7 +71,7 @@ internal class AmountRequirementStateTransformer(
|
|||
}
|
||||
val isError = amountState.amountTextField.isError || requirementError != null
|
||||
return amountState.copy(
|
||||
isPrimaryButtonEnabled = !isError,
|
||||
isPrimaryButtonEnabled = !isAmountZeroOrNull && !isError,
|
||||
amountTextField = amountState.amountTextField.copy(
|
||||
isError = isError,
|
||||
isWarning = isIntegerOnlyError,
|
||||
|
|
|
|||
|
|
@ -647,7 +647,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
currencyStatus = cryptoCurrencyStatus,
|
||||
amount = amount,
|
||||
fee = fee,
|
||||
balanceAfterTransaction = balanceAfterTransaction,
|
||||
feeCurrencyBalanceAfterTransaction = balanceAfterTransaction,
|
||||
)
|
||||
stateController.update(
|
||||
AddStakingNotificationsTransformer(
|
||||
|
|
@ -850,13 +850,11 @@ internal class StakingViewModel @Inject constructor(
|
|||
)
|
||||
getCurrencyStatusUpdatesUseCase(userWalletId, cryptoCurrencyId, false)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.distinctUntilChangedBy { it.getOrNull()?.value?.yieldBalance }
|
||||
.filter { value.currentStep == StakingStep.InitialInfo }
|
||||
.onEach { maybeStatus ->
|
||||
maybeStatus.fold(
|
||||
ifRight = { status ->
|
||||
if (status.value !is CryptoCurrencyStatus.Loaded) return@fold
|
||||
|
||||
if (!isInitialInfoAnalyticSent) {
|
||||
isInitialInfoAnalyticSent = true
|
||||
val balances = status.value.yieldBalance as? YieldBalance.Data
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
currencyStatus = fromTokenStatus,
|
||||
amount = amount.value,
|
||||
fee = fee,
|
||||
balanceAfterTransaction = balanceAfterTransaction,
|
||||
feeCurrencyBalanceAfterTransaction = balanceAfterTransaction,
|
||||
)
|
||||
|
||||
return currencyCheck
|
||||
|
|
@ -1323,13 +1323,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
amount.value > reducedBalance -> {
|
||||
IncludeFeeInAmount.BalanceNotEnough
|
||||
}
|
||||
amountWithFee < reducedBalance -> {
|
||||
amountWithFee <= reducedBalance -> {
|
||||
IncludeFeeInAmount.Excluded
|
||||
}
|
||||
else -> {
|
||||
if (feeValue < amount.value) {
|
||||
IncludeFeeInAmount.Included(
|
||||
SwapAmount(
|
||||
amountSubtractFee = SwapAmount(
|
||||
reducedBalance - feeValue,
|
||||
getNativeToken(fromToken.network.backendId).decimals,
|
||||
),
|
||||
|
|
@ -1994,7 +1994,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private suspend fun getFeePaidCurrency(currency: CryptoCurrency): FeePaidCurrency {
|
||||
return currenciesRepository.getFeePaidCurrency(
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
network = currency.network,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ dependencies {
|
|||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.blockchain)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.firebase.perf) {
|
||||
exclude(group = "com.google.firebase", module = "protolite-well-known-types")
|
||||
exclude(group = "com.google.protobuf", module = "protobuf-javalite")
|
||||
}
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ internal class WalletDeepLinksHandler @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun onSellCurrencyDeepLink(userWallet: UserWallet, data: SellCurrencyDeepLink.Data) {
|
||||
val cryptoCurrency = getCryptoCurrencyUseCase(userWallet.walletId, data.currencyId).getOrNull()
|
||||
val cryptoCurrency = getCryptoCurrencyUseCase(userWallet, data.currencyId).getOrNull()
|
||||
|
||||
if (cryptoCurrency == null) {
|
||||
Timber.e("onSellCurrencyDeepLink cryptoCurrency is null")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.analytics.utils
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.google.firebase.perf.FirebasePerformance
|
||||
import com.google.firebase.perf.metrics.Trace
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.common.extensions.isZero
|
||||
|
|
@ -12,6 +14,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
|
|
@ -31,11 +34,20 @@ internal class TokenListAnalyticsSender @Inject constructor(
|
|||
|
||||
private val balanceWasSentMap = mutableMapOf<String, Boolean>()
|
||||
private val mutex = Mutex()
|
||||
private val loadingTraces = mutableMapOf<UserWalletId, Trace>()
|
||||
|
||||
suspend fun send(displayedUiState: WalletState?, userWallet: UserWallet, tokenList: TokenList) {
|
||||
if (screenLifecycleProvider.isBackgroundState.value) return
|
||||
if (displayedUiState == null || displayedUiState.pullToRefreshConfig.isRefreshing) return
|
||||
if (tokenList.totalFiatBalance is TotalFiatBalance.Loading) return
|
||||
|
||||
if (tokenList.totalFiatBalance is TotalFiatBalance.Loading) {
|
||||
startLoadingTraceIfNeeded(userWallet.walletId, tokenList)
|
||||
return
|
||||
}
|
||||
|
||||
if (isTerminalState(tokenList.totalFiatBalance)) {
|
||||
stopLoadingTraceIfNeeded(userWallet.walletId, tokenList.totalFiatBalance)
|
||||
}
|
||||
|
||||
val currenciesStatuses = tokenList.flattenCurrencies()
|
||||
|
||||
|
|
@ -45,6 +57,35 @@ internal class TokenListAnalyticsSender @Inject constructor(
|
|||
sendTokenBalancesIfNeeded(currenciesStatuses)
|
||||
}
|
||||
|
||||
private suspend fun startLoadingTraceIfNeeded(userWalletId: UserWalletId, tokenList: TokenList) {
|
||||
mutex.withLock {
|
||||
if (!loadingTraces.containsKey(userWalletId)) {
|
||||
val trace = FirebasePerformance.getInstance().newTrace(BALANCE_LOADED_TRACE_NAME)
|
||||
trace.start()
|
||||
trace.putAttribute(TOKENS_COUNT, tokenList.flattenCurrencies().size.toString())
|
||||
loadingTraces[userWalletId] = trace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun stopLoadingTraceIfNeeded(userWalletId: UserWalletId, totalFiatBalance: TotalFiatBalance) {
|
||||
mutex.withLock {
|
||||
loadingTraces[userWalletId]?.apply {
|
||||
when (totalFiatBalance) {
|
||||
is TotalFiatBalance.Loaded -> putAttribute(HAS_ERROR, "No")
|
||||
is TotalFiatBalance.Failed -> putAttribute(HAS_ERROR, "Yes")
|
||||
else -> { /* Intentionally do nothing */ }
|
||||
}
|
||||
stop()
|
||||
loadingTraces.remove(userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTerminalState(balance: TotalFiatBalance): Boolean {
|
||||
return balance is TotalFiatBalance.Failed || balance is TotalFiatBalance.Loaded
|
||||
}
|
||||
|
||||
private fun sendBalanceLoadedEventIfNeeded(
|
||||
fiatBalance: TotalFiatBalance,
|
||||
currenciesStatuses: List<CryptoCurrencyStatus>,
|
||||
|
|
@ -179,4 +220,10 @@ internal class TokenListAnalyticsSender @Inject constructor(
|
|||
analyticsEventHandler.send(MainScreen.NetworksUnreachable(unreachableCurrencies))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BALANCE_LOADED_TRACE_NAME = "Total_balance_loaded"
|
||||
const val HAS_ERROR = "has_error"
|
||||
const val TOKENS_COUNT = "tokens_count"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import arrow.core.raise.either
|
|||
import arrow.core.raise.ensure
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
|
|
@ -22,7 +23,7 @@ internal class ScanCardToUnlockWalletClickHandler @Inject constructor(
|
|||
|
||||
suspend operator fun invoke(walletId: UserWalletId): Either<ScanCardToUnlockWalletError, Unit> {
|
||||
return either {
|
||||
when (val result = scanCardProcessor.scan()) {
|
||||
when (val result = scanCardProcessor.scan(analyticsSource = AnalyticsParam.ScreensSources.Main)) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.UserCancelled) {
|
||||
scanFailsCounter++
|
||||
|
|
|
|||
|
|
@ -55,6 +55,18 @@ internal enum class Wallet2CobrandImage(
|
|||
batchIds = setOf("AF97"),
|
||||
),
|
||||
|
||||
CashClubGold(
|
||||
cards2ResId = R.drawable.ill_cashclubgold_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_cashclubgold_card3_120_106,
|
||||
batchIds = setOf("BB000004"),
|
||||
),
|
||||
|
||||
Changenow(
|
||||
cards2ResId = R.drawable.ill_changenow_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_changenow_card3_120_106,
|
||||
batchIds = setOf("BB000013"),
|
||||
),
|
||||
|
||||
CoinMetrica(
|
||||
cards2ResId = R.drawable.ill_coin_metrica_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_coin_metrica_card3_120_106,
|
||||
|
|
@ -85,12 +97,24 @@ internal enum class Wallet2CobrandImage(
|
|||
batchIds = setOf("AF32"),
|
||||
),
|
||||
|
||||
GetsMine(
|
||||
cards2ResId = R.drawable.ill_gets_mine_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_gets_mine_card3_120_106,
|
||||
batchIds = setOf("BB000008"),
|
||||
),
|
||||
|
||||
Grim(
|
||||
cards2ResId = R.drawable.ill_grim_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_grim_card3_120_106,
|
||||
batchIds = setOf("AF13"),
|
||||
),
|
||||
|
||||
Hodl(
|
||||
cards2ResId = R.drawable.ill_hodl_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_hodl_card3_120_106,
|
||||
batchIds = setOf("BB000009"),
|
||||
),
|
||||
|
||||
Jr(
|
||||
cards2ResId = R.drawable.ill_jr_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_jr_card3_120_106,
|
||||
|
|
@ -139,12 +163,24 @@ internal enum class Wallet2CobrandImage(
|
|||
batchIds = setOf("AF52"),
|
||||
),
|
||||
|
||||
Kango(
|
||||
cards2ResId = R.drawable.ill_kango_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_kango_card3_120_106,
|
||||
batchIds = setOf("BB000006"),
|
||||
),
|
||||
|
||||
Konan(
|
||||
cards2ResId = R.drawable.ill_konan_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_konan_card3_120_106,
|
||||
batchIds = setOf("AF93"),
|
||||
),
|
||||
|
||||
Kroak(
|
||||
cards2ResId = R.drawable.ill_kroak_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_kroak_card3_120_106,
|
||||
batchIds = setOf("BB000011"),
|
||||
),
|
||||
|
||||
Neiro(
|
||||
cards2ResId = R.drawable.ill_neiro_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_neiro_card3_120_106,
|
||||
|
|
@ -157,6 +193,12 @@ internal enum class Wallet2CobrandImage(
|
|||
batchIds = setOf("AF26"),
|
||||
),
|
||||
|
||||
PassimPay(
|
||||
cards2ResId = R.drawable.ill_passimpay_cards2_120_106,
|
||||
cards3ResId = R.drawable.ill_passimpay_cards3_120_106,
|
||||
batchIds = setOf("BB000007"),
|
||||
),
|
||||
|
||||
// for multicolored cards use image of 3 cards in all cases
|
||||
Pastel(
|
||||
cards2ResId = R.drawable.ill_pastel_cards3_120_106,
|
||||
|
|
@ -170,12 +212,24 @@ internal enum class Wallet2CobrandImage(
|
|||
batchIds = setOf("AF34"),
|
||||
),
|
||||
|
||||
Rizo(
|
||||
cards2ResId = R.drawable.ill_rizo_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_rizo_card3_120_106,
|
||||
batchIds = setOf("BB000012"),
|
||||
),
|
||||
|
||||
SatoshiFriends(
|
||||
cards2ResId = R.drawable.ill_satoshi_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_satoshi_card3_120_106,
|
||||
batchIds = setOf("AF19"),
|
||||
),
|
||||
|
||||
SinCity(
|
||||
cards2ResId = R.drawable.ill_sincity_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_sincity_card3_120_106,
|
||||
batchIds = setOf("BB000010"),
|
||||
),
|
||||
|
||||
StealthCard(
|
||||
cards2ResId = R.drawable.ill_stealth_cards2_120_106,
|
||||
cards3ResId = R.drawable.ill_stealth_cards3_120_106,
|
||||
|
|
@ -194,6 +248,12 @@ internal enum class Wallet2CobrandImage(
|
|||
batchIds = setOf("AF07"),
|
||||
),
|
||||
|
||||
USA(
|
||||
cards2ResId = R.drawable.ill_usa_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_usa_card3_120_106,
|
||||
batchIds = setOf("AF91"),
|
||||
),
|
||||
|
||||
VeChain(
|
||||
cards2ResId = R.drawable.ill_vechain_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_vechain_card3_120_106,
|
||||
|
|
@ -207,6 +267,12 @@ internal enum class Wallet2CobrandImage(
|
|||
batchIds = setOf("AF40", "AF41", "AF42", "AF75", "AF76", "AF77"),
|
||||
),
|
||||
|
||||
Vnish(
|
||||
cards2ResId = R.drawable.ill_vnish_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_vnish_card3_120_106,
|
||||
batchIds = setOf("BB000005"),
|
||||
),
|
||||
|
||||
VoltInu(
|
||||
cards2ResId = R.drawable.ill_volt_inu_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_volt_inu_card3_120_106,
|
||||
|
|
@ -218,4 +284,16 @@ internal enum class Wallet2CobrandImage(
|
|||
cards3ResId = R.drawable.ill_white_card3_120_106,
|
||||
batchIds = setOf("AF15"),
|
||||
),
|
||||
|
||||
WildGoat(
|
||||
cards2ResId = R.drawable.ill_wildgoat_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_wildgoat_card3_120_106,
|
||||
batchIds = setOf("BB000001"),
|
||||
),
|
||||
|
||||
Winter(
|
||||
cards2ResId = R.drawable.ill_winter_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_winter_card3_120_106,
|
||||
batchIds = setOf("AF85", "AF86", "AF87", "AF990013", "AF990012", "AF990011"),
|
||||
),
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ internal fun LazyListScope.lazyActions(
|
|||
item(key = ACTIONS_CONTENT_TYPE + selectedWalletIndex, contentType = ACTIONS_CONTENT_TYPE) {
|
||||
HorizontalActionChips(
|
||||
buttons = actions.map(WalletManageButton::config).toImmutableList(),
|
||||
modifier = modifier.animateItem(),
|
||||
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ internal fun LazyListScope.actions(
|
|||
modifier = modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth()
|
||||
.animateItem(),
|
||||
.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -31,13 +31,13 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
|
|||
is WalletNotification.NoteMigration -> {
|
||||
NoteMigrationNotification(
|
||||
config = it.config,
|
||||
modifier = modifier.animateItem(),
|
||||
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
Notification(
|
||||
config = it.config,
|
||||
modifier = modifier.animateItem(),
|
||||
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||
iconTint = when (it) {
|
||||
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
|
||||
is WalletNotification.Informational -> TangemTheme.colors.icon.accent
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ private fun LazyListScope.nonContentItem(modifier: Modifier = Modifier) {
|
|||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.animateItem()
|
||||
.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
||||
.padding(top = TangemTheme.dimens.spacing96),
|
||||
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing16),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlock
|
||||
|
|
@ -14,12 +13,11 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
internal fun LazyListScope.marketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier) {
|
||||
item(
|
||||
key = MarketPriceBlockState::class.java,
|
||||
contentType = MarketPriceBlockState::class.java,
|
||||
) {
|
||||
MarketPriceBlock(state = state, modifier = modifier.animateItemPlacement())
|
||||
MarketPriceBlock(state = state, modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null))
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 28 KiB |