Updated on 2026-08-14
This commit is contained in:
parent
3580350769
commit
8aba7b07d1
49 changed files with 172 additions and 134 deletions
|
|
@ -23,10 +23,10 @@ internal class ScanCardExceptionConverter : TwoWayConverter<TangemError, ScanCar
|
|||
}
|
||||
|
||||
private fun convertTangemSdkError(value: TangemSdkError): ScanCardException = when (value) {
|
||||
is TangemSdkError.UserCancelled -> ScanCardException.UserCancelled
|
||||
is TangemSdkError.UserCancelled -> ScanCardException.UserCancelled()
|
||||
is TangemSdkError.WrongAccessCode,
|
||||
is TangemSdkError.WrongPasscode,
|
||||
-> ScanCardException.WrongAccessCode
|
||||
-> ScanCardException.WrongAccessCode()
|
||||
is TangemSdkError.WrongCardNumber -> ScanCardException.WrongCardId(value.cardId)
|
||||
else -> ScanCardException.UnknownException(value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.tangem.blockchainsdk.utils.fromNetworkId
|
|||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.wallets.derivations.DerivationStyleProvider
|
||||
import com.tangem.domain.card.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.card.common.TapWorkarounds.hasOldStyleDerivation
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
|
|
@ -54,7 +54,7 @@ internal class DerivationsFinder(
|
|||
}
|
||||
|
||||
// pay attention to this
|
||||
if (!card.useOldStyleDerivation) {
|
||||
if (!card.hasOldStyleDerivation) {
|
||||
blockchains.removeUnnecessaryBlockchains()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ fun assertEitherRight(actual: Either<Throwable, Unit>) {
|
|||
fun <B> assertEitherLeft(actual: Either<Throwable, B>, expected: Throwable) {
|
||||
actual
|
||||
.onRight { error("Actual is Either.Right: $it") }
|
||||
.onLeft {
|
||||
Truth.assertThat(it::class.java).isEqualTo(expected::class.java)
|
||||
Truth.assertThat(it).hasMessageThat().isEqualTo(expected.message)
|
||||
.onLeft { throwable ->
|
||||
Truth.assertThat(throwable::class.java).isEqualTo(expected::class.java)
|
||||
Truth.assertThat(throwable).hasMessageThat().isEqualTo(expected.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ fun AmountBlockV2(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
@Composable
|
||||
private fun AmountBlockV2(
|
||||
title: TextReference,
|
||||
|
|
@ -97,7 +97,10 @@ private fun AmountBlockV2(
|
|||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.conditional(onClick != null) {
|
||||
clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick!!)
|
||||
clickable(
|
||||
enabled = !isClickDisabled && !isEditingDisabled,
|
||||
onClick = requireNotNull(onClick),
|
||||
)
|
||||
}
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
|
|
@ -121,7 +124,9 @@ private fun AmountBlockV2(
|
|||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.padding(top = 8.dp).weight(1f),
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.weight(1f),
|
||||
) {
|
||||
ResizableText(
|
||||
text = firstAmount,
|
||||
|
|
|
|||
|
|
@ -222,10 +222,8 @@ private fun AmountItem(
|
|||
isExpanded = isExpandSelector,
|
||||
onDismiss = { isExpandSelector = false },
|
||||
onItemClick = { approveType ->
|
||||
onChangeApproveType.let {
|
||||
isExpandSelector = false
|
||||
onChangeApproveType.invoke(approveType)
|
||||
}
|
||||
isExpandSelector = false
|
||||
onChangeApproveType.invoke(approveType)
|
||||
},
|
||||
items = approveItems,
|
||||
selectedType = approveType,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ data class EvmTransactionScanRequest(
|
|||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
data class EvmTransactionBulkScanRequest(
|
||||
@Json(name = "chain") val chain: String,
|
||||
@Json(name = "options") val options: List<String>,
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ internal class DevApiConfigsManager(
|
|||
.onEach { savedEnvironments ->
|
||||
val apiConfigs = configs.value
|
||||
|
||||
configs.value = apiConfigs.mapValues {
|
||||
val (config, currentEnvironment) = it
|
||||
configs.value = apiConfigs.mapValues { entry ->
|
||||
val (config, currentEnvironment) = entry
|
||||
|
||||
savedEnvironments[config.id.name] ?: currentEnvironment
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ internal class MockApiConfigsManager(
|
|||
super.addListener(listener)
|
||||
|
||||
configs
|
||||
.map { it.entries.firstOrNull { it.key.id == listener.id } }
|
||||
.map { map -> map.entries.firstOrNull { it.key.id == listener.id } }
|
||||
.filterNotNull()
|
||||
.onEach { (apiConfig, currentEnvironment) ->
|
||||
listener.onChange(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.squareup.moshi.JsonClass
|
|||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
data class ConstructTransactionRequestBody(
|
||||
@Json(name = "gasArgs")
|
||||
val gasArgs: GasArgs? = null,
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ class AssetLoader @Inject constructor(
|
|||
if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null"))
|
||||
parsedConfig
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to load config [$fileName] from assets")
|
||||
onFailure = { throwable ->
|
||||
Timber.e(throwable, "Failed to load config [$fileName] from assets")
|
||||
null
|
||||
},
|
||||
)
|
||||
|
|
@ -79,8 +79,8 @@ class AssetLoader @Inject constructor(
|
|||
if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null"))
|
||||
parsedConfig.orEmpty()
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to load config [$fileName] from assets")
|
||||
onFailure = { throwable ->
|
||||
Timber.e(throwable, "Failed to load config [$fileName] from assets")
|
||||
emptyMap()
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ internal class DefaultExpressServiceLoader @Inject constructor(
|
|||
|
||||
@Suppress("SuspendFunWithFlowReturnType")
|
||||
private suspend fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow {
|
||||
val initializationStatus = initializationStatuses.value.get(key = userWalletId)
|
||||
val initializationStatus = initializationStatuses.value[userWalletId]
|
||||
if (initializationStatus != null) return initializationStatus
|
||||
|
||||
val cached = expressAssetsStore.getSyncOrNull(userWalletId)
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ internal class DefaultAvailableAppCurrenciesStore(
|
|||
|
||||
override suspend fun store(response: CurrenciesResponse) {
|
||||
val currencies = response.currencies
|
||||
.map {
|
||||
it.copy(
|
||||
iconSmallUrl = response.imageHost?.plus(IMAGE_SMALL)?.format(it.id),
|
||||
iconMediumUrl = response.imageHost?.plus(IMAGE_MEDIUM)?.format(it.id),
|
||||
.map { currency ->
|
||||
currency.copy(
|
||||
iconSmallUrl = response.imageHost?.plus(IMAGE_SMALL)?.format(currency.id),
|
||||
iconMediumUrl = response.imageHost?.plus(IMAGE_MEDIUM)?.format(currency.id),
|
||||
)
|
||||
}
|
||||
.associateBy(CurrenciesResponse.Currency::code)
|
||||
|
|
|
|||
|
|
@ -32,16 +32,16 @@ object NFTSdkAssetConverter : TwoWayConverter<Pair<Network, SdkNFTAsset>, NFTAss
|
|||
label = rarity.label,
|
||||
)
|
||||
},
|
||||
media = asset.media?.let {
|
||||
media = asset.media?.let { media ->
|
||||
NFTAsset.Media(
|
||||
animationUrl = it.animationUrl,
|
||||
imageUrl = it.imageUrl,
|
||||
animationUrl = media.animationUrl,
|
||||
imageUrl = media.imageUrl,
|
||||
)
|
||||
},
|
||||
traits = asset.traits.map {
|
||||
traits = asset.traits.map { trait ->
|
||||
NFTAsset.Trait(
|
||||
name = it.name,
|
||||
value = it.value,
|
||||
name = trait.name,
|
||||
value = trait.value,
|
||||
)
|
||||
},
|
||||
source = StatusSource.CACHE,
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@ class NFTSdkCollectionConverter(
|
|||
.filter {
|
||||
it.id !is NFTAsset.Identifier.Unknown
|
||||
}
|
||||
.let {
|
||||
if (it.isEmpty()) {
|
||||
.let { items ->
|
||||
if (items.isEmpty()) {
|
||||
NFTCollection.Assets.Empty
|
||||
} else {
|
||||
NFTCollection.Assets.Value(
|
||||
items = it,
|
||||
items = items,
|
||||
source = StatusSource.CACHE,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ internal object PreferencesDataStore {
|
|||
|
||||
private fun createCorruptionHandler(): ReplaceFileCorruptionHandler<Preferences> {
|
||||
return ReplaceFileCorruptionHandler(
|
||||
produceNewData = {
|
||||
Timber.w(it)
|
||||
produceNewData = { corruptionException ->
|
||||
Timber.w(corruptionException)
|
||||
emptyPreferences()
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ inline fun <reified T> AppPreferencesStore.getObject(key: Preferences.Key<String
|
|||
val adapter = moshi.adapter(T::class.java)
|
||||
emitAll(
|
||||
data.map { preferences ->
|
||||
preferences[key]?.let {
|
||||
preferences[key]?.let { value ->
|
||||
try {
|
||||
adapter.fromJson(it)
|
||||
adapter.fromJson(value)
|
||||
} catch (e: JsonDataException) {
|
||||
null
|
||||
}
|
||||
|
|
@ -59,9 +59,9 @@ suspend inline fun <reified T> AppPreferencesStore.getObjectSyncOrNull(key: Pref
|
|||
val adapter = moshi.adapter(T::class.java) // TODO: Support parameterized types
|
||||
data.firstOrNull()
|
||||
?.get(key)
|
||||
?.let {
|
||||
?.let { value ->
|
||||
try {
|
||||
adapter.fromJson(it)
|
||||
adapter.fromJson(value)
|
||||
} catch (e: JsonDataException) {
|
||||
null
|
||||
}
|
||||
|
|
@ -76,9 +76,9 @@ suspend inline fun <reified T> AppPreferencesStore.getObjectSyncOrDefault(
|
|||
val adapter = moshi.adapter(T::class.java)
|
||||
data.firstOrNull()
|
||||
?.get(key)
|
||||
?.let {
|
||||
?.let { value ->
|
||||
try {
|
||||
adapter.fromJson(it)
|
||||
adapter.fromJson(value)
|
||||
} catch (e: JsonDataException) {
|
||||
default
|
||||
}
|
||||
|
|
@ -159,7 +159,7 @@ inline fun <reified V> AppPreferencesStore.getObjectMap(key: Preferences.Key<Str
|
|||
val adapter = moshi.adapter<Map<String, V>>(type)
|
||||
|
||||
emitAll(
|
||||
data.map { it[key]?.let(adapter::fromJson) ?: emptyMap() },
|
||||
data.map { it[key]?.let(adapter::fromJson).orEmpty() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ inline fun <reified T> AppPreferencesStore.getObjectSet(key: Preferences.Key<Str
|
|||
val adapter = moshi.adapter<Set<T>>(Types.newParameterizedType(Set::class.java, T::class.java))
|
||||
emitAll(
|
||||
data.map {
|
||||
it[key]?.let(adapter::fromJson) ?: emptySet()
|
||||
it[key]?.let(adapter::fromJson).orEmpty()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ internal class DefaultSwapBestRateAnimationStore(
|
|||
* If true, reset flag to false
|
||||
*/
|
||||
override suspend fun getSyncOrNull(): Boolean {
|
||||
val shouldShowBestRateAnimation = dataStore.getSyncOrNull() ?: true
|
||||
val shouldShowBestRateAnimation = dataStore.getSyncOrNull() != false
|
||||
if (shouldShowBestRateAnimation) {
|
||||
dataStore.store(false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ internal class DefaultExpressAssetsStore(
|
|||
runtimeStore.store(userWalletId.stringValue, item)
|
||||
}
|
||||
launch {
|
||||
persistenceStore.updateData {
|
||||
it.toMutableMap().apply {
|
||||
persistenceStore.updateData { assetsByWalletId ->
|
||||
assetsByWalletId.toMutableMap().apply {
|
||||
put(userWalletId.stringValue, item)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ internal object PendingActionConverter : Converter<BalanceDTO.PendingAction, Pen
|
|||
passthrough = value.passthrough,
|
||||
args = with(value.args) {
|
||||
PendingAction.PendingActionArgs(
|
||||
amount = this?.amount?.let {
|
||||
amount = this?.amount?.let { amount ->
|
||||
PendingAction.PendingActionArgs.Amount(
|
||||
required = it.required,
|
||||
minimum = it.minimum,
|
||||
maximum = it.maximum,
|
||||
required = amount.required,
|
||||
minimum = amount.minimum,
|
||||
maximum = amount.maximum,
|
||||
)
|
||||
},
|
||||
duration = this?.duration?.let { duration ->
|
||||
|
|
@ -28,10 +28,10 @@ internal object PendingActionConverter : Converter<BalanceDTO.PendingAction, Pen
|
|||
},
|
||||
validatorAddress = this?.validatorAddress?.required,
|
||||
validatorAddresses = this?.validatorAddresses?.required,
|
||||
tronResource = this?.tronResource?.let {
|
||||
tronResource = this?.tronResource?.let { tronResource ->
|
||||
PendingAction.PendingActionArgs.TronResource(
|
||||
required = it.required,
|
||||
options = it.options,
|
||||
required = tronResource.required,
|
||||
options = tronResource.options,
|
||||
)
|
||||
},
|
||||
signatureVerification = this?.signatureVerification?.required,
|
||||
|
|
|
|||
|
|
@ -28,14 +28,14 @@ internal class DefaultWalletManagersStore(
|
|||
): WalletManager? {
|
||||
val walletManagers = getSyncOrNull(userWalletId)
|
||||
|
||||
return walletManagers?.singleOrNull {
|
||||
it.wallet.blockchain == blockchain &&
|
||||
it.wallet.publicKey.derivationPath?.rawPath == derivationPath
|
||||
return walletManagers?.singleOrNull { walletManager ->
|
||||
walletManager.wallet.blockchain == blockchain &&
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath == derivationPath
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAllSync(userWalletId: UserWalletId): List<WalletManager> {
|
||||
return getSyncOrNull(userWalletId) ?: emptyList()
|
||||
return getSyncOrNull(userWalletId).orEmpty()
|
||||
}
|
||||
|
||||
override suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager) {
|
||||
|
|
|
|||
|
|
@ -84,12 +84,12 @@ fun ResizableText(
|
|||
reduceFactor: Double = 0.9,
|
||||
) {
|
||||
var fontSize by remember { mutableStateOf(style.fontSize) }
|
||||
var readyToDraw by remember { mutableStateOf(value = false) }
|
||||
var isReadyToDraw by remember { mutableStateOf(value = false) }
|
||||
|
||||
Text(
|
||||
modifier = modifier
|
||||
.drawWithContent {
|
||||
if (readyToDraw) drawContent()
|
||||
if (isReadyToDraw) drawContent()
|
||||
}
|
||||
.wrapContentHeight(),
|
||||
text = text,
|
||||
|
|
@ -106,7 +106,7 @@ fun ResizableText(
|
|||
|
||||
if (minFontSize != TextUnit.Unspecified && reducedFontSize <= minFontSize) {
|
||||
fontSize = minFontSize
|
||||
readyToDraw = true
|
||||
isReadyToDraw = true
|
||||
} else {
|
||||
fontSize = reducedFontSize
|
||||
}
|
||||
|
|
@ -115,7 +115,7 @@ fun ResizableText(
|
|||
if (result.hasVisualOverflow) {
|
||||
reduceFontSize()
|
||||
} else {
|
||||
readyToDraw = true
|
||||
isReadyToDraw = true
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ private fun TangemTextField(
|
|||
null
|
||||
},
|
||||
)
|
||||
iconRes?.let { iconRes ->
|
||||
iconRes?.let { resId ->
|
||||
IconButton(
|
||||
modifier = Modifier.size(32.dp),
|
||||
onClick = onClear,
|
||||
|
|
@ -182,7 +182,7 @@ private fun TangemTextField(
|
|||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(24.dp),
|
||||
painter = painterResource(id = iconRes),
|
||||
painter = painterResource(id = resId),
|
||||
tint = colors.trailingIconColor(enabled = enabled, isError = isError).value,
|
||||
contentDescription = "Clear input",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ fun RoundedActionButton(
|
|||
ActionBaseButton(
|
||||
config = config,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius24),
|
||||
content = { modifier ->
|
||||
content = { contentModifier ->
|
||||
ActionButtonContent(
|
||||
config = config,
|
||||
text = { color -> Text(text = config.text, textColor = color) },
|
||||
modifier = modifier.padding(
|
||||
modifier = contentModifier.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing24,
|
||||
),
|
||||
|
|
@ -138,9 +138,13 @@ fun ActionBaseButton(
|
|||
onClick = config.onClick,
|
||||
onLongClick = {
|
||||
val toastReference = config.onLongClick?.invoke()
|
||||
toastReference?.let { toastReference ->
|
||||
if (toastReference != null) {
|
||||
Toast
|
||||
.makeText(context, toastReference.resolveReference(context.resources), Toast.LENGTH_SHORT)
|
||||
.makeText(
|
||||
context,
|
||||
toastReference.resolveReference(context.resources),
|
||||
Toast.LENGTH_SHORT,
|
||||
)
|
||||
.show()
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package com.tangem.core.ui.components.containers.pullToRefresh
|
|||
data class PullToRefreshConfig(val isRefreshing: Boolean, val onRefresh: (ShowRefreshState) -> Unit) {
|
||||
|
||||
@JvmInline
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
value class ShowRefreshState(
|
||||
val value: Boolean = true,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -228,7 +228,12 @@ internal data class DropdownMenuPositionProvider(
|
|||
|
||||
onPositionCalculated(
|
||||
anchorBounds,
|
||||
IntRect(x, y, x + popupContentSize.width, y + popupContentSize.height),
|
||||
IntRect(
|
||||
left = x,
|
||||
top = y,
|
||||
right = x + popupContentSize.width,
|
||||
bottom = y + popupContentSize.height,
|
||||
),
|
||||
)
|
||||
return IntOffset(x, y)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,12 @@ fun AmountTextField(
|
|||
val decimalFormat = rememberDecimalFormat()
|
||||
BoxWithConstraints(modifier = modifier) {
|
||||
val fontSize = if (isAutoResize) {
|
||||
resizeFont(visualTransformation, value, textStyle, reduceFactor)
|
||||
resizeFont(
|
||||
visualTransformation = visualTransformation,
|
||||
value = value,
|
||||
textStyle = textStyle,
|
||||
reduceFactor = reduceFactor,
|
||||
)
|
||||
} else {
|
||||
textStyle.fontSize
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,9 +108,9 @@ private fun CellDecoration(
|
|||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
repeat(length) {
|
||||
val char = if (it < value.length) {
|
||||
if (isPasswordVisual) PASSWORD_VISUAL_CHAR.toString() else value[it].toString()
|
||||
repeat(length) { i ->
|
||||
val char = if (i < value.length) {
|
||||
if (isPasswordVisual) PASSWORD_VISUAL_CHAR.toString() else value[i].toString()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,12 @@ class AmountVisualTransformation(
|
|||
val groupingSymbol = decimalFormat.decimalFormatSymbols.groupingSeparator
|
||||
return TransformedText(
|
||||
text = formattedText,
|
||||
offsetMapping = OffsetMappingImpl(text.text, formattedText, symbol, groupingSymbol),
|
||||
offsetMapping = OffsetMappingImpl(
|
||||
text = text.text,
|
||||
formattedText = formattedText,
|
||||
currencySymbol = symbol,
|
||||
groupingSymbol = groupingSymbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +98,7 @@ class AmountVisualTransformation(
|
|||
private val text: String,
|
||||
private val formattedText: AnnotatedString,
|
||||
private val currencySymbol: String?,
|
||||
private val gropingSymbol: Char,
|
||||
private val groupingSymbol: Char,
|
||||
) : OffsetMapping {
|
||||
override fun originalToTransformed(offset: Int): Int {
|
||||
var noneDigitCount = 0
|
||||
|
|
@ -101,7 +106,7 @@ class AmountVisualTransformation(
|
|||
val symbolOffset = currencySymbol?.let { formattedText.indexOf(it) } ?: -1
|
||||
while (i < offset + noneDigitCount) {
|
||||
val char = formattedText.getOrNull(i++)
|
||||
if (char == gropingSymbol) noneDigitCount++
|
||||
if (char == groupingSymbol) noneDigitCount++
|
||||
if (symbolOffset == 0 && char?.isWhitespace() == true) noneDigitCount++
|
||||
}
|
||||
var transformedOffset = if (symbolOffset == 0 && currencySymbol != null) {
|
||||
|
|
@ -118,7 +123,7 @@ class AmountVisualTransformation(
|
|||
}
|
||||
|
||||
override fun transformedToOriginal(offset: Int): Int {
|
||||
val noneDigitCount = formattedText.take(offset).count { it == gropingSymbol }
|
||||
val noneDigitCount = formattedText.take(offset).count { it == groupingSymbol }
|
||||
return (offset - noneDigitCount).coerceIn(0, text.length)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,11 +141,11 @@ fun TangemLinearProgressIndicator(
|
|||
drawLinearIndicatorBackground(backgroundColor, strokeWidth, strokeCap)
|
||||
if (firstLineHead - firstLineTail > 0) {
|
||||
drawLinearIndicator(
|
||||
firstLineHead,
|
||||
firstLineTail,
|
||||
color,
|
||||
strokeWidth,
|
||||
strokeCap,
|
||||
startFraction = firstLineHead,
|
||||
endFraction = firstLineTail,
|
||||
color = color,
|
||||
strokeWidth = strokeWidth,
|
||||
strokeCap = strokeCap,
|
||||
)
|
||||
}
|
||||
if ((secondLineHead - secondLineTail) > 0) {
|
||||
|
|
@ -204,7 +204,12 @@ private fun DrawScope.drawLinearIndicator(
|
|||
// if there isn't enough space to draw the stroke caps, fall back to StrokeCap.Butt
|
||||
if (strokeCap == StrokeCap.Butt || height > width) {
|
||||
// Progress line
|
||||
drawLine(color, Offset(barStart, yOffset), Offset(barEnd, yOffset), strokeWidth)
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(barStart, yOffset),
|
||||
end = Offset(barEnd, yOffset),
|
||||
strokeWidth = strokeWidth,
|
||||
)
|
||||
} else {
|
||||
// need to adjust barStart and barEnd for the stroke caps
|
||||
val strokeCapOffset = strokeWidth / 2
|
||||
|
|
@ -253,7 +258,7 @@ private const val SECOND_LINE_TAIL_DELAY = 1267
|
|||
private val FIRST_LINE_HEAD_EASING = CubicBezierEasing(a = 0.2f, b = 0f, c = 0.8f, d = 1f)
|
||||
private val FIRST_LINE_TAIL_EASING = CubicBezierEasing(a = 0.4f, b = 0f, c = 1f, d = 1f)
|
||||
private val SECOND_LINE_HEAD_EASING = CubicBezierEasing(a = 0f, b = 0f, c = 0.65f, d = 1f)
|
||||
private val SECOND_LINE_TAIL_EASING = CubicBezierEasing(a = 0.1f, 0f, 0.45f, d = 1f)
|
||||
private val SECOND_LINE_TAIL_EASING = CubicBezierEasing(a = 0.1f, b = 0f, c = 0.45f, d = 1f)
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
|
|
|
|||
|
|
@ -99,11 +99,11 @@ fun StoriesProgressBar(
|
|||
.clip(RoundedCornerShape(2.dp))
|
||||
.background(TangemColorPalette.White)
|
||||
.fillMaxHeight()
|
||||
.let {
|
||||
.let { modifier ->
|
||||
when (index) {
|
||||
currentStep -> it.fillMaxWidth(progress.value)
|
||||
in 0..currentStep -> it.fillMaxWidth(fraction = 1f)
|
||||
else -> it
|
||||
currentStep -> modifier.fillMaxWidth(progress.value)
|
||||
in 0..currentStep -> modifier.fillMaxWidth(fraction = 1f)
|
||||
else -> modifier
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,14 +37,14 @@ internal class DefaultAnalyticsRepository(
|
|||
}
|
||||
|
||||
override suspend fun setWalletBalanceState(userWalletId: UserWalletId, balanceState: WalletBalanceState) {
|
||||
appPreferencesStore.editData {
|
||||
val walletsBalanceState = it.getObjectMap<WalletBalanceState>(
|
||||
appPreferencesStore.editData { preferences ->
|
||||
val walletsBalanceState = preferences.getObjectMap<WalletBalanceState>(
|
||||
key = PreferencesKeys.WALLETS_BALANCES_STATES_KEY,
|
||||
)
|
||||
val updatedWalletsBalanceState = walletsBalanceState
|
||||
.plus(pair = userWalletId.stringValue to balanceState)
|
||||
|
||||
it.setObjectMap(PreferencesKeys.WALLETS_BALANCES_STATES_KEY, updatedWalletsBalanceState)
|
||||
preferences.setObjectMap(PreferencesKeys.WALLETS_BALANCES_STATES_KEY, updatedWalletsBalanceState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,16 +13,17 @@ internal class BlockAidEvmScanTransactionConverter(
|
|||
private val blockchain: Blockchain,
|
||||
) : Converter<List<SDKTransactionData.Uncompiled>, EvmTransactionBulkScanRequest> {
|
||||
|
||||
@Suppress("NullableToStringCall")
|
||||
override fun convert(value: List<SDKTransactionData.Uncompiled>): EvmTransactionBulkScanRequest {
|
||||
return EvmTransactionBulkScanRequest(
|
||||
chain = blockchain.getChainId().toString(),
|
||||
options = listOf(BlockAidScanOptions.GasEstimation.value),
|
||||
metadata = TransactionMetadata(domain = "https://tangem.com"),
|
||||
data = value.map {
|
||||
data = value.map { transactionData ->
|
||||
Data(
|
||||
from = it.sourceAddress,
|
||||
to = it.destinationAddress,
|
||||
data = (it.extras as? EthereumTransactionExtras)?.callData?.dataHex.orEmpty(),
|
||||
from = transactionData.sourceAddress,
|
||||
to = transactionData.destinationAddress,
|
||||
data = (transactionData.extras as? EthereumTransactionExtras)?.callData?.dataHex.orEmpty(),
|
||||
)
|
||||
},
|
||||
aggregated = false,
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ internal object BlockAidMapper {
|
|||
val tokenInfo = TokenInfo(
|
||||
chainId = exposure.asset.chainId,
|
||||
logoUrl = exposure.asset.logoUrl,
|
||||
symbol = exposure.asset.symbol ?: "",
|
||||
symbol = exposure.asset.symbol.orEmpty(),
|
||||
decimals = exposure.asset.decimals ?: 0,
|
||||
)
|
||||
return exposure.spenders.flatMap { (_, spender) ->
|
||||
|
|
@ -188,8 +188,8 @@ internal object BlockAidMapper {
|
|||
private fun mapApproveNftTransaction(exposure: Exposure): ApproveInfo.NonFungibleToken {
|
||||
return ApproveInfo.NonFungibleToken(
|
||||
name = exposure.asset.name.orEmpty(),
|
||||
logoUrl = exposure.spenders.values.firstOrNull()?.exposure?.firstOrNull()?.logoUrl
|
||||
?: exposure.asset.logoUrl,
|
||||
logoUrl = exposure.spenders.values.firstOrNull()
|
||||
?.exposure?.firstOrNull()?.logoUrl ?: exposure.asset.logoUrl,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -201,7 +201,7 @@ internal object BlockAidMapper {
|
|||
val token = TokenInfo(
|
||||
chainId = diff.asset.chainId,
|
||||
logoUrl = diff.asset.logoUrl,
|
||||
symbol = diff.asset.symbol ?: "",
|
||||
symbol = diff.asset.symbol.orEmpty(),
|
||||
decimals = diff.asset.decimals ?: 0,
|
||||
)
|
||||
diff.outTransfer.orEmpty().forEach { transfer ->
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ internal class DefaultCardRepository(
|
|||
override fun wasCardScanned(cardId: String): Flow<Boolean> {
|
||||
return appPreferencesStore.getObjectList<UsedCardInfo>(key = PreferencesKeys.USED_CARDS_INFO_KEY)
|
||||
.map { savedCards ->
|
||||
savedCards?.any { it.cardId == cardId } ?: false
|
||||
savedCards?.any { it.cardId == cardId } == true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,11 +63,11 @@ internal class DefaultCardRepository(
|
|||
}
|
||||
|
||||
override suspend fun isActivationStarted(cardId: String): Boolean {
|
||||
return getUsedCardSync(cardId)?.isActivationStarted ?: false
|
||||
return getUsedCardSync(cardId)?.isActivationStarted == true
|
||||
}
|
||||
|
||||
override suspend fun isActivationFinished(cardId: String): Boolean {
|
||||
return getUsedCardSync(cardId)?.isActivationFinished ?: false
|
||||
return getUsedCardSync(cardId)?.isActivationFinished == true
|
||||
}
|
||||
|
||||
override suspend fun isActivationInProgress(cardId: String): Boolean {
|
||||
|
|
|
|||
|
|
@ -67,10 +67,12 @@ class DefaultNotificationsRepository @Inject constructor(
|
|||
}
|
||||
|
||||
override suspend fun setNotificationsWasEnabledAutomatically(userWalletId: String) {
|
||||
appPreferencesStore.editData {
|
||||
it.setObjectMap(
|
||||
appPreferencesStore.editData { preferences ->
|
||||
preferences.setObjectMap(
|
||||
key = PreferencesKeys.NOTIFICATIONS_AUTOMATICALLY_ENABLED_STATES_KEY,
|
||||
value = it.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_AUTOMATICALLY_ENABLED_STATES_KEY)
|
||||
value = preferences.getObjectMap<Boolean>(
|
||||
PreferencesKeys.NOTIFICATIONS_AUTOMATICALLY_ENABLED_STATES_KEY,
|
||||
)
|
||||
.plus(userWalletId to true),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ internal class DefaultPushNotificationsRepository @Inject constructor(
|
|||
) : PushNotificationsRepository {
|
||||
|
||||
override suspend fun createApplicationId(pushToken: String?): ApplicationId = withContext(dispatchers.io) {
|
||||
tangemTechApi.createApplicationId(
|
||||
val appId = tangemTechApi.createApplicationId(
|
||||
NotificationApplicationCreateBody(
|
||||
platform = appInfoProvider.platform.lowercase(),
|
||||
device = appInfoProvider.device,
|
||||
|
|
@ -33,7 +33,9 @@ internal class DefaultPushNotificationsRepository @Inject constructor(
|
|||
version = appInfoProvider.appVersion,
|
||||
pushToken = pushToken,
|
||||
),
|
||||
).getOrThrow().appId.let(::ApplicationId)
|
||||
).getOrThrow().appId
|
||||
|
||||
ApplicationId(appId)
|
||||
}
|
||||
|
||||
override suspend fun saveApplicationId(appId: ApplicationId) {
|
||||
|
|
|
|||
|
|
@ -50,16 +50,16 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository {
|
|||
val result = QrResult(address = address)
|
||||
|
||||
extractParameters(withoutSchema)
|
||||
.forEach {
|
||||
when (it.key) {
|
||||
.forEach { entry ->
|
||||
when (entry.key) {
|
||||
Parameter.Amount -> {
|
||||
// According to BIP-0021, the value is specified in decimals. No conversion needed
|
||||
result.amount = it.value.parseBigDecimalOrNull()
|
||||
result.amount = entry.value.parseBigDecimalOrNull()
|
||||
}
|
||||
Parameter.Message,
|
||||
Parameter.Memo,
|
||||
-> {
|
||||
result.memo = URLDecoder.decode(it.value, "UTF-8")
|
||||
result.memo = URLDecoder.decode(entry.value, "UTF-8")
|
||||
}
|
||||
Parameter.Address -> {
|
||||
// If 'address' parameter is exists, then currency must be TOKEN.
|
||||
|
|
@ -70,7 +70,7 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository {
|
|||
// matches the contract address of the token.
|
||||
// Otherwise, the scanned string is likely malformed, and we stop the entire parsing routin
|
||||
if (tokenCurrency.contractAddress.equals(address, ignoreCase = true)) {
|
||||
result.address = it.value
|
||||
result.address = entry.value
|
||||
} else {
|
||||
return QrResult()
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository {
|
|||
-> {
|
||||
// Extra convert parses scientific notation to decimal
|
||||
// This is necessary to be able comparing BigDecimal values
|
||||
result.amount = it.value.parseBigDecimalOrNull()
|
||||
result.amount = entry.value.parseBigDecimalOrNull()
|
||||
?.toPlainString()?.toBigDecimalOrNull()
|
||||
?.divide(BigDecimal.TEN.pow(cryptoCurrency.decimals))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
|||
): List<TangemPayTxHistoryItem> {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getCacheKey(customerWalletAddress = config.customerWalletAddress, cursor = cursor),
|
||||
skipCache = config.refresh,
|
||||
skipCache = config.shouldRefresh,
|
||||
block = { fetch(customerWalletAddress = config.customerWalletAddress, cursor = cursor, pageSize = limit) },
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import com.tangem.common.extensions.toMapKey
|
|||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.card.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.card.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.card.common.TapWorkarounds.hasOldStyleDerivation
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.configs.CardConfig
|
||||
import com.tangem.domain.card.configs.Wallet2CardConfig
|
||||
|
|
@ -112,7 +112,7 @@ fun makePublicKey(
|
|||
private fun getDerivationParams(card: CardDTO): DerivationParams? {
|
||||
return if (!card.settings.isHDWalletAllowed) {
|
||||
null
|
||||
} else if (card.useOldStyleDerivation) {
|
||||
} else if (card.hasOldStyleDerivation) {
|
||||
DerivationParams.Default(DerivationStyle.LEGACY)
|
||||
} else {
|
||||
DerivationParams.Default(DerivationStyle.NEW)
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ internal class DefaultDerivationsRepositoryTest {
|
|||
scanResponse = MockScanResponseFactory.create(cardConfig = MultiWalletCardConfig, derivedKeys = emptyMap()),
|
||||
)
|
||||
coEvery { userWalletsStore.getSyncStrict(defaultUserWalletId) } returns userWallet
|
||||
coEvery { tangemSdkManager.derivePublicKeys(null, any(), any()) } throws ScanCardException.UserCancelled
|
||||
coEvery { tangemSdkManager.derivePublicKeys(null, any(), any()) } throws ScanCardException.UserCancelled()
|
||||
|
||||
runCatching {
|
||||
repository.derivePublicKeys(
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ package com.tangem.domain.card
|
|||
|
||||
sealed class ScanCardException : Exception() {
|
||||
|
||||
data object UserCancelled : ScanCardException() {
|
||||
class UserCancelled : ScanCardException() {
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun readResolve(): Any = UserCancelled
|
||||
private fun readResolve(): Any = UserCancelled()
|
||||
}
|
||||
|
||||
data object WrongAccessCode : ScanCardException() {
|
||||
class WrongAccessCode : ScanCardException() {
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun readResolve(): Any = WrongAccessCode
|
||||
private fun readResolve(): Any = WrongAccessCode()
|
||||
}
|
||||
|
||||
open class ChainException : ScanCardException()
|
||||
|
|
|
|||
|
|
@ -109,8 +109,8 @@ internal class TangemCardTypesResolver(
|
|||
override fun isAttestationFailed(): Boolean = card.attestation.status == Attestation.Status.Failed
|
||||
|
||||
override fun hasWalletSignedHashes(): Boolean {
|
||||
return card.wallets.any {
|
||||
val totalSignedHashes = it.totalSignedHashes ?: 0
|
||||
return card.wallets.any { wallet ->
|
||||
val totalSignedHashes = wallet.totalSignedHashes ?: 0
|
||||
totalSignedHashes > 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
|||
|
||||
sealed class IntroductionProcess(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent("Introduction Process", event, params) {
|
||||
|
||||
object ScreenOpened : IntroductionProcess("Introduction Process Screen Opened")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ class ParamCardCurrencyConverter : Converter<CardTypesResolver, WalletType?> {
|
|||
value.isTangemNote() -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
|
||||
value.isTangemTwins() -> AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin)
|
||||
value.getBlockchain() != Blockchain.Unknown -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain())
|
||||
value.getPrimaryToken() != null -> AnalyticsParam.CurrencyType.Token(value.getPrimaryToken()!!)
|
||||
value.getPrimaryToken() != null -> {
|
||||
AnalyticsParam.CurrencyType.Token(requireNotNull(value.getPrimaryToken()))
|
||||
}
|
||||
else -> null
|
||||
} ?: return null
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
|||
|
||||
sealed class Shop(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent("Shop", event, params) {
|
||||
|
||||
object ScreenOpened : Shop("Shop Screen Opened")
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ object TapWorkarounds {
|
|||
val CardDTO.canSkipBackup: Boolean
|
||||
get() = this.firmwareVersion < backupRequiredFirmwareVersion || demoConfig.isDemoCardId(cardId)
|
||||
|
||||
val CardDTO.useOldStyleDerivation: Boolean
|
||||
val CardDTO.hasOldStyleDerivation: Boolean
|
||||
get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95"
|
||||
|
||||
val CardDTO.isExcluded: Boolean
|
||||
get() {
|
||||
val excludedBatch = excludedBatches.contains(batchId)
|
||||
val excludedIssuerName = excludedIssuers.contains(issuer.name.uppercase(Locale.ROOT))
|
||||
return excludedBatch || excludedIssuerName
|
||||
val isBatchExcluded = excludedBatches.contains(batchId)
|
||||
val isIssuerNameExcluded = excludedIssuers.contains(issuer.name.uppercase(Locale.ROOT))
|
||||
return isBatchExcluded || isIssuerNameExcluded
|
||||
}
|
||||
|
||||
val CardDTO.isNotSupportedInThatRelease: Boolean
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ data object Wallet2CardConfig : CardConfig {
|
|||
* Logic to determine primary curve for blockchain in TangemWallet 2.0
|
||||
* Order is important here
|
||||
*/
|
||||
@Suppress("NullableToStringCall")
|
||||
override fun primaryCurve(blockchain: Blockchain): EllipticCurve? {
|
||||
// order is important, new curve is preferred for wallet 2
|
||||
// TODO Comment old logic without direct mapping until tests and release
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
package com.tangem.domain.tangempay.model
|
||||
|
||||
data class TangemPayTxHistoryListConfig(val customerWalletAddress: String, val refresh: Boolean)
|
||||
data class TangemPayTxHistoryListConfig(val customerWalletAddress: String, val shouldRefresh: Boolean)
|
||||
|
|
@ -57,7 +57,7 @@ internal class TangemPayTxHistoryListManager(
|
|||
BatchAction.Reload(
|
||||
requestParams = TangemPayTxHistoryListConfig(
|
||||
customerWalletAddress = customerWalletAddress,
|
||||
refresh = true,
|
||||
shouldRefresh = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -66,7 +66,7 @@ internal class TangemPayTxHistoryListManager(
|
|||
suspend fun loadMore(customerWalletAddress: String) {
|
||||
actionsFlow.emit(
|
||||
BatchAction.LoadMore(
|
||||
requestParams = TangemPayTxHistoryListConfig(customerWalletAddress, refresh = false),
|
||||
requestParams = TangemPayTxHistoryListConfig(customerWalletAddress, shouldRefresh = false),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue