Updated on 2026-08-14
This commit is contained in:
commit
69174e20cb
19 changed files with 144 additions and 86 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit cce49feacb18dc9ad7cc5da62d58f1b3338a1057
|
||||
Subproject commit f1aa2f2cbf374ecba5da753d9f51c9df81749ef2
|
||||
|
|
@ -92,6 +92,7 @@ import kotlinx.coroutines.flow.*
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
lateinit var tangemSdkManager: TangemSdkManager
|
||||
lateinit var backupService: BackupService
|
||||
|
|
@ -330,7 +331,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
|
||||
private fun installAppTheme() {
|
||||
appThemeModeFlow = createAppThemeModeFlow()
|
||||
val mode = runBlocking { appThemeModeFlow.first() }
|
||||
val mode = runBlocking {
|
||||
withTimeoutOrNull(APP_THEME_LOAD_TIMEOUT.seconds) {
|
||||
appThemeModeFlow.first()
|
||||
} ?: AppThemeMode.DEFAULT
|
||||
}
|
||||
|
||||
updateAppTheme(mode)
|
||||
}
|
||||
|
|
@ -599,4 +604,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
.onRight { Timber.d("Submitting hashes succeeded") }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val APP_THEME_LOAD_TIMEOUT = 2
|
||||
}
|
||||
}
|
||||
|
|
@ -163,7 +163,9 @@ internal class WcSessionRequestConverter(
|
|||
return sessionsRepository.loadSessions(userWalletId)
|
||||
.firstOrNull { it.topic == sessionRequest.topic }
|
||||
?.accounts?.firstOrNull {
|
||||
it.chainId == sessionRequest.chainId && it.walletAddress.lowercase() == walletAddress?.lowercase()
|
||||
// if walletAddress == null take first account
|
||||
it.chainId == sessionRequest.chainId &&
|
||||
(walletAddress == null || it.walletAddress.lowercase() == walletAddress.lowercase())
|
||||
}?.derivationPath
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain.mapper
|
||||
|
||||
import com.tangem.blockchain.blockchains.solana.solanaj.core.SolanaTransaction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.solana.SolanaTransactionRequest
|
||||
import org.p2p.solanaj.core.AccountMeta
|
||||
import org.p2p.solanaj.core.PublicKey
|
||||
import org.p2p.solanaj.core.TransactionInstruction
|
||||
|
||||
internal fun SolanaTransactionRequest.mapToTransaction(): SolanaTransaction {
|
||||
val from = PublicKey(feePayer)
|
||||
val instructions = instructions.map(SolanaTransactionRequest.Instruction::mapToInstruction)
|
||||
|
||||
return SolanaTransaction(from)
|
||||
.apply {
|
||||
instructions.forEach(::addInstruction)
|
||||
setRecentBlockHash(recentBlockhash)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SolanaTransactionRequest.Instruction.mapToInstruction() = TransactionInstruction(
|
||||
/* programId = */ PublicKey(programId),
|
||||
/* keys = */ keys.map(SolanaTransactionRequest.Key::mapToAccountMeta),
|
||||
/* data = */ data.toByteArray(),
|
||||
)
|
||||
|
||||
private fun SolanaTransactionRequest.Key.mapToAccountMeta() = AccountMeta(
|
||||
/* publicKey = */ PublicKey(publicKey),
|
||||
/* isSigner = */ isSigner,
|
||||
/* isWritable = */ isWritable,
|
||||
)
|
||||
|
|
@ -7,39 +7,8 @@ import com.tangem.tap.domain.walletconnect2.domain.WcRequestData
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class SolanaTransactionRequest(
|
||||
@Json(name = "feePayer")
|
||||
val feePayer: String,
|
||||
|
||||
@Json(name = "recentBlockhash")
|
||||
val recentBlockhash: String,
|
||||
|
||||
@Json(name = "instructions")
|
||||
val instructions: List<Instruction>,
|
||||
val feePayer: String?,
|
||||
|
||||
@Json(name = "transaction")
|
||||
val transaction: String,
|
||||
) : WcRequestData {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Instruction(
|
||||
@Json(name = "programId")
|
||||
val programId: String,
|
||||
|
||||
@Json(name = "data")
|
||||
val data: String,
|
||||
|
||||
@Json(name = "keys")
|
||||
val keys: List<Key>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Key(
|
||||
@Json(name = "isSigner")
|
||||
val isSigner: Boolean,
|
||||
|
||||
@Json(name = "isWritable")
|
||||
val isWritable: Boolean,
|
||||
|
||||
@Json(name = "pubkey")
|
||||
val publicKey: String,
|
||||
)
|
||||
}
|
||||
) : WcRequestData
|
||||
|
|
@ -53,6 +53,9 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsSta
|
|||
store.inject(DaggerGraphState::balanceHidingRepository)
|
||||
.getBalanceHidingSettings().isHidingEnabledInSettings
|
||||
},
|
||||
needEnrollBiometrics = runBlocking {
|
||||
runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() ?: false
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ import com.tangem.wallet.R
|
|||
internal object SignTransactionDialog {
|
||||
|
||||
fun create(preparedData: WcPreparedRequest.SignTransaction, context: Context): AlertDialog {
|
||||
val message = context.getString(R.string.wallet_connect_alert_sign_message, "")
|
||||
val signMessage = context.getString(R.string.wallet_connect_alert_sign_message, "")
|
||||
val message = "${preparedData.preparedRequestData.dAppName}\n$signMessage"
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(context.getString(R.string.wallet_connect_title))
|
||||
setMessage(message)
|
||||
|
|
|
|||
|
|
@ -37,4 +37,10 @@ data class ExpressErrorValue(
|
|||
|
||||
@Json(name = "expressFromDecimals")
|
||||
val expressFromDecimals: Int?,
|
||||
|
||||
@Json(name = "fromAmount")
|
||||
val fromAmount: String?,
|
||||
|
||||
@Json(name = "fromAmountProvider")
|
||||
val fromAmountProvider: String?,
|
||||
)
|
||||
|
|
@ -10,6 +10,8 @@ import androidx.datastore.preferences.core.Preferences
|
|||
import androidx.datastore.preferences.core.emptyPreferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import com.tangem.datasource.local.preferences.PreferencesDataStore.INSTANCE
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.APP_LOGS_KEY
|
||||
import com.tangem.datasource.local.preferences.utils.CleanupKeyMigration
|
||||
import com.tangem.datasource.local.preferences.utils.SharedPreferencesKeyMigration
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
|
|
@ -77,6 +79,7 @@ internal object PreferencesDataStore {
|
|||
legacyKeyName = LEGACY_DEFAULT_KEY_NAME,
|
||||
keyName = PreferencesKeys.BALANCE_HIDING_SETTINGS_KEY.name,
|
||||
),
|
||||
CleanupKeyMigration(key = APP_LOGS_KEY),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.datasource.local.preferences.utils
|
||||
|
||||
import androidx.datastore.core.DataMigration
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
|
||||
internal class CleanupKeyMigration<T>(private val key: Preferences.Key<T>) : DataMigration<Preferences> {
|
||||
|
||||
override suspend fun cleanUp() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
override suspend fun shouldMigrate(currentData: Preferences): Boolean = currentData.contains(key)
|
||||
|
||||
override suspend fun migrate(currentData: Preferences): Preferences {
|
||||
currentData.toMutablePreferences().remove(key)
|
||||
return currentData
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +47,9 @@ dependencies {
|
|||
implementation(projects.domain.qrScanning)
|
||||
implementation(projects.domain.qrScanning.models)
|
||||
|
||||
/** Data */
|
||||
implementation(projects.data.card)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
|
|
@ -57,4 +60,5 @@ dependencies {
|
|||
/** Other dependencies */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.tangem.card.core)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.data.card.sdk.CardSdkProvider
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter
|
||||
import com.tangem.feature.qrscanning.presentation.QrScanningState
|
||||
|
|
@ -20,6 +21,7 @@ import javax.inject.Inject
|
|||
internal class QrScanningViewModel @Inject constructor(
|
||||
private val stateHolder: QrScanningStateController,
|
||||
private val clickIntents: QrScanningClickIntentsImplementor,
|
||||
private val cardSdkProvider: CardSdkProvider,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -31,6 +33,12 @@ internal class QrScanningViewModel @Inject constructor(
|
|||
val uiState: StateFlow<QrScanningState> = stateHolder.uiState
|
||||
val launchGalleryEvent: SharedFlow<GalleryRequest> = clickIntents.launchGallery
|
||||
|
||||
init {
|
||||
// samsung for some reason disables reader mode, and then it works unstable
|
||||
// to prevent this disable ir manually before scan QR
|
||||
cardSdkProvider.sdk.forceDisableReaderMode()
|
||||
}
|
||||
|
||||
fun setRouter(router: QrScanningInnerRouter) {
|
||||
clickIntents.initialize(
|
||||
router = router,
|
||||
|
|
@ -49,4 +57,10 @@ internal class QrScanningViewModel @Inject constructor(
|
|||
fun onDismissBottomSheetState() {
|
||||
stateHolder.update(DismissBottomSheetTransformer())
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
// don't forget enable reader mode after scan complete
|
||||
cardSdkProvider.sdk.forceEnableReaderMode()
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ internal class ErrorsDataConverter(
|
|||
2270 -> ExpressDataError.ExchangeNotEnoughBalanceError(code = error.code)
|
||||
2280 -> ExpressDataError.ExchangeInvalidAddressError(code = error.code)
|
||||
2290 -> tryParseExchangeInvalidFromDecimalsError(error = error)
|
||||
2320 -> tryParseProviderDifferentAmountError(error = error)
|
||||
else -> ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
|
@ -79,4 +80,20 @@ internal class ErrorsDataConverter(
|
|||
expressFromDecimals = expressFromDecimals,
|
||||
)
|
||||
}
|
||||
|
||||
private fun tryParseProviderDifferentAmountError(error: ExpressError): ExpressDataError {
|
||||
val decimals = error.value?.decimals ?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
|
||||
val fromAmount = error.value?.fromAmount?.toBigDecimalOrNull()
|
||||
?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
val fromAmountProvider = error.value?.fromAmountProvider?.toBigDecimalOrNull()
|
||||
?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
|
||||
return ExpressDataError.ProviderDifferentAmountError(
|
||||
code = error.code,
|
||||
decimals = decimals,
|
||||
fromAmount = fromAmount.movePointLeft(decimals),
|
||||
fromProviderAmount = fromAmountProvider.movePointLeft(decimals),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,13 @@ sealed class ExpressDataError {
|
|||
val expressFromDecimals: Int,
|
||||
) : ExpressDataError()
|
||||
|
||||
data class ProviderDifferentAmountError(
|
||||
override val code: Int,
|
||||
val fromAmount: BigDecimal,
|
||||
val fromProviderAmount: BigDecimal,
|
||||
val decimals: Int,
|
||||
) : ExpressDataError()
|
||||
|
||||
data class UnknownErrorWithCode(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class InvalidSignatureError(override val code: Int = 990) : ExpressDataError()
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@ import com.tangem.core.ui.components.notifications.NotificationConfig
|
|||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.format.bigdecimal.anyDecimals
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.uncapped
|
||||
import com.tangem.core.ui.format.bigdecimal.*
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
@ -781,6 +778,19 @@ internal class StateBuilder(
|
|||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
is ExpressDataError.ProviderDifferentAmountError -> SwapWarning.GeneralError(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(id = R.string.common_error),
|
||||
subtitle = resourceReference(
|
||||
R.string.express_error_provider_amount_roundup,
|
||||
formatArgs = wrappedList(
|
||||
expressDataError.code,
|
||||
expressDataError.fromProviderAmount.format { simple(decimals = expressDataError.decimals) },
|
||||
),
|
||||
),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
else -> SwapWarning.GeneralWarning(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = providerErrorTitle,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ internal fun getExpressErrorMessage(expressDataError: ExpressDataError): TextRef
|
|||
id = R.string.express_error_swap_pair_unavailable,
|
||||
formatArgs = wrappedList(expressDataError.code),
|
||||
)
|
||||
is ExpressDataError.ProviderDifferentAmountError -> resourceReference(
|
||||
R.string.express_error_provider_amount_roundup,
|
||||
formatArgs = wrappedList(
|
||||
expressDataError.code,
|
||||
expressDataError.fromProviderAmount.format { simple(decimals = expressDataError.decimals) },
|
||||
),
|
||||
)
|
||||
else -> resourceReference(R.string.express_error_code, wrappedList(expressDataError.code.toString()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
|||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
|
||||
|
|
@ -25,6 +26,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
|
|||
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
|
||||
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
|
||||
private val hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
) {
|
||||
|
||||
private var readyForRateAppNotification = false
|
||||
|
|
@ -36,22 +38,32 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
|
|||
flow = getPrimaryCurrencyStatusUpdatesUseCase(userWallet.walletId),
|
||||
flow2 = isReadyToShowRateAppUseCase().conflate(),
|
||||
flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(),
|
||||
) { primaryCurrencyStatus, isReadyToShowRating, isNeedToBackup ->
|
||||
flow4 = getWalletsUseCase().conflate(),
|
||||
) { maybePrimaryCurrencyStatus, isReadyToShowRating, isNeedToBackup, userWallets ->
|
||||
readyForRateAppNotification = true
|
||||
buildList {
|
||||
addCriticalNotifications(cardTypesResolver)
|
||||
|
||||
addInformationalNotifications(cardTypesResolver, clickIntents)
|
||||
|
||||
addWarningNotifications(
|
||||
userWallet,
|
||||
cardTypesResolver,
|
||||
primaryCurrencyStatus,
|
||||
isNeedToBackup,
|
||||
clickIntents,
|
||||
addCriticalNotifications(
|
||||
cardTypesResolver = cardTypesResolver,
|
||||
)
|
||||
|
||||
addRateTheAppNotification(isReadyToShowRating, clickIntents)
|
||||
addInformationalNotifications(
|
||||
userWallets = userWallets,
|
||||
cardTypesResolver = cardTypesResolver,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
|
||||
addWarningNotifications(
|
||||
userWallet = userWallet,
|
||||
cardTypesResolver = cardTypesResolver,
|
||||
maybePrimaryCurrencyStatus = maybePrimaryCurrencyStatus,
|
||||
isNeedToBackup = isNeedToBackup,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
|
||||
addRateTheAppNotification(
|
||||
isReadyToShowRating = isReadyToShowRating,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
}.toImmutableList()
|
||||
}
|
||||
}
|
||||
|
|
@ -76,14 +88,19 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
|
|||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addInformationalNotifications(
|
||||
userWallets: List<UserWallet>,
|
||||
cardTypesResolver: CardTypesResolver,
|
||||
clickIntents: WalletClickIntents,
|
||||
) {
|
||||
val userHasWalletOrWallet2 = userWallets.any {
|
||||
val typesResolver = it.scanResponse.cardTypesResolver
|
||||
typesResolver.isTangemWallet() || typesResolver.isWallet2()
|
||||
}
|
||||
addIf(
|
||||
element = WalletNotification.NoteMigration(
|
||||
onClick = { clickIntents.onNoteMigrationButtonClick(NOTE_MIGRATION_URL) },
|
||||
),
|
||||
condition = cardTypesResolver.isTangemNote(),
|
||||
condition = cardTypesResolver.isTangemNote() && !userHasWalletOrWallet2,
|
||||
)
|
||||
|
||||
addIf(
|
||||
|
|
|
|||
|
|
@ -88,9 +88,9 @@ markdownComposeView = "0.5.4"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-871"
|
||||
tangemBlockchainSdk = "develop-875"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-403"
|
||||
tangemCardSdk = "develop-410"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
tangemVico = "2.0.0-alpha.25-tangem17"
|
||||
#tangemVico = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ fun createPrivateProviderType(name: String): ProviderType? {
|
|||
"tangemRosetta" -> ProviderType.Cardano.Rosetta
|
||||
"fireAcademy" -> ProviderType.Chia.FireAcademy
|
||||
"tangemChia" -> ProviderType.Chia.Tangem
|
||||
"tangemChia3" -> ProviderType.Chia.TangemNew
|
||||
"infura" -> ProviderType.EthereumLike.Infura
|
||||
"getblock" -> ProviderType.GetBlock
|
||||
"arkhiaHedera" -> ProviderType.Hedera.Arkhia
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue