Updated on 2026-08-14

This commit is contained in:
Tangem 2023-04-18 10:42:37 +03:00
commit 6a67347def
357 changed files with 4804 additions and 3288 deletions

View file

@ -40,18 +40,24 @@ dependencies {
implementation(deps.androidx.constraintLayout)
implementation(deps.androidx.activity.compose)
implementation(deps.androidx.browser)
implementation(deps.androidx.paging.runtime)
implementation(deps.lifecycle.runtime.ktx)
implementation(deps.lifecycle.common.java8)
implementation(deps.lifecycle.viewModel.ktx)
/** Compose libraries */
implementation(deps.compose.constraintLayout)
implementation(deps.compose.material)
implementation(deps.compose.animation)
implementation(deps.compose.coil)
implementation(deps.compose.constraintLayout)
implementation(deps.compose.foundation)
implementation(deps.compose.material)
implementation(deps.compose.navigation.hilt)
implementation(deps.compose.shimmer)
implementation(deps.compose.ui)
implementation(deps.compose.ui.tooling)
implementation(deps.compose.coil)
implementation(deps.compose.shimmer)
implementation(deps.compose.paging)
/** Firebase libraries */
implementation(platform(deps.firebase.bom))
@ -72,6 +78,7 @@ dependencies {
kapt(deps.hilt.kapt)
/** Other libraries */
implementation(deps.kotlin.immutable.collections)
implementation(deps.material)
implementation(deps.googlePlay.core)
implementation(deps.googlePlay.core.ktx)

View file

@ -35,6 +35,7 @@
android:supportsRtl="true"
android:allowBackup="false"
android:networkSecurityConfig="@xml/network_security_config"
android:hardwareAccelerated="true"
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:allowBackup">

View file

@ -462,9 +462,17 @@
"networkId": "optimistic-ethereum/test"
}
]
},
{
"id": "kava",
"symbol": "KAVA",
"name": "Kava EVM",
"networks":
[
{
"networkId": "kava/test"
}
]
}
],
"total" : 0,
"imageHost" : "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/"
]
}

View file

@ -59,8 +59,6 @@ class ForegroundActivityObserver : ActivityResultCaller {
}
}
fun ForegroundActivityObserver.withForegroundActivity(
block: (Activity) -> Unit
) {
fun ForegroundActivityObserver.withForegroundActivity(block: (Activity) -> Unit) {
foregroundActivity?.let { block(it) }
}

View file

@ -11,12 +11,7 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks
class NavBarInsetsFragmentLifecycleCallback : FragmentLifecycleCallbacks() {
override fun onFragmentViewCreated(
fm: FragmentManager,
f: Fragment,
v: View,
savedInstanceState: Bundle?,
) {
override fun onFragmentViewCreated(fm: FragmentManager, f: Fragment, v: View, savedInstanceState: Bundle?) {
if (v is ComposeView) return
ViewCompat.setOnApplyWindowInsetsListener(v) { view, windowInsets ->

View file

@ -49,6 +49,7 @@ import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
import com.tangem.tap.features.tokens.api.featuretoggles.TokensListFeatureToggles
import com.tangem.tap.persistence.PreferencesStorage
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphAction
@ -126,6 +127,9 @@ class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var networkConnectionManager: NetworkConnectionManager
@Inject
lateinit var tokensListFeatureToggles: TokensListFeatureToggles
override fun onCreate() {
super.onCreate()
@ -174,6 +178,7 @@ class TapApplication : Application(), ImageLoaderFactory {
action = DaggerGraphAction.SetApplicationDependencies(
assetReader = assetReader,
networkConnectionManager = networkConnectionManager,
tokensListFeatureToggles = tokensListFeatureToggles,
),
)

View file

@ -4,7 +4,7 @@ import timber.log.Timber
class CompositionCounter(
val id: String,
count: Int = 0
count: Int = 0,
) {
var count: Int = count
private set
@ -20,7 +20,7 @@ class CompositionCounter(
class CompositionLogger(
private val recomposeViewId: String,
private val tag: String = recomposeViewId,
private var turnOnForIds: List<String> = listOf(recomposeViewId)
private var turnOnForIds: List<String> = listOf(recomposeViewId),
) {
val count: Int
get() = compositionCounter.count

View file

@ -10,7 +10,7 @@ import java.math.RoundingMode
*/
class CurrencyConverter(
private val rateValue: BigDecimal,
private val decimals: Int
private val decimals: Int,
) {
private val roundingMode = RoundingMode.HALF_UP

View file

@ -13,7 +13,7 @@ class CustomTabsManager {
.setDefaultColorSchemeParams(
CustomTabColorSchemeParams.Builder()
.setNavigationBarColor(context.getColorCompat(R.color.toolbarColor))
.build()
.build(),
)
.build()
customTabsIntent.launchUrl(context, Uri.parse(url))

View file

@ -9,7 +9,7 @@ import timber.log.Timber
*/
class GlobalLayoutStateHandler<T : View>(
private val view: T,
attachImmediately: Boolean = true
attachImmediately: Boolean = true,
) : ViewTreeObserver.OnGlobalLayoutListener {
var onStateChanged: ((T) -> Unit)? = null

View file

@ -14,14 +14,14 @@ object TangemSdkErrorMapper {
is TangemSdkError.SerializeCommandError -> TangemSdkError.SerializeCommandError()
is TangemSdkError.DeserializeApduFailed -> TangemSdkError.DeserializeApduFailed()
is TangemSdkError.EncodingFailedTypeMismatch -> TangemSdkError.EncodingFailedTypeMismatch(
error.customMessage
error.customMessage,
)
is TangemSdkError.EncodingFailed -> TangemSdkError.EncodingFailed(error.customMessage)
is TangemSdkError.DecodingFailedMissingTag -> TangemSdkError.DecodingFailedMissingTag(
error.customMessage
error.customMessage,
)
is TangemSdkError.DecodingFailedTypeMismatch -> TangemSdkError.DecodingFailedTypeMismatch(
error.customMessage
error.customMessage,
)
is TangemSdkError.DecodingFailed -> TangemSdkError.DecodingFailed(error.customMessage)
is TangemSdkError.InvalidResponse -> TangemSdkError.InvalidResponse()
@ -94,8 +94,6 @@ object TangemSdkErrorMapper {
is TangemSdkError.BackupFailedEmptyWallets -> TangemSdkError.BackupFailedEmptyWallets()
is TangemSdkError.BackupFailedNotEmptyWallets -> TangemSdkError.BackupFailedNotEmptyWallets()
is TangemSdkError.NoActiveBackup -> TangemSdkError.NoActiveBackup()
is TangemSdkError.ResetBackupFailedHasBackupedWallets ->
TangemSdkError.ResetBackupFailedHasBackupedWallets()
is TangemSdkError.BackupServiceInvalidState -> TangemSdkError.BackupServiceInvalidState()
is TangemSdkError.NoBackupCardForIndex -> TangemSdkError.NoBackupCardForIndex()
is TangemSdkError.EmptyBackupCards -> TangemSdkError.EmptyBackupCards()
@ -121,6 +119,11 @@ object TangemSdkErrorMapper {
is TangemSdkError.InvalidEncryptionKey -> error
is TangemSdkError.KeyGenerationException -> error
is TangemSdkError.MnemonicException -> error
is TangemSdkError.WalletAlreadyCreated -> error
is TangemSdkError.ResetBackupFailedHasBackedUpWallets -> error
is TangemSdkError.KeysImportDisabled -> error
is TangemSdkError.Underlying -> error
is TangemSdkError.UserCodeRecoveryDisabled -> error
}
}
}

View file

@ -54,7 +54,7 @@ sealed class Token(
object ShowWalletAddress : Token(
category = "Token",
event = "Button - Show the Wallet Address"
event = "Button - Show the Wallet Address",
)
sealed class Receive(

View file

@ -9,6 +9,7 @@ import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.common.analytics.converters.TopUpEventConverter
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.extensions.copy
import com.tangem.tap.domain.model.TotalFiatBalance
import com.tangem.tap.domain.model.UserWallet
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.domain.model.WalletStoreModel
@ -17,7 +18,6 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
import com.tangem.tap.domain.walletStores.WalletStoresManager
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.persistence.ToppedUpWalletStorage
import com.tangem.tap.scope
import kotlinx.coroutines.launch
@ -60,8 +60,8 @@ class TopUpController(
hadMissedDerivations = blockchains.isNotEmpty()
}
fun totalBalanceStateChanged(state: ProgressState) {
if (state == ProgressState.Done) tryToSend()
fun totalBalanceStateChanged(totalFiatBalance: TotalFiatBalance) {
if (totalFiatBalance is TotalFiatBalance.Loaded) tryToSend()
}
fun loadDataSuccess() {
@ -116,10 +116,7 @@ class TopUpController(
}
}
fun send(
scanResponse: ScanResponse,
cardBalanceState: AnalyticsParam.CardBalanceState,
) {
fun send(scanResponse: ScanResponse, cardBalanceState: AnalyticsParam.CardBalanceState) {
UserWalletIdBuilder.scanResponse(scanResponse).build()?.let {
send(it, cardBalanceState, scanResponse.cardTypesResolver)
}

View file

@ -8,10 +8,7 @@ import androidx.compose.runtime.CompositionLocalProvider
* Used for disable ripple if button is enable = false
*/
@Composable
fun ToggledRippleTheme(
isEnabled: Boolean,
content: @Composable () -> Unit,
) {
fun ToggledRippleTheme(isEnabled: Boolean, content: @Composable () -> Unit) {
val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme()
CompositionLocalProvider(theme) { content() }
}

View file

@ -36,7 +36,7 @@ import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
import com.tangem.domain.redux.global.DomainGlobalState
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
import com.tangem.tap.features.tokens.addCustomToken.compose.SelectTokenNetworkDialog
import com.tangem.tap.features.addCustomToken.compose.SelectTokenNetworkDialog
import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber
@ -53,16 +53,22 @@ fun ComposeDialogManager() {
ShowTheDialog(dialogSate)
LaunchedEffect(key1 = Unit, block = {
domainStore.subscribe(subscriber) { state ->
state.skipRepeats { oldState, newState ->
oldState.globalState == newState.globalState
}.select { it.globalState }
}
})
DisposableEffect(key1 = Unit, effect = {
onDispose { domainStore.unsubscribe(subscriber) }
})
LaunchedEffect(
key1 = Unit,
block = {
domainStore.subscribe(subscriber) { state ->
state.skipRepeats { oldState, newState ->
oldState.globalState == newState.globalState
}.select { it.globalState }
}
},
)
DisposableEffect(
key1 = Unit,
effect = {
onDispose { domainStore.unsubscribe(subscriber) }
},
)
}
@Composable
@ -77,7 +83,7 @@ private fun ShowTheDialog(dialogState: MutableState<DomainDialog?>) {
is DomainDialog.DialogError -> ErrorDialog(
title = stringResource(id = R.string.common_error),
body = errorConverter.convert(dialog.error).message,
onDismissRequest
onDismissRequest,
)
is DomainDialog.SelectTokenDialog -> SelectTokenNetworkDialog(dialog, onDismissRequest)
else -> {}
@ -97,14 +103,14 @@ fun <T> SimpleDialog(
) {
Dialog(
properties = DialogProperties(false, false),
onDismissRequest = { }
onDismissRequest = { },
) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.medium
shape = MaterialTheme.shapes.medium,
) {
Column(
modifier = Modifier.padding(22.dp)
modifier = Modifier.padding(22.dp),
) {
DialogTitle(title = title)
LazyColumn {
@ -133,19 +139,15 @@ private fun DialogTitle(title: String) {
style = LocalTextStyle.provides(
TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
).value
fontSize = 20.sp,
),
).value,
)
SpacerH16()
}
@Composable
fun ErrorDialog(
title: String,
body: String,
onDismissRequest: () -> Unit,
) {
fun ErrorDialog(title: String, body: String, onDismissRequest: () -> Unit) {
AlertDialog(
title = { DialogTitle(title) },
text = { Text(body) },
@ -154,6 +156,6 @@ fun ErrorDialog(
Button(onClick = onDismissRequest) {
Text(text = stringResource(id = R.string.common_ok))
}
}
},
)
}

View file

@ -15,11 +15,7 @@ import androidx.compose.ui.unit.dp
[REDACTED_AUTHOR]
*/
@Composable
fun ErrorView(
text: String,
modifier: Modifier = Modifier,
style: TextStyle = LocalTextStyle.current,
) {
fun ErrorView(text: String, modifier: Modifier = Modifier, style: TextStyle = LocalTextStyle.current) {
Text(
text,
color = MaterialTheme.colors.error,

View file

@ -213,10 +213,7 @@ private fun OutlinedProgressTextField(
}
@Composable
private fun AnimatedErrorView(
errorConverter: ModuleMessageConverter,
error: ModuleError? = null,
) {
private fun AnimatedErrorView(errorConverter: ModuleMessageConverter, error: ModuleError? = null) {
AnimatedVisibility(
visible = error != null,
enter = fadeIn() + slideInVertically(),

View file

@ -110,10 +110,7 @@ fun PinCodeWidget(
}
@Composable
private fun PinElement(
config: PinViewConfig,
pinSymbol: String,
) {
private fun PinElement(config: PinViewConfig, pinSymbol: String) {
Box(Modifier.padding(config.pinBoxPadding)) {
Box(config.pinBoxModifier) {
Text(

View file

@ -128,11 +128,7 @@ internal data class TangemTextFieldColors(
}
@Composable
override fun labelColor(
enabled: Boolean,
error: Boolean,
interactionSource: InteractionSource,
): State<Color> {
override fun labelColor(enabled: Boolean, error: Boolean, interactionSource: InteractionSource): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
val targetValue = when {

View file

@ -12,16 +12,13 @@ import androidx.compose.ui.unit.sp
*/
@Composable
fun TitleSubtitle(
title: String,
subtitle: String
) {
fun TitleSubtitle(title: String, subtitle: String) {
Column {
Text(text = title)
Text(
text = subtitle,
fontSize = 12.sp,
color = Color.Gray
color = Color.Gray,
)
}
}

View file

@ -21,11 +21,7 @@ import com.tangem.wallet.R
[REDACTED_AUTHOR]
*/
@Composable
fun AddCustomTokenWarning(
warning: ModuleMessage,
converter: ModuleMessageConverter,
modifier: Modifier = Modifier,
) {
fun AddCustomTokenWarning(warning: ModuleMessage, converter: ModuleMessageConverter, modifier: Modifier = Modifier) {
Surface(
modifier = modifier,
shape = MaterialTheme.shapes.small,
@ -39,14 +35,14 @@ fun AddCustomTokenWarning(
text = stringResource(id = R.string.common_warning),
color = colorResource(id = R.color.white),
fontSize = 14.sp,
fontWeight = FontWeight.Bold
fontWeight = FontWeight.Bold,
)
SpacerH8()
Text(
text = converter.convert(warning).message,
color = colorResource(id = R.color.white),
fontSize = 13.sp,
lineHeight = 18.sp
lineHeight = 18.sp,
)
}
}

View file

@ -1,6 +1,10 @@
package com.tangem.tap.common.compose.extensions
import androidx.compose.animation.core.*
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
@ -41,8 +45,8 @@ fun animatable(
targetValue = values.second,
animationSpec = tween(
durationMillis = duration,
easing = easing
)
easing = easing,
),
)
}
}

View file

@ -6,7 +6,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState
open class Button(val enabled: Boolean)
open class IndeterminateProgressButton(
val state: ButtonState
val state: ButtonState,
) : Button(state != ButtonState.DISABLED) {
val progressState: ProgressState

View file

@ -22,7 +22,7 @@ fun Activity.sendEmail(
subject: String,
message: String,
file: File? = null,
onFail: ((Exception) -> Unit)? = null
onFail: ((Exception) -> Unit)? = null,
) {
fun createEmailShareIntent(recipient: String, subject: String, text: String, file: File? = null): Intent {
val builder = ShareCompat.IntentBuilder.from(this)

View file

@ -38,6 +38,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.Dash -> R.drawable.ic_dash_no_color
Blockchain.Kaspa -> R.drawable.ic_kaspa_no_color
Blockchain.TON, Blockchain.TONTestnet -> R.drawable.ic_ton_no_color
Blockchain.Kava, Blockchain.KavaTestnet -> R.drawable.ic_kava_no_color
else -> R.drawable.ic_tangem_logo
}
}

View file

@ -8,8 +8,7 @@ import android.net.Uri
import androidx.annotation.AnyRes
import androidx.core.content.ContextCompat
fun Context.readFile(fileName: String): String =
this.openFileInput(fileName).bufferedReader().readText()
fun Context.readFile(fileName: String): String = this.openFileInput(fileName).bufferedReader().readText()
fun Context.rewriteFile(content: String, fileName: String) {
this.openFileOutput(fileName, Context.MODE_PRIVATE).use {

View file

@ -1,4 +1,3 @@
package com.tangem.tap.common.extensions
fun <K, V> Map<out K?, V?>.filterNotNull(): Map<K, V> =
filter { it.key != null && it.value != null } as Map<K, V>
fun <K, V> Map<out K?, V?>.filterNotNull(): Map<K, V> = filter { it.key != null && it.value != null } as Map<K, V>

View file

@ -9,6 +9,7 @@ import com.tangem.feature.referral.ReferralFragment
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.features.addCustomToken.AddCustomTokenFragment
import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsFragment
import com.tangem.tap.features.details.ui.details.DetailsFragment
@ -25,12 +26,14 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFra
import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.shop.ui.ShopFragment
import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment
import com.tangem.tap.features.tokens.presentation.AddTokensFragment
import com.tangem.tap.features.tokens.impl.presentation.TokensListFragment
import com.tangem.tap.features.tokens.legacy.AddTokensFragment
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.features.walletSelector.ui.WalletSelectorBottomSheetFragment
import com.tangem.tap.features.welcome.ui.WelcomeFragment
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.wallet.R
import timber.log.Timber
@ -107,7 +110,12 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.AppSettings -> AppSettingsFragment()
AppScreen.ResetToFactory -> ResetCardFragment()
AppScreen.Disclaimer -> DisclaimerFragment()
AppScreen.AddTokens -> AddTokensFragment()
AppScreen.AddTokens -> {
val featureToggles = store.state.daggerGraphState.get(
getDependency = DaggerGraphState::tokensListFeatureToggles,
)
if (featureToggles.isRedesignedScreenEnabled) TokensListFragment() else AddTokensFragment()
}
AppScreen.AddCustomToken -> AddCustomTokenFragment()
AppScreen.WalletDetails -> WalletDetailsFragment()
AppScreen.WalletConnectSessions -> WalletConnectFragment()

View file

@ -5,10 +5,12 @@ import android.text.SpannedString
import android.text.style.RelativeSizeSpan
import androidx.core.text.buildSpannedString
import com.tangem.common.extensions.isZero
import timber.log.Timber
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.text.NumberFormat
import java.util.*
// TODO: move extensions to utils
@ -46,9 +48,18 @@ fun BigDecimal.toFormattedCurrencyString(
return "$formattedAmount $currency"
}
fun BigDecimal.toFiatRateString(
fiatCurrencyName: String,
): String {
fun BigDecimal.toFiatRateString(fiatCurrencyName: String, fiatCode: String): String {
try {
val formatter = NumberFormat.getCurrencyInstance()
Currency.getInstance(fiatCode)?.let { currency ->
formatter.currency = currency
formatter.maximumFractionDigits = 2
formatter.roundingMode = RoundingMode.HALF_UP
return formatter.format(this)
}
} catch (e: IllegalArgumentException) {
Timber.e(e, "can't parse currency")
}
val value = this
.setScale(2, RoundingMode.HALF_UP)
.formatWithSpaces()
@ -58,10 +69,15 @@ fun BigDecimal.toFiatRateString(
fun BigDecimal.toFiatString(
rateValue: BigDecimal,
fiatCurrencyName: String,
fiatCode: String,
formatWithSpaces: Boolean = false,
): String {
val fiatValue = rateValue.multiply(this)
return fiatValue.toFormattedFiatValue(fiatCurrencyName, formatWithSpaces)
return fiatValue.toFormattedFiatValue(
fiatCurrencyName = fiatCurrencyName,
fiatCode = fiatCode,
formatWithSpaces = formatWithSpaces,
)
}
fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
@ -71,8 +87,20 @@ fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
fun BigDecimal.toFormattedFiatValue(
fiatCurrencyName: String,
fiatCode: String,
formatWithSpaces: Boolean = false,
): String {
try {
val formatter = NumberFormat.getCurrencyInstance()
Currency.getInstance(fiatCode)?.let { currency ->
formatter.currency = currency
formatter.maximumFractionDigits = 2
formatter.roundingMode = RoundingMode.HALF_UP
return formatter.format(this)
}
} catch (e: IllegalArgumentException) {
Timber.e(e, "can't parse currency")
}
val fiatValue = this.setScale(2, RoundingMode.HALF_UP)
.let { if (formatWithSpaces) it.formatWithSpaces() else it }
return "$fiatValue$fiatCurrencyName"

View file

@ -25,12 +25,7 @@ fun String?.ellipsizeBeforeSpace(allowedSize: Int): String {
newString.substring(startIndex until newString.length)
}
fun String.colorSegment(
context: Context,
color: Int,
startIndex: Int = 0,
endIndex: Int = this.length,
): Spannable {
fun String.colorSegment(context: Context, color: Int, startIndex: Int = 0, endIndex: Int = this.length): Spannable {
return this.toSpannable()
.also { spannable ->
spannable.setSpan(

View file

@ -10,7 +10,7 @@ inline fun Transition.addListener(
crossinline onEnd: (animator: Transition) -> Unit = {},
crossinline onCancel: (animator: Transition) -> Unit = {},
crossinline onPause: (animator: Transition) -> Unit = {},
crossinline onRepeat: (animator: Transition) -> Unit = {}
crossinline onRepeat: (animator: Transition) -> Unit = {},
): Transition.TransitionListener {
val listener = object : Transition.TransitionListener {
override fun onTransitionStart(transition: Transition) = onStart(transition)

View file

@ -83,12 +83,11 @@ fun View.invisible(invisible: Boolean = true, invokeBeforeStateChanged: (() -> U
}
}
fun Context.dpToPixels(dp: Int): Int =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
this.resources.displayMetrics,
).toInt()
fun Context.dpToPixels(dp: Int): Int = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
this.resources.displayMetrics,
).toInt()
tailrec fun Context?.getActivity(): Activity? = this as? Activity
?: (this as? ContextWrapper)?.baseContext?.getActivity()

View file

@ -12,9 +12,8 @@ import com.tangem.tap.common.TestActions
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.proxy.redux.DaggerGraphState
@ -81,7 +80,7 @@ fun WalletManager.getTopUpUrl(): String? {
)
}
fun WalletManager?.getAddressData(): AddressData? {
fun WalletManager?.getAddressData(): WalletDataModel.AddressData? {
val wallet = this?.wallet ?: return null
val addressDataList = wallet.createAddressesData()
@ -100,11 +99,6 @@ fun <T> WalletManager.Companion.stub(): T {
} as T
}
fun Wallet.getTxHistory(currency: Currency): List<TransactionData> {
return (currency as? Currency.Token)?.let { this.getTokenTxHistory(it.token) }
?: getBlockchainTxHistory()
}
fun Wallet.getBlockchainTxHistory(): List<TransactionData> {
return historyTransactions.filter {
it.contractAddress.isNullOrEmpty()

View file

@ -10,10 +10,7 @@ import timber.log.Timber
private const val COIL_LOG_TAG = "COIL"
fun createCoilImageLoader(
context: Context,
logEnabled: Boolean = false,
): ImageLoader {
fun createCoilImageLoader(context: Context, logEnabled: Boolean = false): ImageLoader {
return ImageLoader.Builder(context)
.apply {
if (!logEnabled) return@apply
@ -27,7 +24,7 @@ fun createCoilImageLoader(
}
.apply {
level = HttpLoggingInterceptor.Level.BODY
}
},
)
.build()
}

View file

@ -54,7 +54,9 @@ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
cameraPermission == PackageManager.PERMISSION_GRANTED
} else true
} else {
true
}
}
private fun requestPermission() {

View file

@ -12,17 +12,12 @@ class SpaceItemDecoration(
private lateinit var space: Space
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
if (state.itemCount == 0) return
if (!::space.isInitialized) {
space = Space(
view.dpToPx(horizontalSpaceDp).toInt(),
view.dpToPx(verticalSpaceDp).toInt()
view.dpToPx(verticalSpaceDp).toInt(),
)
}

View file

@ -15,7 +15,7 @@ import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
import com.tangem.tap.features.shop.redux.ShopReducer
import com.tangem.tap.features.signin.redux.SignInReducer
import com.tangem.tap.features.sprinklr.redux.SprinklrReducer
import com.tangem.tap.features.tokens.redux.TokensReducer
import com.tangem.tap.features.tokens.legacy.redux.TokensReducer
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
import com.tangem.tap.features.welcome.redux.WelcomeReducer

View file

@ -35,8 +35,8 @@ import com.tangem.tap.features.signin.redux.SignInMiddleware
import com.tangem.tap.features.signin.redux.SignInState
import com.tangem.tap.features.sprinklr.redux.SprinklrMiddleware
import com.tangem.tap.features.sprinklr.redux.SprinklrState
import com.tangem.tap.features.tokens.redux.TokensMiddleware
import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
import com.tangem.tap.features.tokens.legacy.redux.TokensState
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware
import com.tangem.tap.features.walletSelector.redux.WalletSelectorMiddleware

View file

@ -2,8 +2,8 @@ package com.tangem.tap.common.redux
import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.TestAction
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.AddressData
/**
[REDACTED_AUTHOR]
@ -24,7 +24,7 @@ sealed class AppDialog : StateDialog {
data class AddressInfoDialog(
val currency: Currency,
val addressData: AddressData,
val addressData: WalletDataModel.AddressData,
) : AppDialog()
data class TestActionsDialog(

View file

@ -5,6 +5,7 @@ import android.content.Intent
import com.google.android.gms.wallet.PaymentData
import com.shopify.buy3.Storefront
import com.tangem.core.analytics.Analytics
import com.tangem.datasource.config.models.ShopifyShop
import com.tangem.tap.common.analytics.converters.ShopOrderToEventConverter
import com.tangem.tap.common.extensions.filterNotNull
import com.tangem.tap.common.shop.data.ProductType
@ -12,7 +13,6 @@ import com.tangem.tap.common.shop.data.TangemProduct
import com.tangem.tap.common.shop.data.TotalSum
import com.tangem.tap.common.shop.googlepay.GooglePayService
import com.tangem.tap.common.shop.shopify.ShopifyService
import com.tangem.datasource.config.models.ShopifyShop
import com.tangem.tap.common.shop.shopify.data.CheckoutItem
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@ -106,11 +106,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
// }
// }
suspend fun handleGooglePayResult(
resultCode: Int,
data: Intent?,
productType: ProductType,
): Result<Unit> {
suspend fun handleGooglePayResult(resultCode: Int, data: Intent?, productType: ProductType): Result<Unit> {
val result = googlePayService.handleResponseFromGooglePay(resultCode, data)
result.onSuccess {
val finalizePaymentResult = completeTokenizedPayment(it, productType)

View file

@ -4,7 +4,8 @@ import com.tangem.tap.common.shop.TangemShopService
enum class ProductType(val sku: String) {
WALLET_2_CARDS(TangemShopService.TANGEM_WALLET_2_CARDS_SKU),
WALLET_3_CARDS(TangemShopService.TANGEM_WALLET_3_CARDS_SKU);
WALLET_3_CARDS(TangemShopService.TANGEM_WALLET_3_CARDS_SKU),
;
companion object {
fun fromSku(sku: String): ProductType? {

View file

@ -3,5 +3,5 @@ package com.tangem.tap.common.shop.data
data class TangemProduct(
val type: ProductType,
val totalSum: TotalSum? = null,
val appliedDiscount: String? = null
val appliedDiscount: String? = null,
)

View file

@ -94,11 +94,7 @@ object GooglePayUtil {
}
}
private fun getTransactionInfo(
price: String,
countryCode: String,
currencyCode: String,
): JSONObject {
private fun getTransactionInfo(price: String, countryCode: String, currencyCode: String): JSONObject {
return JSONObject().apply {
put("totalPrice", price)
put("totalPriceStatus", "FINAL")

View file

@ -106,10 +106,7 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
}
}
suspend fun createCheckout(
checkoutItems: List<CheckoutItem>,
checkoutID: ID? = null,
): Result<Checkout> {
suspend fun createCheckout(checkoutItems: List<CheckoutItem>, checkoutID: ID? = null): Result<Checkout> {
val storefrontLineItems: MutableList<CheckoutLineItemInput> = checkoutItems
.map { CheckoutLineItemInput(it.quantity, it.id) }.toMutableList()
@ -197,10 +194,7 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
return runCheckoutMutation(query)
}
suspend fun completeWithTokenizedPayment(
payment: TokenizedPaymentInputV3,
checkoutID: ID,
): Result<Checkout> {
suspend fun completeWithTokenizedPayment(payment: TokenizedPaymentInputV3, checkoutID: ID): Result<Checkout> {
val query = mutation { mutationQuery: MutationQuery ->
mutationQuery
.checkoutCompleteWithTokenizedPaymentV3(
@ -247,25 +241,21 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
private suspend fun queryAsync(
query: QueryRootQuery,
retryHandler: RetryHandler<QueryRoot>,
): GraphCallResult<QueryRoot> =
withContext(Dispatchers.IO) {
suspendCoroutine { continuation ->
client.queryGraph(query).enqueue(retryHandler = retryHandler) { result ->
continuation.resume(result)
}
): GraphCallResult<QueryRoot> = withContext(Dispatchers.IO) {
suspendCoroutine { continuation ->
client.queryGraph(query).enqueue(retryHandler = retryHandler) { result ->
continuation.resume(result)
}
}
}
private suspend fun queryAsync(
query: QueryRootQuery,
): GraphCallResult<QueryRoot> =
withContext(Dispatchers.IO) {
suspendCoroutine { continuation ->
client.queryGraph(query).enqueue { result ->
continuation.resume(result)
}
private suspend fun queryAsync(query: QueryRootQuery): GraphCallResult<QueryRoot> = withContext(Dispatchers.IO) {
suspendCoroutine { continuation ->
client.queryGraph(query).enqueue { result ->
continuation.resume(result)
}
}
}
private suspend fun mutationQueryAsync(query: MutationQuery): GraphCallResult<Mutation> =
withContext(Dispatchers.IO) {

View file

@ -4,5 +4,5 @@ import com.shopify.graphql.support.ID
data class CheckoutItem(
val id: ID,
val quantity: Int
val quantity: Int,
)

View file

@ -18,14 +18,14 @@ import com.tangem.wallet.R
*/
class MaxAmountSnackbar(
parent: ViewGroup,
content: MaxAmountSnackbarView
content: MaxAmountSnackbarView,
) : BaseTransientBottomBar<MaxAmountSnackbar>(parent, content, content) {
companion object {
fun make(view: View, onClick: () -> Unit): MaxAmountSnackbar {
val parent = view.findSuitableParent() ?: throw IllegalArgumentException(
"No suitable parent found from the given view. Please provide a valid view."
"No suitable parent found from the given view. Please provide a valid view.",
)
val inflater = LayoutInflater.from(view.context)
val customView = inflater.inflate(R.layout.view_snackbar_max_amount, parent, false) as MaxAmountSnackbarView
@ -63,7 +63,7 @@ class MaxAmountSnackbar(
class MaxAmountSnackbarView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
defStyleAttr: Int = 0,
) : ConstraintLayout(context, attrs, defStyleAttr), ContentViewCallback {
init {

View file

@ -0,0 +1,36 @@
package com.tangem.tap.common.utils
import android.os.Handler
import android.os.Looper
import org.rekotlin.StoreSubscriber
private val mainLooper by lazy { Looper.getMainLooper() }
private val mainHandler by lazy { Handler(mainLooper) }
/**
* A subscriber interface for safely subscribing to state changes in a Store.
*
* @param State the type of the state in the Store
*/
interface SafeStoreSubscriber<State> : StoreSubscriber<State> {
/**
* A function that will be called when the state in the Store changes.
*
* @param state the new state in the Store
*/
override fun newState(state: State) {
if (Thread.currentThread() != mainLooper.thread) {
mainHandler.post { newStateOnMain(state) }
} else {
newStateOnMain(state)
}
}
/**
* A function that will be called on the main thread when the state in the Store changes.
*
* @param state the new state in the Store
*/
fun newStateOnMain(state: State)
}

View file

@ -11,15 +11,13 @@ import java.util.*
class PayIdManager {
@Suppress("MagicNumber")
suspend fun verifyPayId(
payId: String,
blockchain: Blockchain,
): Result<VerifyPayIdResponse> = withContext(Dispatchers.IO) {
val splitPayId = payId.split("\$")
val user = splitPayId[0]
val baseUrl = "https://${splitPayId[1]}/"
return@withContext PayIdVerifyService(baseUrl).verifyAddress(user, blockchain.getPayIdNetwork())
}
suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result<VerifyPayIdResponse> =
withContext(Dispatchers.IO) {
val splitPayId = payId.split("\$")
val user = splitPayId[0]
val baseUrl = "https://${splitPayId[1]}/"
return@withContext PayIdVerifyService(baseUrl).verifyAddress(user, blockchain.getPayIdNetwork())
}
private fun Blockchain.getPayIdNetwork(): String {
return when (this) {
@ -30,8 +28,10 @@ class PayIdManager {
}
companion object {
private val payIdRegExp = ("^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9]" +
"(?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$").toRegex()
private val payIdRegExp = (
"^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9]" +
"(?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$"
).toRegex()
val payIdSupported: EnumSet<Blockchain> = EnumSet.of(
Blockchain.XRP,

View file

@ -18,10 +18,10 @@ import com.tangem.common.core.Config
import com.tangem.common.core.TangemSdkError
import com.tangem.common.core.UserCodeRequestPolicy
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.common.map
import com.tangem.common.usersCode.UserCodeRepository
import com.tangem.core.analytics.Analytics
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.ScanResponse
import com.tangem.operations.CommandResponse
@ -82,9 +82,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
).also { sendScanResultsToAnalytics(it) }
}
suspend fun createProductWallet(
scanResponse: ScanResponse,
): CompletionResult<CreateProductWalletTaskResponse> {
suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult<CreateProductWalletTaskResponse> {
return runTaskAsync(
CreateProductWalletTask(scanResponse.cardTypesResolver),
scanResponse.card.cardId,
@ -92,9 +90,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
)
}
private fun sendScanResultsToAnalytics(
result: CompletionResult<ScanResponse>,
) {
private fun sendScanResultsToAnalytics(result: CompletionResult<ScanResponse>) {
if (result is CompletionResult.Failure) {
(result.error as? TangemSdkError)?.let { error ->
Analytics.send(Basic.ScanError(error))
@ -194,14 +190,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
cardId: String? = null,
initialMessage: Message? = null,
accessCode: String? = null,
): CompletionResult<T> =
withContext(Dispatchers.Main) {
suspendCancellableCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage, accessCode) { result ->
if (continuation.isActive) continuation.resume(result)
}
): CompletionResult<T> = withContext(Dispatchers.Main) {
suspendCancellableCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage, accessCode) { result ->
if (continuation.isActive) continuation.resume(result)
}
}
}
private suspend fun <T : CommandResponse> runTaskAsyncReturnOnMain(
runnable: CardSessionRunnable<T>,
@ -226,9 +221,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return context.getString(stringResId, *formatArgs)
}
fun setAccessCodeRequestPolicy(
useBiometricsForAccessCode: Boolean,
) {
fun setAccessCodeRequestPolicy(useBiometricsForAccessCode: Boolean) {
tangemSdk.config.userCodeRequestPolicy = if (useBiometricsForAccessCode) {
UserCodeRequestPolicy.AlwaysWithBiometrics(codeType = UserCodeType.AccessCode)
} else {
@ -247,6 +240,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
allowUntrustedCards = true,
filter = CardFilter(
allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(),
maxFirmwareVersion = FirmwareVersion(major = 4, minor = 52),
),
)
}

View file

@ -53,10 +53,7 @@ class TangemSigner(
}
}
override suspend fun sign(
hash: ByteArray,
publicKey: Wallet.PublicKey,
): CompletionResult<ByteArray> {
override suspend fun sign(hash: ByteArray, publicKey: Wallet.PublicKey): CompletionResult<ByteArray> {
val result = sign(
hashes = listOf(hash),
publicKey = publicKey,

View file

@ -19,7 +19,7 @@ data class WarningMessage(
val origin: Origin = Origin.Remote,
@StringRes val buttonTextId: Int? = null,
val titleFormatArg: String? = null,
val messageFormatArg: String? = null
val messageFormatArg: String? = null,
) {
val blockchainList: List<Blockchain>? by lazy {
blockchains?.map { Blockchain.fromId(it.uppercase()) }
@ -35,7 +35,7 @@ data class WarningMessage(
Warning,
@Json(name = "info")
Info
Info,
}
enum class Type {
@ -47,7 +47,7 @@ data class WarningMessage(
AppRating,
TestCard
TestCard,
}
enum class Location {
@ -55,7 +55,7 @@ data class WarningMessage(
MainScreen,
@Json(name = "send")
SendScreen
SendScreen,
}
enum class Origin {

View file

@ -107,7 +107,7 @@ class WarningMessagesManager {
messageResId = R.string.warning_signed_tx_previously,
origin = WarningMessage.Origin.Local,
buttonTextId = R.string.warning_button_learn_more,
titleFormatArg = "\u26A0"
titleFormatArg = "\u26A0",
)
fun appRatingWarning(): WarningMessage = WarningMessage(

View file

@ -8,7 +8,7 @@ import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isTestCard
@ -92,10 +92,7 @@ private fun getDerivationParams(derivationPath: String?, card: CardDTO): Derivat
}
}
fun WalletManagerFactory.makeWalletManagerForApp(
scanResponse: ScanResponse,
currency: Currency,
): WalletManager? {
fun WalletManagerFactory.makeWalletManagerForApp(scanResponse: ScanResponse, currency: Currency): WalletManager? {
return makeWalletManagerForApp(
scanResponse,
blockchain = currency.blockchain,
@ -112,9 +109,7 @@ fun WalletManagerFactory.makeWalletManagersForApp(
.mapNotNull { this.makeWalletManagerForApp(scanResponse, it) }
}
fun WalletManagerFactory.makePrimaryWalletManager(
scanResponse: ScanResponse,
): WalletManager? {
fun WalletManagerFactory.makePrimaryWalletManager(scanResponse: ScanResponse): WalletManager? {
val blockchain = if (scanResponse.card.isTestCard) {
scanResponse.cardTypesResolver.getBlockchain().getTestnetVersion() ?: return null
} else {

View file

@ -13,11 +13,12 @@ sealed interface TotalFiatBalance {
override val amount: BigDecimal? = null
}
data class Error(
override val amount: BigDecimal?,
) : TotalFiatBalance
object Failed : TotalFiatBalance {
override val amount: BigDecimal? = null
}
data class Loaded(
override val amount: BigDecimal,
val isWarning: Boolean,
) : TotalFiatBalance
}

View file

@ -1,10 +1,11 @@
package com.tangem.tap.domain.model
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.address.AddressType
import com.tangem.tap.domain.model.WalletDataModel.AddressData
import com.tangem.tap.domain.model.WalletDataModel.Status
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.redux.AddressData
import java.math.BigDecimal
/**
@ -21,7 +22,8 @@ import java.math.BigDecimal
data class WalletDataModel(
val currency: Currency,
val status: Status,
val walletAddresses: List<AddressData>,
// FIXME: Left only selected wallet address here and move list of wallet addresses to WalletStoreModel
val walletAddresses: WalletAddresses?,
val existentialDeposit: BigDecimal?,
val fiatRate: BigDecimal?,
val isCardSingleToken: Boolean,
@ -29,6 +31,18 @@ data class WalletDataModel(
val historyTransactions: List<TransactionData>?,
) {
data class WalletAddresses(
val selectedAddress: AddressData,
val list: List<AddressData>,
)
data class AddressData(
val address: String,
val type: AddressType,
val shareUrl: String,
val exploreUrl: String,
)
/**
* Represent current status of currency
* @property amount Currency amount

View file

@ -2,13 +2,14 @@ package com.tangem.tap.domain.model
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.domain.model.WalletStoreModel.WalletRent
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.Currency
import java.math.BigDecimal
// FIXME: Move list of wallet addresses from WalletDataModel to this class
/**
* Contains info about the blockchain and its currencies
*

View file

@ -1,22 +1,15 @@
package com.tangem.tap.domain.model.builders
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.ProductType
import com.tangem.domain.common.SaltPayWorkaround
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TwinCardNumber
import com.tangem.domain.common.TwinsHelper
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.operations.attestation.TangemApi
import com.tangem.tap.domain.model.UserWallet
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.domain.userWalletList.GetCardImageUseCase
class UserWalletBuilder(
private val scanResponse: ScanResponse,
private val onlineCardVerifier: OnlineCardVerifier = OnlineCardVerifier(),
private val getCardImageUseCase: GetCardImageUseCase = GetCardImageUseCase(),
) {
private var backupCardsIds: Set<String> = emptySet()
@ -50,7 +43,7 @@ class UserWalletBuilder(
UserWallet(
walletId = it,
name = userWalletName,
artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey),
artworkUrl = getCardImageUseCase.invoke(card.cardId, card.cardPublicKey),
cardsInWallet = backupCardsIds.plus(card.cardId),
scanResponse = this,
isMultiCurrency = cardTypesResolver.isMultiwalletAllowed(),
@ -58,37 +51,4 @@ class UserWalletBuilder(
}
}
}
private suspend fun loadArtworkUrl(cardId: String, cardPublicKey: ByteArray): String {
return when (val result = onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) {
is Result.Success -> {
val artworkId = result.data.artwork?.id
if (artworkId.isNullOrEmpty()) {
getFallbackArtworkUrl(cardId)
} else {
getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
}
}
is Result.Failure -> getFallbackArtworkUrl(cardId)
}
}
private fun getFallbackArtworkUrl(cardId: String): String {
return when {
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
SaltPayWorkaround.isSaltPayCardId(cardId) -> Artwork.SALT_PAY_URL
else -> when (TwinsHelper.getTwinCardNumber(cardId)) {
TwinCardNumber.First -> Artwork.TWIN_CARD_1
TwinCardNumber.Second -> Artwork.TWIN_CARD_2
else -> Artwork.DEFAULT_IMG_URL
}
}
}
private fun getUrlForArtwork(cardId: String, cardPublicKeyHex: String, artworkId: String): String {
return TangemApi.Companion.BaseUrl.VERIFY.url + TangemApi.ARTWORK +
"?artworkId=$artworkId&CID=$cardId&publicKey=$cardPublicKeyHex"
}
}

View file

@ -21,7 +21,9 @@ class UserWalletIdBuilder private constructor(
} else {
publicKey
}
} else null
} else {
null
}
return seed?.let {
UserWalletId(value = calculateUserWalletId(it))
@ -34,7 +36,9 @@ class UserWalletIdBuilder private constructor(
return if (keyHash != null) {
message.calculateHmacSha256(keyHash)
} else null
} else {
null
}
}
companion object {

View file

@ -4,8 +4,9 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.tap.common.extensions.getBlockchainTxHistory
import com.tangem.tap.common.extensions.getTokenTxHistory
@ -34,10 +35,7 @@ interface WalletStoreBuilder {
return BlockchainNetworkWalletStoreBuilderImpl(userWallet, blockchainNetwork)
}
operator fun invoke(
userWallet: UserWallet,
walletManager: WalletManager,
): WalletMangerWalletStoreBuilder {
operator fun invoke(userWallet: UserWallet, walletManager: WalletManager): WalletMangerWalletStoreBuilder {
return WalletMangerWalletStoreBuilderImpl(userWallet, walletManager)
}
}
@ -110,7 +108,7 @@ private fun BlockchainNetwork.getBlockchainWalletData(
return WalletDataModel(
currency = currency,
status = WalletDataModel.Loading,
walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(),
walletAddresses = walletManager?.wallet?.getWalletAddresses(),
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = false,
@ -134,7 +132,7 @@ private fun BlockchainNetwork.getTokensWalletsData(
WalletDataModel(
currency = currency,
status = WalletDataModel.Loading,
walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(),
walletAddresses = walletManager?.wallet?.getWalletAddresses(),
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = token == primaryToken,
@ -152,7 +150,7 @@ private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): Wal
derivationPath = wallet.publicKey.derivationPath?.rawPath,
),
status = WalletDataModel.Loading,
walletAddresses = wallet.createAddressesData(),
walletAddresses = wallet.getWalletAddresses(),
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = false,
@ -161,10 +159,7 @@ private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): Wal
)
}
private fun Token.toTokenWalletData(
walletManager: WalletManager,
primaryToken: Token?,
): WalletDataModel {
private fun Token.toTokenWalletData(walletManager: WalletManager, primaryToken: Token?): WalletDataModel {
val wallet = walletManager.wallet
return WalletDataModel(
currency = Currency.Token(
@ -173,7 +168,7 @@ private fun Token.toTokenWalletData(
derivationPath = wallet.publicKey.derivationPath?.rawPath,
),
status = WalletDataModel.Loading,
walletAddresses = wallet.createAddressesData(),
walletAddresses = wallet.getWalletAddresses(),
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = this == primaryToken,
@ -184,4 +179,15 @@ private fun Token.toTokenWalletData(
private fun getExistentialDeposit(walletManager: WalletManager?): BigDecimal? {
return (walletManager as? ExistentialDepositProvider)?.getExistentialDeposit()
}
private fun Wallet.getWalletAddresses(): WalletDataModel.WalletAddresses? {
return this.createAddressesData()
.takeIf { it.isNotEmpty() }
?.let { addresses ->
WalletDataModel.WalletAddresses(
list = addresses,
selectedAddress = addresses.first(),
)
}
}

View file

@ -99,10 +99,7 @@ object ScanCardProcessor {
}
}
private fun sendAnalytics(
analyticsEvent: AnalyticsEvent?,
scanResponse: ScanResponse,
) {
private fun sendAnalytics(analyticsEvent: AnalyticsEvent?, scanResponse: ScanResponse) {
analyticsEvent?.let {
// this workaround needed to send CardWasScannedEvent without adding a context
val interceptor = CardContextInterceptor(scanResponse)
@ -257,10 +254,7 @@ object ScanCardProcessor {
}
}
private suspend inline fun navigateTo(
screen: AppScreen,
onProgressStateChange: (showProgress: Boolean) -> Unit,
) {
private suspend inline fun navigateTo(screen: AppScreen, onProgressStateChange: (showProgress: Boolean) -> Unit) {
delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatchOnMain(NavigationAction.NavigateTo(screen))
onProgressStateChange(false)

View file

@ -28,7 +28,7 @@ class CreateWalletsTask(curves: List<EllipticCurve>? = null) : CardSessionRunnab
private fun createWallet(
curve: EllipticCurve,
session: CardSession,
callback: (result: CompletionResult<Card>) -> Unit
callback: (result: CompletionResult<Card>) -> Unit,
) {
CreateWalletTask(curve).run(session) { result ->
when (result) {

View file

@ -23,11 +23,15 @@ class SignHashTask(
when (response) {
is CompletionResult.Success -> {
callback(CompletionResult.Success(TangemSignHashResponse(
response.data.signature,
response.data.totalSignedHashes,
session.environment.card?.wallet(publicKey.seedKey)?.remainingSignatures
)))
callback(
CompletionResult.Success(
TangemSignHashResponse(
response.data.signature,
response.data.totalSignedHashes,
session.environment.card?.wallet(publicKey.seedKey)?.remainingSignatures,
),
),
)
}
is CompletionResult.Failure ->
callback(CompletionResult.Failure(response.error))

View file

@ -22,11 +22,15 @@ class SignHashesTask(
SignHashesCommand(hashes.toTypedArray(), publicKey.seedKey, publicKey.derivationPath).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
callback(CompletionResult.Success(TangemSignHashesResponse(
response.data.signatures,
response.data.totalSignedHashes,
session.environment.card?.wallet(publicKey.seedKey)?.remainingSignatures
)))
callback(
CompletionResult.Success(
TangemSignHashesResponse(
response.data.signatures,
response.data.totalSignedHashes,
session.environment.card?.wallet(publicKey.seedKey)?.remainingSignatures,
),
),
)
}
is CompletionResult.Failure ->
callback(CompletionResult.Failure(response.error))

View file

@ -10,7 +10,7 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.common.map
import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.CardTypesResolver
@ -267,10 +267,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
}
}
private fun getBlockchains(
cardId: String,
card: CardDTO,
): List<Blockchain> {
private fun getBlockchains(cardId: String, card: CardDTO): List<Blockchain> {
return when {
DemoHelper.isDemoCardId(cardId) -> DemoHelper.config.demoBlockchains
card.isTestCard -> listOf(Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet)

View file

@ -13,7 +13,7 @@ import com.tangem.operations.wallet.CreateWalletTask
[REDACTED_AUTHOR]
*/
class CreateWalletsResponse(
val createWalletResponses: List<CreateWalletResponse>
val createWalletResponses: List<CreateWalletResponse>,
) : CommandResponse
class CreateWalletsTask(
@ -35,7 +35,7 @@ class CreateWalletsTask(
private fun createWallet(
curve: EllipticCurve,
session: CardSession,
callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit
callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit,
) {
CreateWalletTask(curve).run(session) { result ->
when (result) {

View file

@ -8,9 +8,5 @@ import com.tangem.domain.common.CardDTO
[REDACTED_AUTHOR]
*/
interface ProductCommandProcessor<T> {
fun proceed(
card: CardDTO,
session: CardSession,
callback: (result: CompletionResult<T>) -> Unit,
)
fun proceed(card: CardDTO, session: CardSession, callback: (result: CompletionResult<T>) -> Unit)
}

View file

@ -14,10 +14,7 @@ class ResetToFactorySettingsTask : CardSessionRunnable<Card> {
deleteWallets(session, callback)
}
private fun deleteWallets(
session: CardSession,
callback: (result: CompletionResult<Card>) -> Unit,
) {
private fun deleteWallets(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
val wallet = session.environment.card?.wallets?.lastOrNull().guard {
resetBackup(session, callback)
return
@ -33,10 +30,7 @@ class ResetToFactorySettingsTask : CardSessionRunnable<Card> {
}
}
private fun resetBackup(
session: CardSession,
callback: (result: CompletionResult<Card>) -> Unit,
) {
private fun resetBackup(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
val backupStatus = session.environment.card?.backupStatus
if (backupStatus == null || backupStatus == Card.BackupStatus.NoBackup) {
callback(CompletionResult.Success(session.environment.card!!))

View file

@ -15,7 +15,7 @@ import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toByteArray
import com.tangem.common.extensions.toHexString
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.common.tlv.Tlv
import com.tangem.common.tlv.TlvDecoder
import com.tangem.crypto.CryptoUtils
@ -52,10 +52,7 @@ class ScanProductTask(
override val allowsRequestAccessCodeFromRepository: Boolean = false,
) : CardSessionRunnable<ScanResponse> {
override fun run(
session: CardSession,
callback: (result: CompletionResult<ScanResponse>) -> Unit,
) {
override fun run(session: CardSession, callback: (result: CompletionResult<ScanResponse>) -> Unit) {
val card = this.card ?: session.environment.card.guard {
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
return

View file

@ -5,10 +5,7 @@ import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.common.CardDTO
object CurrenciesRepository {
fun getBlockchains(
cardFirmware: CardDTO.FirmwareVersion,
isTestNet: Boolean = false,
): List<Blockchain> {
fun getBlockchains(cardFirmware: CardDTO.FirmwareVersion, isTestNet: Boolean = false): List<Blockchain> {
val blockchains = if (cardFirmware < FirmwareVersion.MultiWalletAvailable) {
Blockchain.secp256k1Blockchains(isTestNet)
} else {
@ -23,7 +20,7 @@ object CurrenciesRepository {
removeAll(
listOf(
// Any blockchain
)
),
)
}
}

View file

@ -10,20 +10,20 @@ data class CurrencyFromJson(
val id: String,
val name: String,
val symbol: String,
val networks: List<ContractFromJson>? = null
val networks: List<ContractFromJson>? = null,
)
@JsonClass(generateAdapter = true)
data class ContractFromJson(
val networkId: String,
val contractAddress: String?,
val decimalCount: Int?
val decimalCount: Int?,
)
@JsonClass(generateAdapter = true)
data class CurrenciesFromJson(
val imageHost: String?,
val coins: List<CurrencyFromJson>
val coins: List<CurrencyFromJson>,
)
fun List<ContractFromJson>.toContracts(): List<Contract> {
@ -35,7 +35,7 @@ data class Currency(
val name: String,
val symbol: String,
val iconUrl: String,
val contracts: List<Contract>
val contracts: List<Contract>,
) {
companion object {
@ -45,7 +45,7 @@ data class Currency(
name = currency.name,
symbol = currency.symbol,
iconUrl = getIconUrl(currency.id, null),
contracts = currency.networks?.toContracts() ?: emptyList()
contracts = currency.networks?.toContracts() ?: emptyList(),
)
}
@ -55,7 +55,7 @@ data class Currency(
name = currency.name,
symbol = currency.symbol,
iconUrl = getIconUrl(currency.id, imageHost),
contracts = currency.networks.mapNotNull { Contract.fromNetwork(it, imageHost) }
contracts = currency.networks.mapNotNull { Contract.fromNetwork(it, imageHost) },
)
}
}
@ -77,7 +77,7 @@ data class Contract(
blockchain = blockchain,
address = contract.contractAddress,
decimalCount = contract.decimalCount,
iconUrl = getIconUrl(contract.networkId, null)
iconUrl = getIconUrl(contract.networkId, null),
)
}
@ -88,7 +88,7 @@ data class Contract(
blockchain = blockchain,
address = contract.contractAddress,
decimalCount = contract.decimalCount?.toInt(),
iconUrl = getIconUrl(contract.networkId, imageHost)
iconUrl = getIconUrl(contract.networkId, imageHost),
)
}
}

View file

@ -20,10 +20,10 @@ class LoadAvailableCoinsService(
MoshiConverter.networkMoshi.adapter(CurrenciesFromJson::class.java)
suspend fun getSupportedTokens(
isTestNet: Boolean = false,
isTestNet: Boolean,
supportedBlockchains: List<Blockchain>,
page: Int,
searchInput: String? = null,
searchInput: String?,
): Result<LoadedCoins> {
if (isTestNet) {
return Result.Success(
@ -33,15 +33,17 @@ class LoadAvailableCoinsService(
),
)
}
val offset = page * LOAD_PER_PAGE
val result = loadCoins(supportedBlockchains, offset, searchInput)
return when (result) {
val offset = page * LOAD_PER_PAGE
return when (val result = loadCoins(supportedBlockchains, offset, searchInput)) {
is Result.Success -> {
val data = result.data
Result.Success(
LoadedCoins(
currencies = data.coins.map { Currency.fromCoinResponse(it, data.imageHost) },
currencies = data.coins.map {
Currency.fromCoinResponse(currency = it, imageHost = data.imageHost)
},
moreAvailable = data.total > offset + LOAD_PER_PAGE,
),
)
@ -55,22 +57,24 @@ class LoadAvailableCoinsService(
private suspend fun loadCoins(
supportedBlockchains: List<Blockchain>,
offset: Int,
searchInput: String? = null,
searchInput: String?,
): Result<CoinsResponse> {
return withContext(dispatchers.io) {
runCatching {
tangemTechApi.getCoins(
networkIds = supportedBlockchains.toSet().joinToString(",", transform = Blockchain::toNetworkId),
networkIds = supportedBlockchains.joinToString(
separator = ",",
transform = Blockchain::toNetworkId,
),
active = true,
searchText = searchInput,
offset = offset,
limit = LOAD_PER_PAGE,
)
}
.onSuccess { return@withContext Result.Success(it) }
.onFailure { return@withContext Result.Failure(it) }
error("Unreachable code because runCatching must return result")
}.fold(
onSuccess = { Result.Success(it) },
onFailure = { Result.Failure(it) },
)
}
}
@ -83,15 +87,15 @@ class LoadAvailableCoinsService(
private fun List<Currency>.filter(searchInput: String?): List<Currency> {
if (searchInput.isNullOrBlank()) return this
return filter {
it.symbol.contains(searchInput, ignoreCase = true) ||
it.name.contains(searchInput, ignoreCase = true)
return filter { currency ->
currency.symbol.contains(searchInput, ignoreCase = true) ||
currency.name.contains(searchInput, ignoreCase = true)
}
}
companion object {
private companion object {
const val LOAD_PER_PAGE = 100
private const val FILE_NAME_TESTNET_COINS = "testnet_tokens"
const val FILE_NAME_TESTNET_COINS = "testnet_tokens"
}
}

View file

@ -15,6 +15,6 @@ object CurrencyConverter : Converter<Currency, UserTokensResponse.Token> {
name = value.currencyName,
symbol = value.currencySymbol,
decimals = value.decimals,
contractAddress = if (value is Currency.Token) value.token.contractAddress else null
contractAddress = if (value is Currency.Token) value.token.contractAddress else null,
)
}

View file

@ -18,7 +18,7 @@ data class ObsoleteTokenDao(
contractAddress = contractAddress,
decimalCount = decimalCount,
blockchainDao = BlockchainDao.fromBlockchain(blockchain),
customIconUrl = customIconUrl
customIconUrl = customIconUrl,
)
}
}

View file

@ -14,7 +14,7 @@ data class TokenDao(
@Json(name = "blockchain")
val blockchainDao: BlockchainDao,
val customIconUrl: String? = null,
val type: String? = null
val type: String? = null,
) {
fun toToken(): Token {
return Token(

View file

@ -22,13 +22,15 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
val walletsData = walletStores
.asSequence()
.flatMap { it.walletsData }
val calculateAmount = { walletsData.calculateTotalFiatAmount() }
when (walletsData.findStatus()) {
when (val status = walletsData.findStatus()) {
TotalFiatBalanceStatus.Loading -> TotalFiatBalance.Loading
TotalFiatBalanceStatus.Error -> TotalFiatBalance.Error(calculateAmount())
TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(
amount = calculateAmount() ?: BigDecimal.ZERO,
TotalFiatBalanceStatus.Failed -> TotalFiatBalance.Failed
TotalFiatBalanceStatus.Warning,
TotalFiatBalanceStatus.Loaded,
-> TotalFiatBalance.Loaded(
amount = walletsData.calculateTotalFiatAmount(),
isWarning = status == TotalFiatBalanceStatus.Warning,
)
}
}
@ -50,29 +52,30 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
is WalletDataModel.SameCurrencyTransactionInProgress,
is WalletDataModel.TransactionInProgress,
is WalletDataModel.NoAccount,
-> if (walletData.isCustom && walletData.fiatRate == null) {
TotalFiatBalanceStatus.Error
-> if (walletData.isWarningCase()) {
TotalFiatBalanceStatus.Warning
} else {
TotalFiatBalanceStatus.Loaded
}
is WalletDataModel.Unreachable,
is WalletDataModel.MissedDerivation,
-> TotalFiatBalanceStatus.Error
-> TotalFiatBalanceStatus.Failed
is WalletDataModel.Loading -> TotalFiatBalanceStatus.Loading
}
}
}
private fun Sequence<WalletDataModel>.calculateTotalFiatAmount(): BigDecimal? {
private fun Sequence<WalletDataModel>.calculateTotalFiatAmount(): BigDecimal {
return this
.filterNot { it.isCustom && it.fiatRate == null }
.filterNot { it.isWarningCase() }
.map { walletData ->
walletData.fiatRate
?.takeUnless { walletData.status.isErrorStatus }
?.let { walletData.status.amount.toFiatValue(it) }
?: BigDecimal.ZERO
}
.reduce { acc, value ->
value?.let { acc?.plus(it) }
acc + value
}
}
@ -80,20 +83,22 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
prevStatus: TotalFiatBalanceStatus,
newStatus: TotalFiatBalanceStatus,
): TotalFiatBalanceStatus {
return when (prevStatus) {
TotalFiatBalanceStatus.Loading -> prevStatus
TotalFiatBalanceStatus.Loaded,
TotalFiatBalanceStatus.Error,
-> when (newStatus) {
TotalFiatBalanceStatus.Loading,
TotalFiatBalanceStatus.Error,
-> newStatus
TotalFiatBalanceStatus.Loaded -> prevStatus
}
}
return TotalFiatBalanceStatus[minOf(prevStatus.ordinal, newStatus.ordinal)]
}
private fun WalletDataModel.isWarningCase(): Boolean = isCustom && fiatRate == null
private enum class TotalFiatBalanceStatus {
Loading, Error, Loaded,
Loading,
Failed,
Warning,
Loaded,
;
companion object {
private val allValues = values()
operator fun get(index: Int) = allValues[index]
}
}
}

View file

@ -13,10 +13,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: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
val card = session.environment.card
val publicKey = card?.wallets?.firstOrNull()?.publicKey
if (publicKey != null) {

View file

@ -16,10 +16,7 @@ class FinalizeTwinTask(
override val allowsRequestAccessCodeFromRepository: Boolean = false
override fun run(
session: CardSession,
callback: (result: CompletionResult<ScanResponse>) -> Unit,
) {
override fun run(session: CardSession, callback: (result: CompletionResult<ScanResponse>) -> Unit) {
WriteProtectedIssuerDataTask(twinPublicKey, issuerKeys).run(session) { result ->
when (result) {
is CompletionResult.Success ->

View file

@ -19,10 +19,7 @@ class WriteProtectedIssuerDataTask(
private val issuerKeys: KeyPair,
) : CardSessionRunnable<SuccessResponse> {
override fun run(
session: CardSession,
callback: (result: CompletionResult<SuccessResponse>) -> Unit,
) {
override fun run(session: CardSession, callback: (result: CompletionResult<SuccessResponse>) -> Unit) {
SignHashCommand(
twinPublicKey.calculateSha256(),
session.environment.card!!.wallets.first().publicKey,

View file

@ -0,0 +1,59 @@
package com.tangem.tap.domain.userWalletList
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.domain.common.SaltPayWorkaround
import com.tangem.domain.common.TwinCardNumber
import com.tangem.domain.common.TwinsHelper
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.operations.attestation.TangemApi
import com.tangem.tap.features.wallet.redux.Artwork
/**
* Use case for getting card image url
*
* @property verifier REST API
*
[REDACTED_AUTHOR]
*/
class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardVerifier()) {
/**
* Get card image url
*
* @param cardId card id
* @param cardPublicKey card public key
*/
suspend operator fun invoke(cardId: String, cardPublicKey: ByteArray): String {
return when (val result = verifier.getCardInfo(cardId, cardPublicKey)) {
is Result.Success -> {
val artworkId = result.data.artwork?.id
if (artworkId.isNullOrEmpty()) {
getFallbackArtworkUrl(cardId)
} else {
getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
}
}
is Result.Failure -> getFallbackArtworkUrl(cardId)
}
}
private fun getFallbackArtworkUrl(cardId: String): String {
return when {
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
SaltPayWorkaround.isSaltPayCardId(cardId) -> Artwork.SALT_PAY_URL
else -> when (TwinsHelper.getTwinCardNumber(cardId)) {
TwinCardNumber.First -> Artwork.TWIN_CARD_1
TwinCardNumber.Second -> Artwork.TWIN_CARD_2
else -> Artwork.DEFAULT_IMG_URL
}
}
}
private fun getUrlForArtwork(cardId: String, cardPublicKeyHex: String, artworkId: String): String {
return TangemApi.Companion.BaseUrl.VERIFY.url + TangemApi.ARTWORK +
"?artworkId=$artworkId&CID=$cardId&publicKey=$cardPublicKeyHex"
}
}

View file

@ -55,7 +55,10 @@ interface UserWalletsListManager {
* @return [CompletionResult.Success] with updated [UserWallet]
* or [CompletionResult.Failure] with [NoSuchElementException] if [UserWallet] with [userWalletId] not found
* */
suspend fun update(userWalletId: UserWalletId, update: (UserWallet) -> UserWallet): CompletionResult<UserWallet>
suspend fun update(
userWalletId: UserWalletId,
update: suspend (UserWallet) -> UserWallet,
): CompletionResult<UserWallet>
/**
* Delete saved [UserWallet]s with provided [UserWalletId]s

View file

@ -120,7 +120,7 @@ internal class BiometricUserWalletsListManager(
override suspend fun update(
userWalletId: UserWalletId,
update: (UserWallet) -> UserWallet,
update: suspend (UserWallet) -> UserWallet,
): CompletionResult<UserWallet> {
return get(userWalletId)
.map { storedUserWallet ->

View file

@ -58,7 +58,7 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
override suspend fun update(
userWalletId: UserWalletId,
update: (UserWallet) -> UserWallet,
update: suspend (UserWallet) -> UserWallet,
): CompletionResult<UserWallet> = catching {
val wallet = state.value.userWallet
?.takeIf { it.walletId == userWalletId }

View file

@ -25,6 +25,6 @@ internal class DefaultSelectedUserWalletRepository(
}
private enum class StorageKey {
SelectedWalletId
SelectedWalletId,
}
}

View file

@ -73,9 +73,7 @@ internal class DefaultUserWalletsPublicInformationRepository(
}
@JvmName("saveWithPublicInformation")
private suspend fun save(
publicInformation: List<UserWalletPublicInformation>,
): CompletionResult<Unit> = catching {
private suspend fun save(publicInformation: List<UserWalletPublicInformation>): CompletionResult<Unit> = catching {
withContext(Dispatchers.IO) {
publicInformation
.let(publicInformationAdapter::toJson)
@ -85,6 +83,6 @@ internal class DefaultUserWalletsPublicInformationRepository(
}
private enum class StorageKey {
UserWalletPublicInformation
UserWalletPublicInformation,
}
}

View file

@ -148,10 +148,7 @@ internal class DefaultUserWalletsSensitiveInformationRepository(
}
}
private suspend fun ByteArray.getIvAndDecrypt(
userWalletId: String,
encryptionKey: ByteArray,
): ByteArray? {
private suspend fun ByteArray.getIvAndDecrypt(userWalletId: String, encryptionKey: ByteArray): ByteArray? {
return withContext(Dispatchers.Default) {
val iv = secureStorage.get(StorageKey.SensitiveInformationIv(userWalletId).name)
?: error("IV not found")

View file

@ -50,7 +50,9 @@ internal fun UserWallet.updateWith(sensitiveInformation: UserWalletSensitiveInfo
internal fun List<UserWallet>.updateWith(
walletIdToSensitiveInformation: Map<UserWalletId, UserWalletSensitiveInformation>,
): List<UserWallet> {
return if (walletIdToSensitiveInformation.isEmpty()) this else {
return if (walletIdToSensitiveInformation.isEmpty()) {
this
} else {
this.map { wallet ->
walletIdToSensitiveInformation[wallet.walletId]
?.let(wallet::updateWith)

View file

@ -9,19 +9,12 @@ import com.tangem.common.extensions.ByteArrayKey
internal class ByteArrayKeyAdapter {
@ToJson
fun toJson(
writer: JsonWriter,
src: ByteArrayKey,
byteArrayAdapter: JsonAdapter<ByteArray>,
) {
fun toJson(writer: JsonWriter, src: ByteArrayKey, byteArrayAdapter: JsonAdapter<ByteArray>) {
byteArrayAdapter.toJson(writer, src.bytes)
}
@FromJson
fun fromJson(
reader: JsonReader,
byteArrayAdapter: JsonAdapter<ByteArray>,
): ByteArrayKey? {
fun fromJson(reader: JsonReader, byteArrayAdapter: JsonAdapter<ByteArray>): ByteArrayKey? {
return byteArrayAdapter.fromJson(reader)?.let {
ByteArrayKey(bytes = it)
}

View file

@ -9,11 +9,7 @@ import com.tangem.domain.common.CardDTO
internal class CardBackupStatusAdapter {
@ToJson
fun toJson(
writer: JsonWriter,
src: CardDTO.BackupStatus?,
mapAdapter: JsonAdapter<Map<String, String>>,
) {
fun toJson(writer: JsonWriter, src: CardDTO.BackupStatus?, mapAdapter: JsonAdapter<Map<String, String>>) {
val jsonMap = mutableMapOf<String, String>()
when (src) {
@ -37,10 +33,7 @@ internal class CardBackupStatusAdapter {
}
@FromJson
fun fromJson(
reader: JsonReader,
mapAdapter: JsonAdapter<Map<String, String>>,
): CardDTO.BackupStatus? {
fun fromJson(reader: JsonReader, mapAdapter: JsonAdapter<Map<String, String>>): CardDTO.BackupStatus? {
val map = mapAdapter.fromJson(reader) ?: return null
return when (map["status"]) {

View file

@ -7,8 +7,8 @@ import com.squareup.moshi.JsonWriter
import com.squareup.moshi.ToJson
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.common.hdWallet.ExtendedPublicKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.operations.derivation.ExtendedPublicKeysMap
internal class ExtendedPublicKeysMapAdapter {

View file

@ -7,8 +7,8 @@ import com.squareup.moshi.JsonWriter
import com.squareup.moshi.ToJson
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.common.hdWallet.ExtendedPublicKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
internal class WalletDerivedKeysMapAdapter {
@ToJson

View file

@ -16,10 +16,7 @@ interface WalletCurrenciesManager {
*
* @return [CompletionResult] of operation
* */
suspend fun update(
userWallet: UserWallet,
currency: Currency,
): CompletionResult<Unit>
suspend fun update(userWallet: UserWallet, currency: Currency): CompletionResult<Unit>
/**
* Add list of [Currency] to [UserWallet].
@ -32,10 +29,7 @@ interface WalletCurrenciesManager {
*
* @return [CompletionResult] of operation
* */
suspend fun addCurrencies(
userWallet: UserWallet,
currenciesToAdd: List<Currency>,
): CompletionResult<Unit>
suspend fun addCurrencies(userWallet: UserWallet, currenciesToAdd: List<Currency>): CompletionResult<Unit>
/**
* Remove [Currency] from [UserWallet]
@ -48,10 +42,7 @@ interface WalletCurrenciesManager {
*
* @return [CompletionResult] of operation
* */
suspend fun removeCurrency(
userWallet: UserWallet,
currencyToRemove: Currency,
): CompletionResult<Unit>
suspend fun removeCurrency(userWallet: UserWallet, currencyToRemove: Currency): CompletionResult<Unit>
/**
* Remove list of [Currency] from [UserWallet]
@ -64,10 +55,7 @@ interface WalletCurrenciesManager {
*
* @return [CompletionResult] of operation
* */
suspend fun removeCurrencies(
userWallet: UserWallet,
currenciesToRemove: List<Currency>,
): CompletionResult<Unit>
suspend fun removeCurrencies(userWallet: UserWallet, currenciesToRemove: List<Currency>): CompletionResult<Unit>
/**
* Add a callback [Listener]

View file

@ -33,29 +33,27 @@ internal class DefaultWalletCurrenciesManager(
private val listeners = mutableListOf<WalletCurrenciesManager.Listener>()
override suspend fun update(
userWallet: UserWallet,
currency: Currency,
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
listeners.forEach { it.willUpdate(userWallet, currency) }
val walletStore = walletStoresRepository.getSync(userWallet.walletId)
.find {
it.blockchain == currency.blockchain &&
it.derivationPath?.rawPath == currency.derivationPath
}
override suspend fun update(userWallet: UserWallet, currency: Currency): CompletionResult<Unit> =
withContext(Dispatchers.Default) {
listeners.forEach { it.willUpdate(userWallet, currency) }
val walletStore = walletStoresRepository.getSync(userWallet.walletId)
.find {
it.blockchain == currency.blockchain &&
it.derivationPath?.rawPath == currency.derivationPath
}
val updateResult = if (walletStore == null) {
CompletionResult.Success(Unit)
} else {
walletAmountsRepository.updateAmountsForWalletStore(
walletStore = walletStore,
userWallet = userWallet,
fiatCurrency = appCurrencyProvider(),
)
val updateResult = if (walletStore == null) {
CompletionResult.Success(Unit)
} else {
walletAmountsRepository.updateAmountsForWalletStore(
walletStore = walletStore,
userWallet = userWallet,
fiatCurrency = appCurrencyProvider(),
)
}
listeners.forEach { it.didUpdate(userWallet, currency) }
updateResult
}
listeners.forEach { it.didUpdate(userWallet, currency) }
updateResult
}
override suspend fun addCurrencies(
userWallet: UserWallet,
@ -110,10 +108,7 @@ internal class DefaultWalletCurrenciesManager(
}
}
override suspend fun removeCurrency(
userWallet: UserWallet,
currencyToRemove: Currency,
): CompletionResult<Unit> {
override suspend fun removeCurrency(userWallet: UserWallet, currencyToRemove: Currency): CompletionResult<Unit> {
listeners.forEach { it.willCurrencyRemove(userWallet, currencyToRemove) }
return removeCurrencies(userWallet, listOf(currencyToRemove))
}

View file

@ -11,34 +11,29 @@ sealed class WalletStoresError(code: Int) : TangemError(code) {
override val message: String?
get() = customMessage
@Suppress("MagicNumber")
class FetchFiatRatesError(
currencies: List<String>,
override val cause: Throwable?,
) : WalletStoresError(60011) {
) : WalletStoresError(code = 60011) {
override var customMessage: String = "Failed to fetch fiat rates for currencies $currencies"
}
@Suppress("MagicNumber")
class UnknownBlockchain : WalletStoresError(60012) {
class UnknownBlockchain : WalletStoresError(code = 60012) {
override var customMessage: String = "Unknown blockchain"
}
@Suppress("MagicNumber")
object NoInternetConnection : WalletStoresError(60013) {
object NoInternetConnection : WalletStoresError(code = 60013) {
override var customMessage: String = "No internet connection"
}
@Suppress("MagicNumber")
class WalletManagerNotCreated(blockchain: Blockchain) : WalletStoresError(60014) {
class WalletManagerNotCreated(blockchain: Blockchain) : WalletStoresError(code = 60014) {
override var customMessage: String = "Wallet manager can not be created for $blockchain"
}
@Suppress("MagicNumber")
class UpdateWalletManagerTokensError(
blockchain: Blockchain,
override val cause: Throwable,
) : WalletStoresError(600015) {
) : WalletStoresError(code = 600015) {
override var customMessage: String = "Unable to update wallet manager tokens for currency $blockchain: $cause"
}
}

View file

@ -1,9 +1,11 @@
package com.tangem.tap.domain.walletStores
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.CompletionResult
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.domain.model.UserWallet
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.features.wallet.models.Currency
import kotlinx.coroutines.flow.Flow
interface WalletStoresManager {
@ -22,6 +24,7 @@ interface WalletStoresManager {
* @return [Flow] with [WalletStoreModel] list
* */
fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>>
suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel>
/**
@ -49,10 +52,7 @@ interface WalletStoresManager {
*
* @return [CompletionResult] of operation
* */
suspend fun fetch(
userWallet: UserWallet,
refresh: Boolean = false,
): CompletionResult<Unit>
suspend fun fetch(userWallet: UserWallet, refresh: Boolean = false): CompletionResult<Unit>
/**
* Fetch wallet stores associated with provided [UserWallet]s. Fetched [WalletStoreModel]s updates can be observed
@ -63,10 +63,7 @@ interface WalletStoresManager {
*
* @return [CompletionResult] of operation
* */
suspend fun fetch(
userWallets: List<UserWallet>,
refresh: Boolean = false,
): CompletionResult<Unit>
suspend fun fetch(userWallets: List<UserWallet>, refresh: Boolean = false): CompletionResult<Unit>
/**
* Update [WalletStoreModel]s amounts associated with provided [UserWallet]s
@ -75,8 +72,12 @@ interface WalletStoresManager {
*
* @return [CompletionResult] of operation
* */
suspend fun updateAmounts(
userWallets: List<UserWallet>,
suspend fun updateAmounts(userWallets: List<UserWallet>): CompletionResult<Unit>
suspend fun updateSelectedAddress(
userWalletId: UserWalletId,
currency: Currency,
addressType: AddressType,
): CompletionResult<Unit>
// For provider

View file

@ -1,6 +1,7 @@
package com.tangem.tap.domain.walletStores.implementation
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.CompletionResult
import com.tangem.common.doOnSuccess
import com.tangem.common.flatMap
@ -18,6 +19,7 @@ import com.tangem.tap.domain.walletStores.WalletStoresManager
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository
import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository
import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateSelectedAddress
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.toBlockchainNetworks
import kotlinx.coroutines.Dispatchers
@ -58,38 +60,35 @@ internal class DefaultWalletStoresManager(
return walletStoresRepository.clear()
}
override suspend fun fetch(
userWallets: List<UserWallet>,
refresh: Boolean,
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
val fiatCurrency = appCurrencyProvider.invoke()
val isFiatCurrencyChanged = state.value.fiatCurrency != fiatCurrency
override suspend fun fetch(userWallets: List<UserWallet>, refresh: Boolean): CompletionResult<Unit> =
withContext(Dispatchers.Default) {
val fiatCurrency = appCurrencyProvider.invoke()
val isFiatCurrencyChanged = state.value.fiatCurrency != fiatCurrency
state.update { prevState ->
prevState.copy(
fiatCurrency = fiatCurrency,
)
state.update { prevState ->
prevState.copy(
fiatCurrency = fiatCurrency,
)
}
userWallets
.mapNotNull { userWallet ->
val hasNotWalletStoresForUserWallet = !walletStoresRepository.contains(userWallet.walletId)
if (refresh || hasNotWalletStoresForUserWallet || isFiatCurrencyChanged) {
fetchWalletsIfNeeded(userWallet)
} else {
null
}
}
.fold(arrayListOf<UserWallet>()) { acc, data ->
acc.apply { add(data) }
}
.flatMap {
walletAmountsRepository.updateAmountsForUserWallets(it, fiatCurrency)
}
}
userWallets
.mapNotNull { userWallet ->
val hasNotWalletStoresForUserWallet = !walletStoresRepository.contains(userWallet.walletId)
if (refresh || hasNotWalletStoresForUserWallet || isFiatCurrencyChanged) {
fetchWalletsIfNeeded(userWallet)
} else null
}
.fold(arrayListOf<UserWallet>()) { acc, data ->
acc.apply { add(data) }
}
.flatMap {
walletAmountsRepository.updateAmountsForUserWallets(it, fiatCurrency)
}
}
override suspend fun fetch(
userWallet: UserWallet,
refresh: Boolean,
): CompletionResult<Unit> {
override suspend fun fetch(userWallet: UserWallet, refresh: Boolean): CompletionResult<Unit> {
return fetch(listOf(userWallet), refresh)
}
@ -106,6 +105,21 @@ internal class DefaultWalletStoresManager(
}
}
override suspend fun updateSelectedAddress(
userWalletId: UserWalletId,
currency: Currency,
addressType: AddressType,
): CompletionResult<Unit> {
return walletStoresRepository.update(userWalletId) { walletStores ->
walletStores
.firstOrNull {
it.blockchain == currency.blockchain &&
it.derivationPath?.rawPath == currency.derivationPath
}
?.updateSelectedAddress(currency, addressType)
}
}
private suspend fun fetchWalletsIfNeeded(userWallet: UserWallet): CompletionResult<UserWallet> {
return if (userWallet.isMultiCurrency) {
fetchMultiWallets(userWallet)

View file

@ -1,10 +1,12 @@
package com.tangem.tap.domain.walletStores.implementation
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.CompletionResult
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.domain.model.UserWallet
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.domain.walletStores.WalletStoresManager
import com.tangem.tap.features.wallet.models.Currency
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@ -40,4 +42,12 @@ internal class DummyWalletStoresManager : WalletStoresManager {
override suspend fun updateAmounts(userWallets: List<UserWallet>): CompletionResult<Unit> {
return CompletionResult.Success(Unit)
}
override suspend fun updateSelectedAddress(
userWalletId: UserWalletId,
currency: Currency,
addressType: AddressType,
): CompletionResult<Unit> {
return CompletionResult.Success(Unit)
}
}

View file

@ -24,10 +24,7 @@ interface WalletAmountsRepository {
* @param userWallet [UserWallet] which will be used to get the list of associated [WalletStoreModel]
* @param fiatCurrency current app [FiatCurrency]
* */
suspend fun updateAmountsForUserWallet(
userWallet: UserWallet,
fiatCurrency: FiatCurrency,
): CompletionResult<Unit>
suspend fun updateAmountsForUserWallet(userWallet: UserWallet, fiatCurrency: FiatCurrency): CompletionResult<Unit>
suspend fun updateAmountsForWalletStores(
walletStores: List<WalletStoreModel>,

View file

@ -17,10 +17,7 @@ interface WalletManagersRepository {
suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit>
suspend fun delete(
userWalletId: UserWalletId,
blockchain: Blockchain,
): CompletionResult<Unit>
suspend fun delete(userWalletId: UserWalletId, blockchain: Blockchain): CompletionResult<Unit>
companion object
}

View file

@ -22,9 +22,20 @@ interface WalletStoresRepository {
suspend fun clear(): CompletionResult<Unit>
suspend fun storeOrUpdate(
suspend fun storeOrUpdate(userWalletId: UserWalletId, walletStore: WalletStoreModel): CompletionResult<Unit>
/**
* Updates [WalletStoreModel] in storage for user wallet with provided [UserWalletId]
*
* @param userWalletId [UserWalletId] of user wallet
* @param operation Lambda which receives list of [WalletStoreModel] assigned to user wallet with [userWalletId]
* and returns updated [WalletStoreModel]. If null returned then do nothing
*
* @return [CompletionResult] of operation
* */
suspend fun update(
userWalletId: UserWalletId,
walletStore: WalletStoreModel,
operation: (List<WalletStoreModel>) -> WalletStoreModel?,
): CompletionResult<Unit>
companion object

View file

@ -160,21 +160,19 @@ internal class DefaultWalletAmountsRepository(
}
}
private suspend fun fetchAmountsForUserWallets(
userWallets: List<UserWallet>,
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
userWallets.map { async { fetchAmountsForUserWallet(it) } }.awaitAll().fold()
}
private suspend fun fetchAmountsForUserWallets(userWallets: List<UserWallet>): CompletionResult<Unit> =
withContext(Dispatchers.Default) {
userWallets.map { async { fetchAmountsForUserWallet(it) } }.awaitAll().fold()
}
private suspend fun fetchAmountsForUserWallet(
userWallet: UserWallet,
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
val userWalletId = userWallet.walletId
val scanResponse = userWallet.scanResponse
val walletStores = getWalletStores(listOf(userWallet))
private suspend fun fetchAmountsForUserWallet(userWallet: UserWallet): CompletionResult<Unit> =
withContext(Dispatchers.Default) {
val userWalletId = userWallet.walletId
val scanResponse = userWallet.scanResponse
val walletStores = getWalletStores(listOf(userWallet))
fetchAmountForWalletStores(userWalletId, scanResponse, walletStores)
}
fetchAmountForWalletStores(userWalletId, scanResponse, walletStores)
}
private suspend fun fetchAmountForWalletStores(
userWalletId: UserWalletId,
@ -286,7 +284,9 @@ internal class DefaultWalletAmountsRepository(
rent = rentProvider.rentAmount(),
exemptionAmount = rentExempt,
)
} else null,
} else {
null
},
)
}
is Failure -> Unit
@ -295,39 +295,36 @@ internal class DefaultWalletAmountsRepository(
return CompletionResult.Success(Unit)
}
private suspend fun updateWalletStoreWithError(
walletStore: WalletStoreModel,
wallet: Wallet,
error: TangemError,
) = withContext(Dispatchers.Default) {
Timber.e(
error,
"""
private suspend fun updateWalletStoreWithError(walletStore: WalletStoreModel, wallet: Wallet, error: TangemError) =
withContext(Dispatchers.Default) {
Timber.e(
error,
"""
Unable to fetch amounts
|- User wallet id: ${walletStore.userWalletId}
|- Blockchain: ${walletStore.blockchain}
|- Derivation path: ${walletStore.derivationPath?.rawPath}
""".trimIndent(),
)
""".trimIndent(),
)
if (error is BlockchainSdkError) {
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithError(
wallet = wallet,
error = error,
)
},
)
if (error is BlockchainSdkError) {
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithError(
wallet = wallet,
error = error,
)
},
)
}
CompletionResult.Success(Unit)
} else {
CompletionResult.Failure(error)
}
CompletionResult.Success(Unit)
} else {
CompletionResult.Failure(error)
}
}
private suspend fun updateWalletStoreWithAmounts(
walletStore: WalletStoreModel,
@ -359,53 +356,51 @@ internal class DefaultWalletAmountsRepository(
CompletionResult.Success(Unit)
}
private suspend fun updateWalletStoreWithMissedDerivation(
walletStore: WalletStoreModel,
) = withContext(Dispatchers.Default) {
Timber.e(
"""
private suspend fun updateWalletStoreWithMissedDerivation(walletStore: WalletStoreModel) =
withContext(Dispatchers.Default) {
Timber.e(
"""
Missed derivation
|- User wallet id: ${walletStore.userWalletId}
|- Blockchain: ${walletStore.blockchain}
|- Derivation path: ${walletStore.derivationPath?.rawPath}
""".trimIndent(),
)
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithMissedDerivation()
},
""".trimIndent(),
)
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithMissedDerivation()
},
)
}
CompletionResult.Success(Unit)
}
CompletionResult.Success(Unit)
}
private suspend fun updateWalletStoreWithUnreachable(
walletStore: WalletStoreModel,
) = withContext(Dispatchers.Default) {
Timber.e(
"""
private suspend fun updateWalletStoreWithUnreachable(walletStore: WalletStoreModel) =
withContext(Dispatchers.Default) {
Timber.e(
"""
Wallet manager is null
|- User wallet id: ${walletStore.userWalletId}
|- Blockchain: ${walletStore.blockchain}
|- Derivation path: ${walletStore.derivationPath?.rawPath}
""".trimIndent(),
)
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithUnreachable()
},
""".trimIndent(),
)
}
CompletionResult.Success(Unit)
}
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithUnreachable()
},
)
}
CompletionResult.Success(Unit)
}
private suspend fun updateWalletStoresWithFiatRates(
walletStores: List<WalletStoreModel>,
@ -428,48 +423,44 @@ internal class DefaultWalletAmountsRepository(
}
}
private suspend fun updateWalletStoreWithRent(
walletStore: WalletStoreModel,
rent: WalletStoreModel.WalletRent?,
) = withContext(Dispatchers.Default) {
Timber.d(
"""
private suspend fun updateWalletStoreWithRent(walletStore: WalletStoreModel, rent: WalletStoreModel.WalletRent?) =
withContext(Dispatchers.Default) {
Timber.d(
"""
Fetched wallet rent
|- User wallet id: ${walletStore.userWalletId}
|- Blockchain: ${walletStore.blockchain}
|- Derivation path: ${walletStore.derivationPath?.rawPath}
|- Rent: $rent
""".trimIndent(),
)
""".trimIndent(),
)
if (rent != walletStore.walletRent) {
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithRent(rent)
},
)
}
}
}
private suspend fun updateWalletManagerInStorage(
userWalletId: UserWalletId,
walletManager: WalletManager,
) = withContext(Dispatchers.Default) {
WalletManagerStorage.update { prevManagers ->
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty().toMutableList().apply {
replaceByOrAdd(walletManager) {
it.wallet.blockchain == walletManager.wallet.blockchain
if (rent != walletStore.walletRent) {
WalletStoresStorage.update { prevState ->
prevState.replaceWalletStore(
walletStoreToUpdate = walletStore,
update = {
it.updateWithRent(rent)
},
)
}
}
}
prevManagers.apply {
set(userWalletId, newManagersForUserWallet)
private suspend fun updateWalletManagerInStorage(userWalletId: UserWalletId, walletManager: WalletManager) =
withContext(Dispatchers.Default) {
WalletManagerStorage.update { prevManagers ->
val newManagersForUserWallet = prevManagers[userWalletId].orEmpty().toMutableList().apply {
replaceByOrAdd(walletManager) {
it.wallet.blockchain == walletManager.wallet.blockchain
}
}
prevManagers.apply {
set(userWalletId, newManagersForUserWallet)
}
}
}
}
private suspend fun getWalletStores(userWallets: List<UserWallet>): List<WalletStoreModel> {
return userWallets.map { it.walletId }.flatMap { userWalletId ->

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