Updated on 2026-08-14
This commit is contained in:
parent
e98780ff93
commit
479d142807
283 changed files with 2338 additions and 2730 deletions
|
|
@ -20,8 +20,8 @@ import com.tangem.tap.common.extensions.dispatchOnMain
|
|||
import com.tangem.tap.common.redux.NotificationsHandler
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.common.shop.GooglePayService
|
||||
import com.tangem.tap.common.shop.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE
|
||||
import com.tangem.tap.common.shop.googlepay.GooglePayService
|
||||
import com.tangem.tap.common.shop.googlepay.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE
|
||||
import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient
|
||||
import com.tangem.tap.domain.TangemSdkManager
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
|
|
@ -120,7 +120,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
val isScannedBefore = store.state.globalState.scanResponse != null
|
||||
val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted
|
||||
val shopOpened = store.state.shopState.total != null
|
||||
if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore && !shopOpened)) {
|
||||
@Suppress("ComplexCondition")
|
||||
if (backStackIsEmpty || !isOnboardingServiceActive && !isScannedBefore && !shopOpened) {
|
||||
if (userWalletsListManager.hasSavedUserWallets) {
|
||||
store.dispatchOnMain(WelcomeAction.HandleDeepLink(intent))
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome))
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
private fun initWithConfigDependency(config: Config) {
|
||||
shopService = TangemShopService(this, config.shopify!!)
|
||||
initAnalytics(this, config)
|
||||
initFeedbackManager(this, preferencesStorage, config)
|
||||
initFeedbackManager(this, preferencesStorage)
|
||||
}
|
||||
|
||||
private fun initAnalytics(application: Application, config: Config) {
|
||||
|
|
@ -196,7 +196,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
factory.build(Analytics, buildData)
|
||||
}
|
||||
|
||||
private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) {
|
||||
private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage) {
|
||||
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply {
|
||||
appVersion = try {
|
||||
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ package com.tangem.tap
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
val DELAY_SDK_DIALOG_CLOSE = 1400L
|
||||
const val DELAY_SDK_DIALOG_CLOSE = 1400L
|
||||
|
|
@ -56,6 +56,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
override fun newState(state: GlobalState) {
|
||||
if (state.dialog == null) {
|
||||
dialog?.dismiss()
|
||||
|
|
@ -73,7 +74,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
|
||||
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
|
||||
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
|
||||
is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(state.dialog, context)
|
||||
is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(context)
|
||||
is OnboardingDialog.InterruptOnboarding -> InterruptOnboardingDialog.create(context, state.dialog)
|
||||
is WalletConnectDialog.UnsupportedCard ->
|
||||
SimpleAlertDialog.create(
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class KeyboardObserver(activity: Activity) {
|
|||
onKeyboardListener?.invoke(isShow)
|
||||
}
|
||||
|
||||
private fun isSoftKeyChanged() = ((lastWindowHeight - getWindowHeight()).absoluteValue) == getSoftKeyButtonHeight()
|
||||
private fun isSoftKeyChanged() = (lastWindowHeight - getWindowHeight()).absoluteValue == getSoftKeyButtonHeight()
|
||||
|
||||
private fun getSoftKeyButtonHeight(): Int {
|
||||
val applicationDisplayHeight = DisplayMetrics().apply {
|
||||
|
|
@ -57,5 +57,4 @@ class KeyboardObserver(activity: Activity) {
|
|||
|
||||
return realDisplayHeight - applicationDisplayHeight
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -40,9 +40,10 @@ typealias TestAction = Pair<String, () -> Unit>
|
|||
|
||||
class TestActionsBottomSheetDialog(
|
||||
private val appDialog: AppDialog.TestActionsDialog,
|
||||
context: Context
|
||||
context: Context,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.tap.common.analytics
|
||||
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.analytics.api.ErrorEventLogger
|
||||
import com.tangem.analytics.api.EventLogger
|
||||
import com.tangem.core.analytics.api.ErrorEventLogger
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
import timber.log.Timber
|
||||
|
||||
class AnalyticsEventsLogger(
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ package com.tangem.tap.common.analytics
|
|||
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
|
||||
object TangemSdk {
|
||||
object TangemSdkErrorMapper {
|
||||
|
||||
// This mapping is performed to group errors in FirebaseCrashlytics.
|
||||
// At the moment, the errors in Crashlytics can only be grouped by their place of creation (class and line).
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
fun map(error: TangemSdkError): TangemSdkError {
|
||||
return when (error) {
|
||||
is TangemSdkError.TagLost -> TangemSdkError.TagLost()
|
||||
|
|
@ -90,7 +91,8 @@ object TangemSdk {
|
|||
is TangemSdkError.BackupFailedEmptyWallets -> TangemSdkError.BackupFailedEmptyWallets()
|
||||
is TangemSdkError.BackupFailedNotEmptyWallets -> TangemSdkError.BackupFailedNotEmptyWallets()
|
||||
is TangemSdkError.NoActiveBackup -> TangemSdkError.NoActiveBackup()
|
||||
is TangemSdkError.ResetBackupFailedHasBackupedWallets -> TangemSdkError.ResetBackupFailedHasBackupedWallets()
|
||||
is TangemSdkError.ResetBackupFailedHasBackupedWallets ->
|
||||
TangemSdkError.ResetBackupFailedHasBackupedWallets()
|
||||
is TangemSdkError.BackupServiceInvalidState -> TangemSdkError.BackupServiceInvalidState()
|
||||
is TangemSdkError.NoBackupCardForIndex -> TangemSdkError.NoBackupCardForIndex()
|
||||
is TangemSdkError.EmptyBackupCards -> TangemSdkError.EmptyBackupCards()
|
||||
|
|
@ -53,7 +53,9 @@ class BasicTopUpEventConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun AnalyticsParam.CardBalanceState.Companion.from(walletsData: List<WalletData>): AnalyticsParam.CardBalanceState {
|
||||
private fun AnalyticsParam.CardBalanceState.Companion.from(
|
||||
walletsData: List<WalletData>,
|
||||
): AnalyticsParam.CardBalanceState {
|
||||
val totalCryptoAmount = walletsData.calculateTotalCryptoAmount()
|
||||
return when {
|
||||
totalCryptoAmount.isZero() -> AnalyticsParam.CardBalanceState.Empty
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ sealed class AnalyticsParam {
|
|||
class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol)
|
||||
class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency)
|
||||
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
|
||||
class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.symbol)
|
||||
class FiatCurrency(
|
||||
fiatCurrency: com.tangem.tap.common.entities.FiatCurrency,
|
||||
) : CurrencyType(fiatCurrency.symbol)
|
||||
|
||||
class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import android.app.Application
|
|||
import com.amplitude.api.Amplitude
|
||||
import com.amplitude.api.AmplitudeClient
|
||||
import com.tangem.common.Converter
|
||||
import com.tangem.analytics.api.EventLogger
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer
|
|||
|
||||
import android.content.Context
|
||||
import com.appsflyer.AppsFlyerLib
|
||||
import com.tangem.analytics.api.EventLogger
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import com.google.firebase.analytics.FirebaseAnalytics
|
|||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.tangem.analytics.api.ErrorEventLogger
|
||||
import com.tangem.analytics.api.EventLogger
|
||||
import com.tangem.core.analytics.api.ErrorEventLogger
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.ui.text.TextStyle
|
|||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
fun TextAutoSize(
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -46,7 +47,7 @@ fun TextAutoSize(
|
|||
} else {
|
||||
readyToDraw.value = true
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.tap.common.extensions.ValueCallback
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun <T> OutlinedSpinner(
|
||||
|
|
@ -95,7 +96,7 @@ class ClosePopupTrigger {
|
|||
@Preview
|
||||
@Composable
|
||||
fun TestSpinnerPreview() {
|
||||
Scaffold() {
|
||||
Scaffold {
|
||||
OutlinedSpinner(
|
||||
label = "Blockchain name",
|
||||
itemList = listOf(Blockchain.values()),
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ fun OutlinedTextFieldWidget(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "NestedBlockDepth", "MagicNumber", "MaxLineLength")
|
||||
@Composable
|
||||
private fun OutlinedProgressTextField(
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -127,7 +128,7 @@ private fun OutlinedProgressTextField(
|
|||
logger.log("$isNotUserInput: внешние данные ОДИНАКОВЫ с данными в поле")
|
||||
} else {
|
||||
logger.log("$isNotUserInput: внешние данные РАЗЛИЧАЮТСЯ с данными в поле")
|
||||
if ((textDebouncer.emittedValue != textDebouncer.debounced) || textDebouncer.emitsCountBeforeDebounce > 0) {
|
||||
if (textDebouncer.emittedValue != textDebouncer.debounced || textDebouncer.emitsCountBeforeDebounce > 0) {
|
||||
logger.log("$isNotUserInput: пользователь ВВОДИТ данные -> внешние данные игнорируем, ждем RECOMPOSE")
|
||||
} else {
|
||||
logger.log("$isNotUserInput: пользователь НЕ вводит данные -> пытаемся обработать внешние данные")
|
||||
|
|
|
|||
|
|
@ -125,10 +125,6 @@ private fun PinElement(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HiddenInputField() {
|
||||
}
|
||||
|
||||
private fun createPinSymbolsList(size: Int, text: String): List<String?> = List(size) {
|
||||
try {
|
||||
text[it].toString()
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.common.compose.extensions
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.core.graphics.alpha
|
||||
import androidx.core.graphics.blue
|
||||
import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun Color.toAndroidGraphicsColor(): Int {
|
||||
val argb = this.toArgb()
|
||||
return android.graphics.Color.argb(argb.alpha, argb.red, argb.green, argb.blue)
|
||||
}
|
||||
|
||||
fun Color.parse(hexColor: String): Color {
|
||||
return Color(hexColor.removePrefix("#").toInt(16))
|
||||
}
|
||||
|
|
@ -13,7 +13,8 @@ import androidx.core.graphics.drawable.toBitmap
|
|||
*/
|
||||
@Composable
|
||||
fun asImageBitmap(@DrawableRes drawableId: Int): ImageBitmap {
|
||||
val drawable = AppCompatResources.getDrawable(LocalContext.current, drawableId)
|
||||
?: throw NullPointerException()
|
||||
val drawable = requireNotNull(AppCompatResources.getDrawable(LocalContext.current, drawableId)) {
|
||||
"drawable is null"
|
||||
}
|
||||
return drawable.toBitmap().asImageBitmap()
|
||||
}
|
||||
|
|
@ -6,8 +6,7 @@ import com.tangem.operations.backup.BackupService
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun BackupService.primaryCardIsSaltPayVisa(): Boolean {
|
||||
return primaryCardId?.slice(0..3)?.let {
|
||||
SaltPayWorkaround.isVisaBatchId(it)
|
||||
} ?: false
|
||||
return primaryCardId?.slice(0..3)?.let(SaltPayWorkaround::isVisaBatchId) ?: false
|
||||
}
|
||||
|
|
@ -4,12 +4,14 @@ import android.graphics.Bitmap
|
|||
import android.graphics.BitmapFactory
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun Bitmap.toByteArray(): ByteArray {
|
||||
val stream = ByteArrayOutputStream()
|
||||
this.compress(Bitmap.CompressFormat.JPEG, 20, stream)
|
||||
return stream.toByteArray()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun ByteArray.toBitmap(): Bitmap {
|
||||
return BitmapFactory.decodeByteArray(this, 0, this.size)
|
||||
}
|
||||
|
|
@ -5,16 +5,17 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.extensions.remove
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
@DrawableRes
|
||||
fun Blockchain.getGreyedOutIconRes(): Int {
|
||||
return when (this) {
|
||||
Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_no_color
|
||||
// Blockchain.Ducatus -> R.drawable.ic_ducatus
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_no_color
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> R.drawable.ic_bitcoin_no_color
|
||||
Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_no_color
|
||||
Blockchain.Litecoin -> R.drawable.ic_litecoin_no_color
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_no_color
|
||||
Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet, -> R.drawable.ic_eth_no_color
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> R.drawable.ic_eth_no_color
|
||||
Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.ic_eth_no_color
|
||||
Blockchain.RSK -> R.drawable.ic_rsk_no_color
|
||||
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_no_color
|
||||
Blockchain.Tezos -> R.drawable.ic_tezos_no_color
|
||||
|
|
@ -24,7 +25,8 @@ fun Blockchain.getGreyedOutIconRes(): Int {
|
|||
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_no_color
|
||||
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_no_color
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_no_color
|
||||
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_no_color
|
||||
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet ->
|
||||
R.drawable.ic_bsc_no_color
|
||||
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_no_color
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_no_color
|
||||
Blockchain.Gnosis -> R.drawable.ic_gnosis_no_color
|
||||
|
|
@ -49,4 +51,4 @@ fun Blockchain.getNetworkName(): String {
|
|||
}
|
||||
|
||||
val Blockchain.fullNameWithoutTestnet
|
||||
get() = this.fullName.remove(" Testnet")
|
||||
get() = this.fullName.remove(" Testnet")
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.feature.referral.ReferralFragment
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
|
@ -84,21 +82,15 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? {
|
|||
} else {
|
||||
0
|
||||
}
|
||||
val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount)
|
||||
val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount) {
|
||||
this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name
|
||||
else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return tag?.let { AppScreen.valueOf(tag) }
|
||||
}
|
||||
|
||||
fun FragmentActivity.addOnBackPressedDispatcher(
|
||||
isEnabled: Boolean = true,
|
||||
onBackPressed: VoidCallback,
|
||||
): OnBackPressedCallback = (object : OnBackPressedCallback(isEnabled) {
|
||||
override fun handleOnBackPressed() {
|
||||
onBackPressed()
|
||||
}
|
||||
}).also { this.onBackPressedDispatcher.addCallback(it) }
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun fragmentFactory(screen: AppScreen): Fragment {
|
||||
return when (screen) {
|
||||
AppScreen.Home -> HomeFragment()
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import java.util.*
|
|||
|
||||
//todo move extensions to utils
|
||||
fun BigDecimal.toFormattedString(
|
||||
decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US
|
||||
decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, locale: Locale = Locale.US,
|
||||
): String {
|
||||
val symbols = DecimalFormatSymbols(locale)
|
||||
val df = DecimalFormat()
|
||||
|
|
@ -25,9 +25,10 @@ fun BigDecimal.toFormattedString(
|
|||
return df.format(this)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun BigDecimal.toFormattedCurrencyString(
|
||||
decimals: Int, currency: String, roundingMode: RoundingMode = RoundingMode.DOWN,
|
||||
limitNumberOfDecimals: Boolean = true
|
||||
limitNumberOfDecimals: Boolean = true,
|
||||
): String {
|
||||
val decimalsForRounding = if (limitNumberOfDecimals) {
|
||||
if (decimals > 8) 8 else decimals
|
||||
|
|
@ -35,13 +36,13 @@ fun BigDecimal.toFormattedCurrencyString(
|
|||
decimals
|
||||
}
|
||||
val formattedAmount = this.toFormattedString(
|
||||
decimals = decimalsForRounding, roundingMode = roundingMode
|
||||
decimals = decimalsForRounding, roundingMode = roundingMode,
|
||||
)
|
||||
return "$formattedAmount $currency"
|
||||
}
|
||||
|
||||
fun BigDecimal.toFiatRateString(
|
||||
fiatCurrencyName: String
|
||||
fiatCurrencyName: String,
|
||||
): String {
|
||||
val value = this
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
|
|
@ -52,7 +53,7 @@ fun BigDecimal.toFiatRateString(
|
|||
fun BigDecimal.toFiatString(
|
||||
rateValue: BigDecimal,
|
||||
fiatCurrencyName: String,
|
||||
formatWithSpaces: Boolean = false
|
||||
formatWithSpaces: Boolean = false,
|
||||
): String {
|
||||
val fiatValue = rateValue.multiply(this)
|
||||
return fiatValue.toFormattedFiatValue(fiatCurrencyName, formatWithSpaces)
|
||||
|
|
@ -65,7 +66,7 @@ fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
|
|||
|
||||
fun BigDecimal.toFormattedFiatValue(
|
||||
fiatCurrencyName: String,
|
||||
formatWithSpaces: Boolean = false
|
||||
formatWithSpaces: Boolean = false,
|
||||
): String {
|
||||
val fiatValue = this.setScale(2, RoundingMode.HALF_UP)
|
||||
.let { if (formatWithSpaces) it.formatWithSpaces() else it }
|
||||
|
|
@ -82,9 +83,7 @@ fun BigDecimal.scaleToFiat(applyPrecision: Boolean = false): BigDecimal {
|
|||
if (this.isZero()) return this
|
||||
|
||||
val scaledFiat = this.setScale(2, RoundingMode.DOWN)
|
||||
return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1)
|
||||
else scaledFiat
|
||||
|
||||
return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1) else scaledFiat
|
||||
}
|
||||
|
||||
fun BigDecimal.setPrecision(precision: Int, roundingMode: RoundingMode = RoundingMode.DOWN): BigDecimal {
|
||||
|
|
@ -109,7 +108,7 @@ fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean {
|
|||
|
||||
fun BigDecimal.formatAmountAsSpannedString(
|
||||
currencySymbol: String,
|
||||
reminderPartSizeProportion: Float = 0.7f
|
||||
reminderPartSizeProportion: Float = 0.7f,
|
||||
): SpannedString {
|
||||
val amount = this
|
||||
.setScale(2, RoundingMode.HALF_UP)
|
||||
|
|
@ -125,11 +124,12 @@ fun BigDecimal.formatAmountAsSpannedString(
|
|||
append(
|
||||
"$reminder $currencySymbol",
|
||||
RelativeSizeSpan(reminderPartSizeProportion),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun BigDecimal.formatWithSpaces(): String {
|
||||
val str = this.toString()
|
||||
var integerStr = str.substringBefore('.')
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ fun String.colorSegment(
|
|||
context: Context,
|
||||
color: Int,
|
||||
startIndex: Int = 0,
|
||||
endIndex: Int = this.length
|
||||
endIndex: Int = this.length,
|
||||
): Spannable {
|
||||
return this.toSpannable()
|
||||
.also { spannable ->
|
||||
|
|
@ -37,11 +37,12 @@ fun String.colorSegment(
|
|||
ForegroundColorSpan(ContextCompat.getColor(context, color)),
|
||||
startIndex,
|
||||
endIndex,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun String.toQrCode(): Bitmap {
|
||||
val hintMap = Hashtable<EncodeHintType, Any>()
|
||||
hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M // H = 30% damage
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ fun EditText.update(text: String?) {
|
|||
this.setText(text)
|
||||
if (!isFocused || textLength == 0) return
|
||||
|
||||
if (cursorPosition == 0) setSelection(textLength)
|
||||
else setSelection(cursorPosition)
|
||||
if (cursorPosition == 0) setSelection(textLength) else setSelection(cursorPosition)
|
||||
}
|
||||
|
||||
fun EditText.setOnImeActionListener(action: Int, handler: (EditText) -> Unit) {
|
||||
|
|
@ -56,13 +55,4 @@ fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) {
|
|||
error = null
|
||||
isErrorEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
fun TextView.isEllipsized(): Boolean {
|
||||
val layout = this.layout
|
||||
if (layout != null) {
|
||||
val lines: Int = layout.lineCount
|
||||
return (lines > 0) && (layout.getEllipsisCount(lines - 1) > 0)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -6,19 +6,23 @@ import androidx.core.graphics.luminance
|
|||
import androidx.core.graphics.toColorInt
|
||||
import com.tangem.blockchain.common.Token
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@ColorInt
|
||||
fun Token.getColor(isTestnet: Boolean = false): Int {
|
||||
val defaultColor = "#C7C7CC".toColorInt() // equivalent to R.color.lightGray4
|
||||
|
||||
return if (isTestnet)
|
||||
defaultColor
|
||||
else try {
|
||||
("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt()
|
||||
} catch (exception: Exception) {
|
||||
return if (isTestnet) {
|
||||
defaultColor
|
||||
} else {
|
||||
try {
|
||||
("#" + this.contractAddress.subSequence(2..7).toString()).toColorInt()
|
||||
} catch (exception: Exception) {
|
||||
defaultColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@ColorInt
|
||||
fun Token.getTextColor(isTestnet: Boolean = false): Int = when {
|
||||
isTestnet -> Color.WHITE
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
|
|
@ -25,11 +23,6 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
|
||||
fun Fragment.getDrawable(@DrawableRes drawableResId: Int): Drawable? {
|
||||
return ContextCompat.getDrawable(requireContext(), drawableResId)
|
||||
}
|
||||
|
||||
fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? {
|
||||
return ContextCompat.getDrawable(this, drawableResId)
|
||||
|
|
@ -62,17 +55,8 @@ fun View.getQuantityString(@PluralsRes id: Int, quantity: Int): String {
|
|||
return context.resources.getQuantityString(id, quantity, quantity)
|
||||
}
|
||||
|
||||
fun View.getResourceName(): String {
|
||||
return try {
|
||||
resources.getResourceEntryName(id)
|
||||
} catch (ex: Resources.NotFoundException) {
|
||||
"Not found"
|
||||
}
|
||||
}
|
||||
|
||||
fun View.show(show: Boolean, invokeBeforeStateChanged: (() -> Unit)? = null) {
|
||||
return if (show) this.show(invokeBeforeStateChanged)
|
||||
else this.hide(invokeBeforeStateChanged)
|
||||
return if (show) this.show(invokeBeforeStateChanged) else this.hide(invokeBeforeStateChanged)
|
||||
}
|
||||
|
||||
fun View.show(invokeBeforeStateChanged: (() -> Unit)? = null) {
|
||||
|
|
@ -111,12 +95,6 @@ fun Context.pixelsToDp(pixels: Int): Int {
|
|||
.toInt()
|
||||
}
|
||||
|
||||
fun Context.dpToPixels(dp: Float): Float =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this.resources.displayMetrics)
|
||||
|
||||
fun Context.pixelsToDp(pixels: Float): Float =
|
||||
(pixels / (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT))
|
||||
|
||||
tailrec fun Context?.getActivity(): Activity? = this as? Activity
|
||||
?: (this as? ContextWrapper)?.baseContext?.getActivity()
|
||||
|
||||
|
|
@ -136,19 +114,6 @@ fun MaterialCardView.setMargins(
|
|||
this.layoutParams = params
|
||||
}
|
||||
|
||||
fun Activity.setSystemBarTextColor(setTextDark: Boolean) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val flags = this.window.decorView.systemUiVisibility
|
||||
// Update the SystemUiVisibility dependening on whether we want a Light or Dark theme.
|
||||
this.window.decorView.systemUiVisibility =
|
||||
if (setTextDark) {
|
||||
flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
|
||||
} else {
|
||||
flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun View.hideKeyboard() {
|
||||
val inputMethodManager =
|
||||
context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
|
|
@ -185,21 +150,6 @@ fun Fragment.shareText(text: String) {
|
|||
requireContext().shareText(text)
|
||||
}
|
||||
|
||||
fun Context.safeStartActivity(
|
||||
intent: Intent,
|
||||
options: Bundle? = null,
|
||||
fallback: ((ActivityNotFoundException) -> Unit)? = null,
|
||||
finally: VoidCallback? = null,
|
||||
) {
|
||||
try {
|
||||
this.startActivity(intent, options)
|
||||
} catch (ex: ActivityNotFoundException) {
|
||||
fallback?.invoke(ex)
|
||||
} finally {
|
||||
finally?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun View.getString(resId: Int, vararg formatArgs: Any?): String {
|
||||
return context.getString(resId, *formatArgs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import timber.log.Timber
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
|
||||
|
|
@ -72,8 +73,7 @@ fun WalletManager?.getAddressData(): AddressData? {
|
|||
val wallet = this?.wallet ?: return null
|
||||
|
||||
val addressDataList = wallet.createAddressesData()
|
||||
return if (addressDataList.isEmpty()) null
|
||||
else addressDataList[0]
|
||||
return if (addressDataList.isEmpty()) null else addressDataList[0]
|
||||
}
|
||||
|
||||
fun <T> WalletManager.Companion.stub(): T {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ class AdditionalFeedbackInfo {
|
|||
var fee: String = ""
|
||||
var token: String = ""
|
||||
|
||||
private val Address.name: String
|
||||
get() = type.javaClass.simpleName
|
||||
|
||||
fun setCardInfo(data: ScanResponse) {
|
||||
cardId = data.card.cardId
|
||||
cardBlockchain = data.walletData?.blockchain ?: ""
|
||||
|
|
@ -108,6 +111,7 @@ class AdditionalFeedbackInfo {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String {
|
||||
return if (addresses.size == 1) {
|
||||
getExploreUrl(address)
|
||||
|
|
@ -117,7 +121,4 @@ class AdditionalFeedbackInfo {
|
|||
.joinToString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
private val Address.name: String
|
||||
get() = type.javaClass.simpleName
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ interface FeedbackData {
|
|||
|
||||
fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String {
|
||||
return StringBuilder().apply {
|
||||
append(context.getString(mainMessageResId))
|
||||
|
|
@ -42,6 +43,7 @@ class ScanFailsEmail : FeedbackData {
|
|||
override val subjectResId: Int = R.string.feedback_subject_scan_failed
|
||||
override val mainMessageResId: Int = R.string.feedback_preface_scan_failed
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String = StringBuilder().apply {
|
||||
append(context.getString(mainMessageResId))
|
||||
breakLine(4)
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ class TestLeapfrogFragment : Fragment(R.layout.test_leapfrog_fragment) {
|
|||
private lateinit var twinsCardWidget: TwinsCardWidget
|
||||
private val binding: TestLeapfrogFragmentBinding by viewBinding(TestLeapfrogFragmentBinding::bind)
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,11 +16,6 @@ import me.dm7.barcodescanner.zxing.ZXingScannerView
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
|
||||
companion object {
|
||||
val SCAN_QR_REQUEST_CODE = 1001
|
||||
val SCAN_RESULT = "scanResult"
|
||||
}
|
||||
|
||||
private lateinit var mScannerView: ZXingScannerView
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
|
|
@ -29,7 +24,6 @@ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
|
|||
setContentView(mScannerView)
|
||||
|
||||
if (!permissionIsGranted()) requestPermission()
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
@ -66,4 +60,9 @@ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
|
|||
private fun requestPermission() {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SCAN_QR_REQUEST_CODE = 1001
|
||||
const val SCAN_RESULT = "scanResult"
|
||||
}
|
||||
}
|
||||
|
|
@ -88,6 +88,7 @@ val notificationsMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun handleNotificationAction(action: Action) {
|
||||
if (action is Debug && !BuildConfig.DEBUG) return
|
||||
|
||||
|
|
|
|||
|
|
@ -30,10 +30,8 @@ import org.rekotlin.DispatchFunction
|
|||
import org.rekotlin.Middleware
|
||||
import java.util.*
|
||||
|
||||
class GlobalMiddleware {
|
||||
companion object {
|
||||
val handler = globalMiddlewareHandler
|
||||
}
|
||||
object GlobalMiddleware {
|
||||
val handler = globalMiddlewareHandler
|
||||
}
|
||||
|
||||
private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState ->
|
||||
|
|
@ -45,6 +43,7 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleAction(action: Action, appState: () -> AppState?, dispatch: DispatchFunction) {
|
||||
when (action) {
|
||||
is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
|
||||
|
|
@ -9,6 +9,7 @@ import com.tangem.tap.preferencesStorage
|
|||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import org.rekotlin.Action
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolder): GlobalState {
|
||||
|
||||
if (action !is GlobalAction) return state.globalState
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import java.lang.ref.WeakReference
|
|||
data class FragmentShareTransition(
|
||||
val shareElements: List<ShareElement>,
|
||||
val enterTransitionSet: TransitionSet,
|
||||
val exitTransitionSet: TransitionSet
|
||||
val exitTransitionSet: TransitionSet,
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
@ -24,12 +24,12 @@ class ShareElement(view: View, name: String? = null) {
|
|||
init {
|
||||
name?.let { view.transitionName = it }
|
||||
elementName = view.transitionName
|
||||
?: throw UnsupportedOperationException("ShareElement require the name")
|
||||
?: throw UnsupportedOperationException("ShareElement require the name")
|
||||
wView = WeakReference(view)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val imvFrontCard = "imv_front_card"
|
||||
val imvBackCard = "imv_back_card"
|
||||
const val imvFrontCard = "imv_front_card"
|
||||
const val imvBackCard = "imv_back_card"
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,8 @@ import com.tangem.tap.common.extensions.getPreviousScreen
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
class NavigationReducer {
|
||||
companion object {
|
||||
fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state)
|
||||
}
|
||||
object NavigationReducer {
|
||||
fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, state: AppState): NavigationState {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.tap.common.extensions.filterNotNull
|
|||
import com.tangem.tap.common.shop.data.ProductType
|
||||
import com.tangem.tap.common.shop.data.TangemProduct
|
||||
import com.tangem.tap.common.shop.data.TotalSum
|
||||
import com.tangem.tap.common.shop.googlepay.GooglePayService
|
||||
import com.tangem.tap.common.shop.shopify.ShopifyService
|
||||
import com.tangem.tap.common.shop.shopify.ShopifyShop
|
||||
import com.tangem.tap.common.shop.shopify.data.CheckoutItem
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.common.shop
|
||||
package com.tangem.tap.common.shop.googlepay
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Activity.RESULT_CANCELED
|
||||
|
|
@ -6,9 +6,12 @@ import android.app.Activity.RESULT_OK
|
|||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import com.google.android.gms.wallet.*
|
||||
import com.google.android.gms.wallet.AutoResolveHelper
|
||||
import com.google.android.gms.wallet.IsReadyToPayRequest
|
||||
import com.google.android.gms.wallet.PaymentData
|
||||
import com.google.android.gms.wallet.PaymentDataRequest
|
||||
import com.google.android.gms.wallet.PaymentsClient
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.tap.common.shop.googlepay.GooglePayUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONException
|
||||
|
|
@ -48,7 +51,7 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
|
|||
totalPriceCents,
|
||||
currencyCode = currencyCode,
|
||||
countryCode = "RU",
|
||||
merchantID = merchantID
|
||||
merchantID = merchantID,
|
||||
)
|
||||
if (paymentDataRequestJson == null) {
|
||||
Timber.e("RequestPayment: can't fetch payment data request")
|
||||
|
|
@ -57,7 +60,7 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
|
|||
val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString())
|
||||
|
||||
AutoResolveHelper.resolveTask(
|
||||
paymentsClient.loadPaymentData(request), activity, LOAD_PAYMENT_DATA_REQUEST_CODE
|
||||
paymentsClient.loadPaymentData(request), activity, LOAD_PAYMENT_DATA_REQUEST_CODE,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +84,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
|
|||
} else {
|
||||
Result.failure(Exception("$statusCode"))
|
||||
}
|
||||
|
||||
}
|
||||
else -> Result.failure(Exception("Unknown Status"))
|
||||
}
|
||||
|
|
@ -117,7 +119,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
|
|||
.getString("token")
|
||||
|
||||
return GooglePayResponse(address, token)
|
||||
|
||||
} catch (e: JSONException) {
|
||||
Log.e("handlePaymentSuccess", "Error: " + e.toString())
|
||||
}
|
||||
|
|
@ -144,5 +145,5 @@ data class Address(
|
|||
val address3: String,
|
||||
val locality: String,
|
||||
val administrativeArea: String,
|
||||
val sortingCode: String
|
||||
val sortingCode: String,
|
||||
)
|
||||
|
|
@ -14,20 +14,7 @@ object GooglePayUtil {
|
|||
put("apiVersionMinor", 0)
|
||||
}
|
||||
|
||||
private fun gatewayTokenizationSpecification(merchantID: String): JSONObject {
|
||||
return JSONObject().apply {
|
||||
put("type", "PAYMENT_GATEWAY")
|
||||
put(
|
||||
"parameters", JSONObject(
|
||||
mapOf(
|
||||
"gateway" to "shopify",
|
||||
"gatewayMerchantId" to merchantID
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private val allowedCardNetworks = JSONArray(
|
||||
listOf(
|
||||
"AMEX",
|
||||
|
|
@ -35,17 +22,34 @@ object GooglePayUtil {
|
|||
"INTERAC",
|
||||
"JCB",
|
||||
"MASTERCARD",
|
||||
"VISA"
|
||||
)
|
||||
"VISA",
|
||||
),
|
||||
)
|
||||
|
||||
private val allowedCardAuthMethods = JSONArray(
|
||||
listOf(
|
||||
"PAN_ONLY",
|
||||
"CRYPTOGRAM_3DS"
|
||||
)
|
||||
"CRYPTOGRAM_3DS",
|
||||
),
|
||||
)
|
||||
|
||||
private val merchantInfo: JSONObject = JSONObject().put("merchantName", "Example Merchant")
|
||||
|
||||
private fun gatewayTokenizationSpecification(merchantID: String): JSONObject {
|
||||
return JSONObject().apply {
|
||||
put("type", "PAYMENT_GATEWAY")
|
||||
put(
|
||||
"parameters",
|
||||
JSONObject(
|
||||
mapOf(
|
||||
"gateway" to "shopify",
|
||||
"gatewayMerchantId" to merchantID,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun baseCardPaymentMethod(): JSONObject {
|
||||
return JSONObject().apply {
|
||||
|
||||
|
|
@ -53,9 +57,12 @@ object GooglePayUtil {
|
|||
put("allowedAuthMethods", allowedCardAuthMethods)
|
||||
put("allowedCardNetworks", allowedCardNetworks)
|
||||
put("billingAddressRequired", true)
|
||||
put("billingAddressParameters", JSONObject().apply {
|
||||
put("format", "FULL")
|
||||
})
|
||||
put(
|
||||
"billingAddressParameters",
|
||||
JSONObject().apply {
|
||||
put("format", "FULL")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
put("type", "CARD")
|
||||
|
|
@ -83,7 +90,6 @@ object GooglePayUtil {
|
|||
baseRequest.apply {
|
||||
put("allowedPaymentMethods", JSONArray().put(baseCardPaymentMethod()))
|
||||
}
|
||||
|
||||
} catch (e: JSONException) {
|
||||
null
|
||||
}
|
||||
|
|
@ -92,7 +98,7 @@ object GooglePayUtil {
|
|||
private fun getTransactionInfo(
|
||||
price: String,
|
||||
countryCode: String,
|
||||
currencyCode: String
|
||||
currencyCode: String,
|
||||
): JSONObject {
|
||||
return JSONObject().apply {
|
||||
put("totalPrice", price)
|
||||
|
|
@ -102,15 +108,11 @@ object GooglePayUtil {
|
|||
}
|
||||
}
|
||||
|
||||
private val merchantInfo: JSONObject =
|
||||
JSONObject().put("merchantName", "Example Merchant")
|
||||
|
||||
|
||||
fun getPaymentDataRequest(
|
||||
price: String,
|
||||
countryCode: String,
|
||||
currencyCode: String,
|
||||
merchantID: String
|
||||
merchantID: String,
|
||||
): JSONObject? {
|
||||
try {
|
||||
return baseRequest.apply {
|
||||
|
|
@ -133,5 +135,4 @@ object GooglePayUtil {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const val PAYMENTS_ENVIRONMENT = WalletConstants.ENVIRONMENT_TEST
|
||||
|
|
@ -16,11 +16,10 @@ import java.util.concurrent.TimeUnit
|
|||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class ShopifyService(private val application: Application, val shop: ShopifyShop) {
|
||||
val client: GraphClient by lazy { initClient() }
|
||||
|
||||
|
||||
suspend fun getShopName(): Result<String> {
|
||||
val query = query { rootQuery: QueryRootQuery ->
|
||||
rootQuery
|
||||
|
|
@ -40,16 +39,15 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun getProducts(collectionTitleFilter: String? = null): Result<List<Product>> {
|
||||
val filter = collectionTitleFilter?.let { "title:\"$it\"" }
|
||||
|
||||
val query = query { rootQuery: QueryRootQuery ->
|
||||
rootQuery
|
||||
.collections(
|
||||
{ arg -> arg.first(250).query(filter) },
|
||||
) { collectionConnectionQuery ->
|
||||
collectionConnectionQuery.collectionFieldsFragment()
|
||||
}
|
||||
rootQuery.collections(
|
||||
{ arg -> arg.first(250).query(filter) },
|
||||
CollectionConnectionQuery::collectionFieldsFragment,
|
||||
)
|
||||
}
|
||||
return when (val result = queryAsync(query)) {
|
||||
is GraphCallResult.Success -> {
|
||||
|
|
@ -78,7 +76,7 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
}
|
||||
}
|
||||
val retryHandler = RetryHandler.build<QueryRoot>(
|
||||
1, TimeUnit.SECONDS
|
||||
1, TimeUnit.SECONDS,
|
||||
) {
|
||||
this.retryWhen { result ->
|
||||
when (result) {
|
||||
|
|
@ -100,7 +98,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
} else {
|
||||
Result.failure(ShopifyError.Unknown)
|
||||
}
|
||||
|
||||
}
|
||||
is GraphCallResult.Failure -> {
|
||||
Result.failure(result.error)
|
||||
|
|
@ -110,10 +107,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
|
||||
suspend fun createCheckout(
|
||||
checkoutItems: List<CheckoutItem>,
|
||||
checkoutID: ID? = null
|
||||
checkoutID: ID? = null,
|
||||
): Result<Checkout> {
|
||||
|
||||
|
||||
val storefrontLineItems: MutableList<CheckoutLineItemInput> = checkoutItems
|
||||
.map { CheckoutLineItemInput(it.quantity, it.id) }.toMutableList()
|
||||
|
||||
|
|
@ -121,13 +117,13 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutLineItemsReplace(
|
||||
storefrontLineItems, checkoutID
|
||||
storefrontLineItems, checkoutID,
|
||||
) { payloadQuery: CheckoutLineItemsReplacePayloadQuery ->
|
||||
payloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.userErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
.userErrors { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
|
|
@ -137,18 +133,18 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
} else {
|
||||
val input = CheckoutCreateInput()
|
||||
.setLineItemsInput(
|
||||
Input.value(storefrontLineItems)
|
||||
Input.value(storefrontLineItems),
|
||||
)
|
||||
mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutCreate(
|
||||
input
|
||||
input,
|
||||
) { payloadQuery: CheckoutCreatePayloadQuery ->
|
||||
payloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
.checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
|
|
@ -163,13 +159,13 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutDiscountCodeApplyV2(
|
||||
discountCode, checkoutID
|
||||
discountCode, checkoutID,
|
||||
) { payloadQuery: CheckoutDiscountCodeApplyV2PayloadQuery ->
|
||||
payloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
.checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
|
|
@ -183,44 +179,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutDiscountCodeRemove(
|
||||
checkoutID
|
||||
checkoutID,
|
||||
) { payloadQuery: CheckoutDiscountCodeRemovePayloadQuery ->
|
||||
payloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
}
|
||||
}
|
||||
}
|
||||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
suspend fun updateAddress(
|
||||
address: MailingAddress,
|
||||
checkoutID: ID,
|
||||
waitForShippingRates: Boolean
|
||||
): Result<Checkout> {
|
||||
val input = MailingAddressInput()
|
||||
.setAddress1(address.address1)
|
||||
.setAddress2(address.address2)
|
||||
.setCity(address.city)
|
||||
.setCountry(address.country)
|
||||
.setFirstName(address.firstName)
|
||||
.setLastName(address.lastName)
|
||||
.setPhone(address.phone)
|
||||
.setProvince(address.province)
|
||||
.setZip(address.zip)
|
||||
|
||||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutShippingAddressUpdateV2(
|
||||
input, checkoutID
|
||||
) { shippingAddressUpdatePayloadQuery: CheckoutShippingAddressUpdateV2PayloadQuery ->
|
||||
shippingAddressUpdatePayloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
|
|
@ -231,59 +192,18 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
suspend fun updateEmail(email: String?, checkoutID: ID): Result<Checkout> {
|
||||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutEmailUpdateV2(
|
||||
checkoutID, email
|
||||
) { emailUpdatePayloadQuery: CheckoutEmailUpdateV2PayloadQuery ->
|
||||
emailUpdatePayloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
}
|
||||
}
|
||||
}
|
||||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
suspend fun updateShippingRate(handle: String?, checkoutID: ID): Result<Checkout> {
|
||||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutShippingLineUpdate(
|
||||
checkoutID, handle
|
||||
) { shippingLineUpdatePayloadQuery: CheckoutShippingLineUpdatePayloadQuery ->
|
||||
shippingLineUpdatePayloadQuery
|
||||
.checkout { checkoutQuery: CheckoutQuery ->
|
||||
checkoutQuery.checkoutFieldsFragment()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
}
|
||||
}
|
||||
}
|
||||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
suspend fun completeWithTokenizedPayment(
|
||||
payment: TokenizedPaymentInputV3,
|
||||
checkoutID: ID
|
||||
checkoutID: ID,
|
||||
): Result<Checkout> {
|
||||
|
||||
val query = mutation { mutationQuery: MutationQuery ->
|
||||
mutationQuery
|
||||
.checkoutCompleteWithTokenizedPaymentV3(
|
||||
checkoutID, payment
|
||||
checkoutID, payment,
|
||||
) { payloadQuery: CheckoutCompleteWithTokenizedPaymentV3PayloadQuery ->
|
||||
payloadQuery
|
||||
.payment { paymentQuery: PaymentQuery ->
|
||||
|
|
@ -295,7 +215,7 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
checkoutQuery
|
||||
.ready()
|
||||
}
|
||||
.checkoutUserErrors() { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
.checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery ->
|
||||
userErrorQuery
|
||||
.field()
|
||||
.message()
|
||||
|
|
@ -305,10 +225,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
return runCheckoutMutation(query)
|
||||
}
|
||||
|
||||
fun startGooglePaySession() {
|
||||
// PaySession()
|
||||
}
|
||||
|
||||
private suspend fun runCheckoutMutation(mutation: MutationQuery): Result<Checkout> {
|
||||
return when (val result = mutationQueryAsync(mutation)) {
|
||||
is GraphCallResult.Success -> {
|
||||
|
|
@ -322,13 +238,13 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
|
||||
Result.success(checkout)
|
||||
}
|
||||
is GraphCallResult.Failure -> Result.failure(result.error)
|
||||
is GraphCallResult.Failure -> Result.failure(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun queryAsync(
|
||||
query: QueryRootQuery,
|
||||
retryHandler: RetryHandler<QueryRoot>
|
||||
retryHandler: RetryHandler<QueryRoot>,
|
||||
): GraphCallResult<QueryRoot> =
|
||||
withContext(Dispatchers.IO) {
|
||||
suspendCoroutine { continuation ->
|
||||
|
|
@ -358,12 +274,11 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun initClient(): GraphClient {
|
||||
return GraphClient.build(
|
||||
application,
|
||||
shop.domain,
|
||||
shop.storefrontApiKey
|
||||
shop.storefrontApiKey,
|
||||
) {
|
||||
// httpCache(application.filesDir) {
|
||||
// cacheMaxSizeBytes = (1024 * 1024 * 10)
|
||||
|
|
@ -371,13 +286,9 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
|
|||
// HttpCachePolicy.Default.CACHE_FIRST.expireAfter(20, TimeUnit.MINUTES)
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed class ShopifyError : Throwable() {
|
||||
object Unknown : ShopifyError()
|
||||
object GooglePayFailed : ShopifyError()
|
||||
class UserError(val errorMessage: String)
|
||||
}
|
||||
|
|
@ -2,23 +2,24 @@ package com.tangem.tap.common.shop.shopify.data
|
|||
|
||||
import com.shopify.buy3.Storefront
|
||||
|
||||
@Suppress("LongMethod", "MagicNumber")
|
||||
fun Storefront.CheckoutQuery.checkoutFieldsFragment() {
|
||||
// id()
|
||||
ready()
|
||||
webUrl()
|
||||
currencyCode()
|
||||
lineItemsSubtotalPrice { it.amount() }
|
||||
totalPriceV2 {
|
||||
it.currencyCode()
|
||||
it.amount() }
|
||||
it.amount()
|
||||
}
|
||||
lineItems({ arg -> arg.first(250) }) {
|
||||
it.edges {
|
||||
it.node {
|
||||
// it.id()
|
||||
it.title()
|
||||
it.quantity()
|
||||
it.variant() {
|
||||
it.priceV2() { it.amount() }
|
||||
it.variant {
|
||||
it.priceV2 { it.amount() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ package com.tangem.tap.common.shop.shopify.data
|
|||
|
||||
import com.shopify.buy3.Storefront
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun Storefront.CollectionConnectionQuery.collectionFieldsFragment() {
|
||||
edges { collectionEdgeQuery ->
|
||||
collectionEdgeQuery
|
||||
.node { collectionQuery ->
|
||||
collectionQuery
|
||||
.title()
|
||||
.products({ arg -> arg.first(250) }
|
||||
.products(
|
||||
{ arg -> arg.first(250) },
|
||||
) { productConnectionQuery ->
|
||||
productConnectionQuery
|
||||
.edges { productEdgeQuery ->
|
||||
|
|
|
|||
|
|
@ -8,13 +8,22 @@ import java.util.regex.Pattern
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class DecimalDigitsInputFilter(
|
||||
digitsBeforeDecimal: Int,
|
||||
digitsAfterDecimal: Int,
|
||||
private val decimalSeparator: String
|
||||
digitsBeforeDecimal: Int,
|
||||
digitsAfterDecimal: Int,
|
||||
private val decimalSeparator: String,
|
||||
) : InputFilter {
|
||||
private val pattern: Pattern = Pattern.compile("(([1-9]{1}[0-9]{0,${digitsBeforeDecimal - 1}})?||[0]{1})((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?")
|
||||
@Suppress("MaxLineLength")
|
||||
private val pattern: Pattern =
|
||||
Pattern.compile("(([1-9]{1}[0-9]{0,${digitsBeforeDecimal - 1}})?||[0]{1})((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?")
|
||||
|
||||
override fun filter(source: CharSequence, sourceStart: Int, sourceEnd: Int, destination: Spanned, destinationStart: Int, destinationEnd: Int): CharSequence? {
|
||||
override fun filter(
|
||||
source: CharSequence,
|
||||
sourceStart: Int,
|
||||
sourceEnd: Int,
|
||||
destination: Spanned,
|
||||
destinationStart: Int,
|
||||
destinationEnd: Int,
|
||||
): CharSequence? {
|
||||
val destString = destination.toString()
|
||||
val prefix = destString.substring(0, destinationStart)
|
||||
val suffix = destString.substring(destinationEnd, destString.length)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.wallet.R
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
class RefreshBalanceWidget(
|
||||
private val root: ViewGroup,
|
||||
) : ViewStateWidget {
|
||||
|
|
@ -57,6 +58,7 @@ class RefreshBalanceWidget(
|
|||
viewSwitcher.showNext()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun animateState(state: ProgressState) {
|
||||
when (state) {
|
||||
ProgressState.Done, ProgressState.Error -> {
|
||||
|
|
@ -94,6 +96,7 @@ class RefreshBalanceWidget(
|
|||
private fun getState(): ProgressState = if (isArrowRefreshActive()) ProgressState.Done else ProgressState.Loading
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class ShowAnimation : AnimationSet(true) {
|
||||
init {
|
||||
addAnimation(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.common.toggleWidget
|
|||
|
||||
import android.view.View
|
||||
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -6,54 +6,50 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
|||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
class SimpleAlertDialog {
|
||||
companion object {
|
||||
fun create(
|
||||
titleRes: Int? = null,
|
||||
messageRes: Int? = null,
|
||||
title: String? = null,
|
||||
message: String? = null,
|
||||
primaryButtonRes: Int = R.string.common_ok,
|
||||
context: Context,
|
||||
): AlertDialog {
|
||||
return SimpleCancelableAlertDialog.create(
|
||||
titleRes = titleRes,
|
||||
messageRes = messageRes,
|
||||
title = title,
|
||||
message = message,
|
||||
primaryButtonRes = primaryButtonRes,
|
||||
secondaryButtonRes = null,
|
||||
context = context
|
||||
)
|
||||
}
|
||||
object SimpleAlertDialog {
|
||||
fun create(
|
||||
titleRes: Int? = null,
|
||||
messageRes: Int? = null,
|
||||
title: String? = null,
|
||||
message: String? = null,
|
||||
primaryButtonRes: Int = R.string.common_ok,
|
||||
context: Context,
|
||||
): AlertDialog {
|
||||
return SimpleCancelableAlertDialog.create(
|
||||
titleRes = titleRes,
|
||||
messageRes = messageRes,
|
||||
title = title,
|
||||
message = message,
|
||||
primaryButtonRes = primaryButtonRes,
|
||||
secondaryButtonRes = null,
|
||||
context = context,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleCancelableAlertDialog {
|
||||
companion object {
|
||||
fun create(
|
||||
titleRes: Int? = null,
|
||||
messageRes: Int? = null,
|
||||
title: String? = null,
|
||||
message: String? = null,
|
||||
primaryButtonRes: Int = R.string.common_ok,
|
||||
secondaryButtonRes: Int? = R.string.common_cancel,
|
||||
primaryButtonAction: () -> Unit = {},
|
||||
secondaryButtonAction: () -> Unit = {},
|
||||
context: Context,
|
||||
): AlertDialog {
|
||||
object SimpleCancelableAlertDialog {
|
||||
fun create(
|
||||
titleRes: Int? = null,
|
||||
messageRes: Int? = null,
|
||||
title: String? = null,
|
||||
message: String? = null,
|
||||
primaryButtonRes: Int = R.string.common_ok,
|
||||
secondaryButtonRes: Int? = R.string.common_cancel,
|
||||
primaryButtonAction: () -> Unit = {},
|
||||
secondaryButtonAction: () -> Unit = {},
|
||||
context: Context,
|
||||
): AlertDialog {
|
||||
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(titleRes?.let { context.getString(it) } ?: title )
|
||||
setMessage(messageRes?.let { context.getString(it) } ?: message)
|
||||
setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() }
|
||||
if (secondaryButtonRes != null) {
|
||||
setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction()}
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(titleRes?.let { context.getString(it) } ?: title)
|
||||
setMessage(messageRes?.let { context.getString(it) } ?: message)
|
||||
setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() }
|
||||
if (secondaryButtonRes != null) {
|
||||
setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction() }
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,11 @@ import java.util.*
|
|||
|
||||
class PayIdManager {
|
||||
|
||||
suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result<VerifyPayIdResponse> = withContext(Dispatchers.IO) {
|
||||
@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]}/"
|
||||
|
|
@ -19,28 +23,30 @@ class PayIdManager {
|
|||
|
||||
private fun Blockchain.getPayIdNetwork(): String {
|
||||
return when (this) {
|
||||
Blockchain.XRP -> "XRPL"
|
||||
Blockchain.RSK -> "RSK"
|
||||
else -> this.currency
|
||||
}.toLowerCase()
|
||||
Blockchain.XRP -> "XRPL"
|
||||
Blockchain.RSK -> "RSK"
|
||||
else -> this.currency
|
||||
}.lowercase(Locale.getDefault())
|
||||
}
|
||||
|
||||
companion object {
|
||||
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()
|
||||
@Suppress("MaxLineLength")
|
||||
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,
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.Bitcoin,
|
||||
Blockchain.Litecoin,
|
||||
Blockchain.Stellar,
|
||||
Blockchain.Cardano,
|
||||
Blockchain.CardanoShelley,
|
||||
Blockchain.Ducatus,
|
||||
Blockchain.BitcoinCash,
|
||||
Blockchain.Binance,
|
||||
Blockchain.RSK,
|
||||
Blockchain.Tezos
|
||||
Blockchain.XRP,
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.Bitcoin,
|
||||
Blockchain.Litecoin,
|
||||
Blockchain.Stellar,
|
||||
Blockchain.Cardano,
|
||||
Blockchain.CardanoShelley,
|
||||
Blockchain.Ducatus,
|
||||
Blockchain.BitcoinCash,
|
||||
Blockchain.Binance,
|
||||
Blockchain.RSK,
|
||||
Blockchain.Tezos,
|
||||
)
|
||||
|
||||
fun isPayId(value: String?): Boolean = value?.contains(payIdRegExp) ?: false
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class RatesRepository(
|
|||
}
|
||||
.onFailure { Result.Failure(it) }
|
||||
|
||||
throw IllegalStateException("Unreachable code because runCatching must return result")
|
||||
error("Unreachable code because runCatching must return result")
|
||||
}
|
||||
|
||||
private fun handleFiatRatesResult(rates: Map<Currency, Result<BigDecimal>?>): Result.Success<RatesResult> {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ 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.domain.common.CardDTO
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.operations.CommandResponse
|
||||
|
|
@ -30,7 +31,6 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
|||
import com.tangem.operations.pins.CheckUserCodesCommand
|
||||
import com.tangem.operations.pins.CheckUserCodesResponse
|
||||
import com.tangem.operations.pins.SetUserCodeCommand
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Basic
|
||||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
||||
|
|
@ -211,6 +211,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
return withContext(Dispatchers.Main) { result }
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) {
|
||||
tangemSdk.config.cardIdDisplayFormat = when {
|
||||
scanResponse == null -> CardIdDisplayFormat.Full
|
||||
|
|
|
|||
|
|
@ -17,21 +17,20 @@ interface MultiMessageError : TapErrors {
|
|||
|
||||
sealed class TapError(
|
||||
@StringRes val messageResource: Int,
|
||||
override val args: List<Any>? = null
|
||||
override val args: List<Any>? = null,
|
||||
) : Throwable(), TapErrors, ArgError {
|
||||
|
||||
object UnknownError : TapError(R.string.send_error_unknown)
|
||||
open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage))
|
||||
object ScanCardError : TapError(R.string.scan_card_error)
|
||||
object PayIdAlreadyCreated : TapError(R.string.wallet_create_payid_error_already_created)
|
||||
object PayIdCreatingError : TapError(R.string.wallet_create_payid_error_message)
|
||||
object PayIdEmptyField : TapError(R.string.wallet_create_payid_empty)
|
||||
object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle)
|
||||
object NoInternetConnection : TapError(R.string.wallet_notification_no_internet)
|
||||
object InsufficientBalance : TapError(R.string.send_error_insufficient_balance)
|
||||
object BlockchainInternalError : TapError(R.string.send_error_blockchain_internal)
|
||||
object AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance)
|
||||
data class AmountLowerExistentialDeposit(override val args: List<Any>) : TapError(R.string.send_error_minimum_balance_format)
|
||||
data class AmountLowerExistentialDeposit(
|
||||
override val args: List<Any>,
|
||||
) : TapError(R.string.send_error_minimum_balance_format)
|
||||
|
||||
object FeeExceedsBalance : TapError(R.string.send_validation_invalid_fee)
|
||||
object TotalExceedsBalance : TapError(R.string.send_validation_invalid_total)
|
||||
object InvalidAmountValue : TapError(R.string.send_validation_invalid_amount)
|
||||
|
|
@ -48,7 +47,6 @@ sealed class TapError(
|
|||
object CreationError : CustomError("Can't create wallet manager")
|
||||
class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)
|
||||
class InternalError(message: String) : CustomError(message)
|
||||
object BlockchainIsUnreachable : TapError(R.string.wallet_balance_blockchain_unreachable)
|
||||
object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ class ConfigManager {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun setupKey(configValues: ConfigValueModel?) {
|
||||
val values = configValues ?: return
|
||||
config = config.copy(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class FeatureModel(
|
|||
val isCreatingTwinCardsAllowed: Boolean,
|
||||
)
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class ConfigValueModel(
|
||||
val coinMarketCapKey: String,
|
||||
val mercuryoWidgetId: String,
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ class WarningMessagesManager {
|
|||
}
|
||||
|
||||
companion object {
|
||||
const val REMAINING_SIGNATURES_WARNING = 10
|
||||
|
||||
fun devCardWarning(): WarningMessage = WarningMessage(
|
||||
"",
|
||||
"",
|
||||
|
|
@ -171,7 +173,5 @@ class WarningMessagesManager {
|
|||
R.string.alert_demo_message,
|
||||
WarningMessage.Origin.Local,
|
||||
)
|
||||
|
||||
const val REMAINING_SIGNATURES_WARNING = 10
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import com.tangem.blockchain.common.Token
|
|||
import com.tangem.common.card.EllipticCurve
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? {
|
||||
return when (this) {
|
||||
Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(1.5) else BigDecimal.ONE
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ val CardDTO.isTangemWallet: Boolean
|
|||
&& firmwareVersion >= FirmwareVersion.MultiWalletAvailable
|
||||
&& !isSaltPay
|
||||
|
||||
@Suppress("UnnecessaryParentheses")
|
||||
fun CardDTO.hasSignedHashes(): Boolean {
|
||||
return wallets.any { (it.totalSignedHashes ?: 0) > 0 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ fun WalletManagerFactory.makeWalletManagerForApp(
|
|||
curve = wallet.curve,
|
||||
)
|
||||
}
|
||||
scanResponse.card.isHdWalletAllowedByApp && (seedKey != null && derivationParams != null) -> {
|
||||
scanResponse.card.isHdWalletAllowedByApp && seedKey != null && derivationParams != null -> {
|
||||
val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()]
|
||||
val derivationPath = when (derivationParams) {
|
||||
is DerivationParams.Default -> blockchain.derivationPath(derivationParams.style)
|
||||
|
|
@ -143,12 +143,13 @@ fun WalletManagerFactory.makeSaltPayWalletManager(
|
|||
scanResponse: ScanResponse,
|
||||
): EthereumWalletManager {
|
||||
val blockchain = scanResponse.getBlockchain()
|
||||
if (blockchain != Blockchain.SaltPay)
|
||||
throw IllegalArgumentException("WalletManager for the SaltPay can be created based only on Blockchain.SaltPay")
|
||||
if (blockchain != Blockchain.SaltPay) {
|
||||
error("WalletManager for the SaltPay can be created based only on Blockchain.SaltPay")
|
||||
}
|
||||
|
||||
val token = SaltPayWorkaround.tokenFrom(blockchain)
|
||||
val cardWallet = scanResponse.card.wallets.firstOrNull().guard {
|
||||
throw NullPointerException("SaltPay card must have one wallet at least")
|
||||
error("SaltPay card must have one wallet at least")
|
||||
}
|
||||
|
||||
return makeWalletManager(
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ private class BlockchainNetworkWalletStoreBuilderImpl(
|
|||
userWalletId = userWalletId,
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationPath = blockchainNetwork.derivationPath?.let { DerivationPath(it) },
|
||||
walletsData = (listOf(blockchainWalletData) + tokensWalletsData),
|
||||
walletsData = listOf(blockchainWalletData) + tokensWalletsData,
|
||||
walletRent = null,
|
||||
walletManager = walletManager,
|
||||
blockchainNetwork = blockchainNetwork,
|
||||
|
|
@ -79,7 +79,7 @@ private class WalletMangerWalletStoreBuilderImpl(
|
|||
userWalletId = userWalletId,
|
||||
blockchain = wallet.blockchain,
|
||||
derivationPath = wallet.publicKey.derivationPath,
|
||||
walletsData = (listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData)),
|
||||
walletsData = listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData),
|
||||
walletRent = null,
|
||||
walletManager = walletManager,
|
||||
blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager),
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ internal class AddCustomTokenErrorConverter(
|
|||
private val context: Context,
|
||||
) : ModuleMessageConverter<DomainModuleError, ConvertedMessage?> {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun convert(message: DomainModuleError): ConvertedMessage? {
|
||||
val customTokenError = (message as? AddCustomTokenError) ?: throw UnsupportedOperationException()
|
||||
val customTokenError = message as? AddCustomTokenError ?: throw UnsupportedOperationException()
|
||||
|
||||
val rawMessage = when (customTokenError) {
|
||||
AddCustomTokenError.Warning.PotentialScamToken -> R.string.custom_token_validation_error_not_found
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ internal class SaltPayActivationErrorConverter(
|
|||
) : ModuleMessageConverter<SaltPayActivationError, ConvertedDialogMessage?> {
|
||||
|
||||
override fun convert(message: SaltPayActivationError): ConvertedDialogMessage? {
|
||||
val saltPayError = (message as? SaltPayActivationError) ?: throw UnsupportedOperationException()
|
||||
val saltPayError = message as? SaltPayActivationError ?: throw UnsupportedOperationException()
|
||||
|
||||
val dialogMessage = when (saltPayError) {
|
||||
SaltPayActivationError.NoGas -> {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.backupService
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
|
|
@ -141,31 +141,34 @@ object ScanCardProcessor {
|
|||
store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer))
|
||||
|
||||
if (disclaimer.isAccepted()) {
|
||||
nextHandler((scanResponse))
|
||||
} else scope.launch {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
disclaimerWillShow()
|
||||
dispatchOnMain(
|
||||
DisclaimerAction.Show(
|
||||
fromScreen = AppScreen.Home,
|
||||
callback = DisclaimerCallback(
|
||||
onAccept = {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
nextHandler(scanResponse)
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
onProgressStateChange(false)
|
||||
onFailure(TangemSdkError.UserCancelled())
|
||||
}
|
||||
},
|
||||
nextHandler(scanResponse)
|
||||
} else {
|
||||
scope.launch {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
disclaimerWillShow()
|
||||
dispatchOnMain(
|
||||
DisclaimerAction.Show(
|
||||
fromScreen = AppScreen.Home,
|
||||
callback = DisclaimerCallback(
|
||||
onAccept = {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
nextHandler(scanResponse)
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
onProgressStateChange(false)
|
||||
onFailure(TangemSdkError.UserCancelled())
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "MagicNumber")
|
||||
private suspend inline fun onScanSuccess(
|
||||
scanResponse: ScanResponse,
|
||||
crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit,
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class BackupStepAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@FromJson
|
||||
fun fromJson(json: String): BackupStep {
|
||||
return when (json) {
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
//package com.tangem.tap.domain.tasks
|
||||
//
|
||||
//import com.tangem.common.CompletionResult
|
||||
//import com.tangem.common.core.CardSession
|
||||
//import com.tangem.common.core.CardSessionRunnable
|
||||
//import com.tangem.common.core.CompletionCallback
|
||||
//import com.tangem.common.hdWallet.DerivationPath
|
||||
//import com.tangem.common.hdWallet.ExtendedPublicKey
|
||||
//import com.tangem.operations.CommandResponse
|
||||
//import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
|
||||
//import com.tangem.tap.common.extensions.ByteArrayKey
|
||||
//
|
||||
//class ExtendedPublicKeyList(
|
||||
// items: Collection<ExtendedPublicKey>
|
||||
//): ArrayList<ExtendedPublicKey>(items), CommandResponse
|
||||
//
|
||||
//
|
||||
//class DerivationTaskResponse(
|
||||
// val entries: Map<ByteArrayKey, ExtendedPublicKeyList>
|
||||
//): CommandResponse
|
||||
//
|
||||
//class DerivationTask(
|
||||
// private val derivations: Map<ByteArrayKey, List<DerivationPath>>
|
||||
//) : CardSessionRunnable<DerivationTaskResponse> {
|
||||
//
|
||||
// val response: MutableMap<ByteArrayKey, ExtendedPublicKeyList> = mutableMapOf()
|
||||
//
|
||||
// override fun run(session: CardSession, callback: CompletionCallback<DerivationTaskResponse>) {
|
||||
// derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback)
|
||||
// }
|
||||
//
|
||||
// private fun derive(
|
||||
// keys: List<ByteArrayKey>,
|
||||
// index: Int,
|
||||
// session: CardSession,
|
||||
// callback: CompletionCallback<DerivationTaskResponse>
|
||||
// ) {
|
||||
// if (index == keys.count()) {
|
||||
// callback(CompletionResult.Success(DerivationTaskResponse(response.toMap())))
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// val key = keys[index]
|
||||
// val paths = derivations[key]!!
|
||||
// DeriveWalletPublicKeysTask(key.bytes, paths).run(session) { result ->
|
||||
// when (result) {
|
||||
// is CompletionResult.Success -> {
|
||||
// response[key] = result.data[key]
|
||||
// derive(keys = keys, index = index + 1, session = session, callback = callback)
|
||||
// }
|
||||
// is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -8,7 +8,7 @@ import com.tangem.common.extensions.guard
|
|||
import com.tangem.operations.backup.ResetBackupCommand
|
||||
import com.tangem.operations.wallet.PurgeWalletCommand
|
||||
|
||||
class ResetToFactorySettingsTask() : CardSessionRunnable<Card> {
|
||||
class ResetToFactorySettingsTask : CardSessionRunnable<Card> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
|
||||
deleteWallets(session, callback)
|
||||
|
|
|
|||
|
|
@ -170,7 +170,8 @@ private class ScanWalletProcessor(
|
|||
) {
|
||||
val activationInProgress = preferencesStorage.usedCardsPrefStorage.isActivationInProgress(card.cardId)
|
||||
|
||||
if ((card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty())
|
||||
@Suppress("ComplexCondition")
|
||||
if (card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty()
|
||||
&& (activationInProgress || card.isSaltPay)
|
||||
) {
|
||||
StartPrimaryCardLinkingTask().run(session) { linkingResult ->
|
||||
|
|
@ -266,7 +267,7 @@ private class ScanWalletProcessor(
|
|||
}
|
||||
if (additionalBlockchainsToDerive != null) {
|
||||
blockchainsToDerive.addAll(
|
||||
additionalBlockchainsToDerive.map { BlockchainNetwork(it, card) }
|
||||
additionalBlockchainsToDerive.map { BlockchainNetwork(it, card) },
|
||||
)
|
||||
}
|
||||
if (!card.useOldStyleDerivation) {
|
||||
|
|
@ -307,6 +308,7 @@ private class ScanWalletProcessor(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private class ScanTwinProcessor : ProductCommandProcessor<ScanResponse> {
|
||||
override fun proceed(
|
||||
card: CardDTO,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class LoadAvailableCoinsService(
|
|||
LoadedCoins(
|
||||
currencies = data.coins.map { Currency.fromCoinResponse(it, data.imageHost) },
|
||||
moreAvailable = data.total > offset + LOAD_PER_PAGE,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
|
|
@ -70,7 +70,7 @@ class LoadAvailableCoinsService(
|
|||
.onSuccess { return@withContext Result.Success(it) }
|
||||
.onFailure { return@withContext Result.Failure(it) }
|
||||
|
||||
throw IllegalStateException("Unreachable code because runCatching must return result")
|
||||
error("Unreachable code because runCatching must return result")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class OldUserTokensRepository(
|
|||
.onSuccess { return@map it.coins.firstOrNull()?.id }
|
||||
.onFailure { return@map null }
|
||||
|
||||
throw IllegalStateException("Unreachable code because runCatching must return result")
|
||||
error("Unreachable code because runCatching must return result")
|
||||
}
|
||||
.mapIndexedNotNull { index, id ->
|
||||
if (id == null) null else tokens[index].contractAddress to id
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class UserTokensRepository(
|
|||
return@withContext handleGetUserTokensFailure(card = card, userId = userId, error = it)
|
||||
}
|
||||
|
||||
throw IllegalStateException("Unreachable code because runCatching must return result")
|
||||
error("Unreachable code because runCatching must return result")
|
||||
}
|
||||
|
||||
// TODO("After adding DI") replace with CoroutineDispatcherProvider
|
||||
|
|
|
|||
|
|
@ -10,14 +10,13 @@ data class BlockchainDao(
|
|||
@Json(name = "key")
|
||||
val name: String,
|
||||
@Json(name = "testnet")
|
||||
val isTestNet: Boolean
|
||||
val isTestNet: Boolean,
|
||||
) {
|
||||
@Deprecated("The method is used only for migration from older versions of the app")
|
||||
fun toBlockchain(): Blockchain {
|
||||
val blockchain = Blockchain.values().find { it.name.lowercase() == name.lowercase() }
|
||||
?: throw Exception("Invalid BlockchainDao")
|
||||
return if (!isTestNet) blockchain else blockchain.getTestnetVersion()
|
||||
?: throw Exception("Invalid BlockchainDao")
|
||||
?: error("Blockchain is null")
|
||||
return if (!isTestNet) blockchain else blockchain.getTestnetVersion() ?: error("Invalid BlockchainDao")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -12,15 +12,16 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
|||
data class BlockchainNetwork(
|
||||
val blockchain: Blockchain,
|
||||
val derivationPath: String?,
|
||||
val tokens: List<Token>
|
||||
val tokens: List<Token>,
|
||||
) {
|
||||
|
||||
constructor(
|
||||
blockchain: Blockchain,
|
||||
card: CardDTO,
|
||||
) : this(
|
||||
constructor(blockchain: Blockchain, card: CardDTO) : this(
|
||||
blockchain = blockchain,
|
||||
derivationPath = if (card.settings.isHDWalletAllowed) blockchain.derivationPath(card.derivationStyle)?.rawPath else null,
|
||||
derivationPath = if (card.settings.isHDWalletAllowed) {
|
||||
blockchain.derivationPath(card.derivationStyle)?.rawPath
|
||||
} else {
|
||||
null
|
||||
},
|
||||
tokens = emptyList(),
|
||||
)
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ data class BlockchainNetwork(
|
|||
}
|
||||
|
||||
override fun hashCode(): Int = calculateHashCode(
|
||||
blockchain.hashCode(), derivationPath?.hashCode() ?: 0
|
||||
blockchain.hashCode(), derivationPath?.hashCode() ?: 0,
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
|
@ -51,7 +52,7 @@ data class BlockchainNetwork(
|
|||
return BlockchainNetwork(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
walletManager.cardTokens.toList()
|
||||
walletManager.cardTokens.toList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,13 @@ import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView
|
|||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TwinsCardWidget(
|
||||
val leapfrogWidget: LeapfrogWidget,
|
||||
private val deviceScaleFactor: Float = 1f,
|
||||
val getTopOfAnchorViewForActivateState: () -> Float
|
||||
val getTopOfAnchorViewForActivateState: () -> Float,
|
||||
) {
|
||||
|
||||
var state: TwinCardsWidgetState? = null
|
||||
|
|
@ -37,7 +36,7 @@ class TwinsCardWidget(
|
|||
val animator = createAnimator(animate)
|
||||
animator.playTogether(
|
||||
createAnimator(TwinCardNumber.First, createWelcomeProperties(TwinCardNumber.First)),
|
||||
createAnimator(TwinCardNumber.Second, createWelcomeProperties(TwinCardNumber.Second))
|
||||
createAnimator(TwinCardNumber.Second, createWelcomeProperties(TwinCardNumber.Second)),
|
||||
)
|
||||
animator.doOnEnd { onEnd() }
|
||||
leapfrogWidget.fold(animate) { animator.start() }
|
||||
|
|
@ -50,7 +49,7 @@ class TwinsCardWidget(
|
|||
val animator = createAnimator(animate)
|
||||
animator.playTogether(
|
||||
createAnimator(TwinCardNumber.First, createLeapfrogProperties(TwinCardNumber.First)),
|
||||
createAnimator(TwinCardNumber.Second, createLeapfrogProperties(TwinCardNumber.Second))
|
||||
createAnimator(TwinCardNumber.Second, createLeapfrogProperties(TwinCardNumber.Second)),
|
||||
)
|
||||
animator.doOnEnd {
|
||||
leapfrogWidget.initViews()
|
||||
|
|
@ -66,7 +65,7 @@ class TwinsCardWidget(
|
|||
val animator = createAnimator(animate)
|
||||
animator.playTogether(
|
||||
createAnimator(TwinCardNumber.First, createActivateProperties(TwinCardNumber.First)),
|
||||
createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second))
|
||||
createAnimator(TwinCardNumber.Second, createActivateProperties(TwinCardNumber.Second)),
|
||||
)
|
||||
animator.doOnEnd { onEnd() }
|
||||
animator.start()
|
||||
|
|
@ -85,6 +84,7 @@ class TwinsCardWidget(
|
|||
return animator
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun createWelcomeProperties(cardNumber: TwinCardNumber): TwinsCardProperties {
|
||||
return when (cardNumber) {
|
||||
TwinCardNumber.First -> {
|
||||
|
|
@ -118,6 +118,7 @@ class TwinsCardWidget(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun createActivateProperties(cardNumber: TwinCardNumber): TwinsCardProperties {
|
||||
val topOfAnchorView = getTopOfAnchorViewForActivateState()
|
||||
val twinProperties = TwinsCardProperties.from(getLeapViewByCardNumber(cardNumber).state)
|
||||
|
|
|
|||
|
|
@ -34,11 +34,14 @@ class WriteProtectedIssuerDataTask(
|
|||
is CompletionResult.Success -> {
|
||||
writeIssuerData(
|
||||
twinPublicKey, issuerKeys, signResult.data.signature,
|
||||
readResult.data, session, callback
|
||||
readResult.data, session, callback,
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(
|
||||
readResult.error))
|
||||
is CompletionResult.Failure -> callback(
|
||||
CompletionResult.Failure(
|
||||
readResult.error,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +50,7 @@ class WriteProtectedIssuerDataTask(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun writeIssuerData(
|
||||
twinPublicKey: ByteArray, issuerKeys: KeyPair, cardSignature: ByteArray,
|
||||
readResponse: ReadIssuerDataResponse,
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ interface UserWalletsListManager {
|
|||
/**
|
||||
* Save provided user wallet and set it as selected
|
||||
* @param userWallet [UserWallet] to save
|
||||
* @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an
|
||||
* already saved card
|
||||
* @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries
|
||||
* to save an already saved card
|
||||
* @return [CompletionResult] of operation
|
||||
* */
|
||||
*/
|
||||
suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult<Unit>
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -219,13 +219,18 @@ internal class BiometricUserWalletsListManager(
|
|||
private suspend fun loadModels(): CompletionResult<Unit> {
|
||||
return getSavedUserWallets()
|
||||
.map { userWallets ->
|
||||
if (userWallets.isNotEmpty()) state.update { prevState ->
|
||||
val wallets = (userWallets + prevState.userWallets).distinctBy { it.walletId }
|
||||
if (userWallets.isNotEmpty()) {
|
||||
state.update { prevState ->
|
||||
val wallets = (userWallets + prevState.userWallets).distinctBy { it.walletId }
|
||||
|
||||
prevState.copy(
|
||||
userWallets = wallets,
|
||||
selectedUserWalletId = findOrSetSelectedUserWalletId(prevState.selectedUserWalletId, wallets),
|
||||
)
|
||||
prevState.copy(
|
||||
userWallets = wallets,
|
||||
selectedUserWalletId = findOrSetSelectedUserWalletId(
|
||||
prevSelectedWalletId = prevState.selectedUserWalletId,
|
||||
userWallets = wallets,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ internal class BiometricUserWalletsKeysRepository(
|
|||
}
|
||||
|
||||
private suspend fun deleteUserWalletsIds(userWalletsIds: List<UserWalletId>) {
|
||||
val remainingIds = (getUserWalletsIds() - userWalletsIds.toSet())
|
||||
val remainingIds = getUserWalletsIds() - userWalletsIds.toSet()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
secureStorage.store(remainingIds.encode(), StorageKey.UserWalletIds.name)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ sealed class WalletStoresError(code: Int) : TangemError(code) {
|
|||
override val message: String?
|
||||
get() = customMessage
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class FetchFiatRatesError(
|
||||
currencies: List<String>,
|
||||
override val cause: Throwable?,
|
||||
|
|
@ -18,18 +19,22 @@ sealed class WalletStoresError(code: Int) : TangemError(code) {
|
|||
override var customMessage: String = "Failed to fetch fiat rates for currencies $currencies"
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class UnknownBlockchain : WalletStoresError(60012) {
|
||||
override var customMessage: String = "Unknown blockchain"
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
object NoInternetConnection : WalletStoresError(60013) {
|
||||
override var customMessage: String = "No internet connection"
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class WalletManagerNotCreated(blockchain: Blockchain) : WalletStoresError(60014) {
|
||||
override var customMessage: String = "Wallet manager can not be created for $blockchain"
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class UpdateWalletManagerError(
|
||||
blockchain: Blockchain,
|
||||
override val cause: Throwable,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import kotlinx.coroutines.withContext
|
|||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LargeClass")
|
||||
internal class DefaultWalletAmountsRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -59,13 +60,16 @@ internal class DefaultWalletAmountsRepository(
|
|||
userWallets: List<UserWallet>,
|
||||
fiatCurrency: FiatCurrency,
|
||||
): CompletionResult<Unit> {
|
||||
return if (userWallets.isEmpty()) CompletionResult.Success(Unit)
|
||||
else withContext(Dispatchers.Default) {
|
||||
awaitAll(
|
||||
async { fetchAmountsForUserWallets(userWallets) },
|
||||
async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) },
|
||||
)
|
||||
.fold()
|
||||
return if (userWallets.isEmpty()) {
|
||||
CompletionResult.Success(Unit)
|
||||
} else {
|
||||
withContext(Dispatchers.Default) {
|
||||
awaitAll(
|
||||
async { fetchAmountsForUserWallets(userWallets) },
|
||||
async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) },
|
||||
)
|
||||
.fold()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,16 +85,19 @@ internal class DefaultWalletAmountsRepository(
|
|||
userWallet: UserWallet,
|
||||
fiatCurrency: FiatCurrency,
|
||||
): CompletionResult<Unit> {
|
||||
return if (walletStores.isEmpty()) CompletionResult.Success(Unit)
|
||||
else withContext(Dispatchers.Default) {
|
||||
val userWalletId = userWallet.walletId
|
||||
val scanResponse = userWallet.scanResponse
|
||||
return if (walletStores.isEmpty()) {
|
||||
CompletionResult.Success(Unit)
|
||||
} else {
|
||||
withContext(Dispatchers.Default) {
|
||||
val userWalletId = userWallet.walletId
|
||||
val scanResponse = userWallet.scanResponse
|
||||
|
||||
awaitAll(
|
||||
async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) },
|
||||
async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) },
|
||||
)
|
||||
.fold()
|
||||
awaitAll(
|
||||
async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) },
|
||||
async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) },
|
||||
)
|
||||
.fold()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +146,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
return@withContext CompletionResult.Failure(error)
|
||||
}
|
||||
|
||||
throw IllegalStateException("Unreachable code because runCatching must return result")
|
||||
error("Unreachable code because runCatching must return result")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -168,7 +175,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
): CompletionResult<Unit> = coroutineScope {
|
||||
walletStores.map { walletStore ->
|
||||
async {
|
||||
// TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository]
|
||||
//TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository]
|
||||
val walletManager = walletStore.walletManager
|
||||
fetchAmountsForWalletStore(userWalletId, scanResponse, walletStore, walletManager)
|
||||
}
|
||||
|
|
@ -408,8 +415,10 @@ internal class DefaultWalletAmountsRepository(
|
|||
val error = WalletStoresError.NoInternetConnection
|
||||
Timber.e(error)
|
||||
CompletionResult.Failure(error)
|
||||
} else withContext(Dispatchers.IO) {
|
||||
catching { block() }
|
||||
} else {
|
||||
withContext(Dispatchers.IO) {
|
||||
catching { block() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ internal class DefaultWalletManagersRepository(
|
|||
return findOrMakeInternal(userWallet, blockchainNetwork)
|
||||
}
|
||||
|
||||
override suspend fun findOrMakeSingleCurrencyWalletManager(userWallet: UserWallet): CompletionResult<WalletManager> {
|
||||
override suspend fun findOrMakeSingleCurrencyWalletManager(
|
||||
userWallet: UserWallet,
|
||||
): CompletionResult<WalletManager> {
|
||||
return findOrMakeInternal(userWallet, blockchainNetwork = null)
|
||||
}
|
||||
|
||||
|
|
@ -180,8 +182,11 @@ internal class DefaultWalletManagersRepository(
|
|||
blockchain: Blockchain?,
|
||||
): WalletManager? {
|
||||
return walletManagersStorage.getAll().first()[userWalletId]?.let { userWalletManagers ->
|
||||
if (blockchain == null) userWalletManagers.firstOrNull()
|
||||
else userWalletManagers.find { it.wallet.blockchain == blockchain }
|
||||
if (blockchain == null) {
|
||||
userWalletManagers.firstOrNull()
|
||||
} else {
|
||||
userWalletManagers.find { it.wallet.blockchain == blockchain }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import kotlinx.coroutines.sync.Mutex
|
|||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
internal object WalletManagerStorage {
|
||||
private val managers =
|
||||
MutableSharedFlow<HashMap<UserWalletId, List<WalletManager>>>(replay = 1)
|
||||
private val managers = MutableSharedFlow<HashMap<UserWalletId, List<WalletManager>>>(replay = 1)
|
||||
private val mutex = Mutex()
|
||||
|
||||
init {
|
||||
managers.tryEmit(hashMapOf())
|
||||
|
|
@ -22,7 +22,6 @@ internal object WalletManagerStorage {
|
|||
return managers.asSharedFlow()
|
||||
}
|
||||
|
||||
private val mutex = Mutex()
|
||||
suspend fun update(
|
||||
f: suspend (HashMap<UserWalletId, List<WalletManager>>) -> HashMap<UserWalletId, List<WalletManager>>,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import kotlinx.coroutines.sync.Mutex
|
|||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
internal object WalletStoresStorage {
|
||||
private val stores =
|
||||
MutableSharedFlow<HashMap<UserWalletId, List<WalletStoreModel>>>(replay = 1)
|
||||
private val stores = MutableSharedFlow<HashMap<UserWalletId, List<WalletStoreModel>>>(replay = 1)
|
||||
private val mutex = Mutex()
|
||||
|
||||
init {
|
||||
stores.tryEmit(hashMapOf())
|
||||
|
|
@ -22,7 +22,6 @@ internal object WalletStoresStorage {
|
|||
return stores.asSharedFlow()
|
||||
}
|
||||
|
||||
private val mutex = Mutex()
|
||||
suspend fun update(
|
||||
f: suspend (HashMap<UserWalletId, List<WalletStoreModel>>) -> HashMap<UserWalletId, List<WalletStoreModel>>,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -12,69 +12,65 @@ import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
|
|||
import com.trustwallet.walletconnect.models.binance.tradeOrderSerializer
|
||||
import timber.log.Timber
|
||||
|
||||
class BnbHelper {
|
||||
companion object {
|
||||
object BnbHelper {
|
||||
fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer {
|
||||
val input = order.msgs.first().inputs.first()
|
||||
val output = order.msgs.first().inputs.first()
|
||||
|
||||
fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer {
|
||||
val input = order.msgs.first().inputs.first()
|
||||
val output = order.msgs.first().inputs.first()
|
||||
val currency =
|
||||
input.coins.firstOrNull()?.denom ?: Blockchain.Binance
|
||||
val amount = input.coins
|
||||
.mapNotNull { if (it.denom == currency) it.amount else null }
|
||||
.sum()
|
||||
.toBigDecimal()
|
||||
.movePointLeft(Blockchain.Binance.decimals())
|
||||
.stripZeroPlainString()
|
||||
|
||||
val currency =
|
||||
input.coins.firstOrNull()?.denom ?: Blockchain.Binance
|
||||
val amount = input.coins
|
||||
.mapNotNull { if (it.denom == currency) it.amount else null }
|
||||
.sum()
|
||||
.toBigDecimal()
|
||||
.movePointLeft(Blockchain.Binance.decimals())
|
||||
.stripZeroPlainString()
|
||||
|
||||
val gson = GsonBuilder()
|
||||
.registerTypeAdapter(tradeOrderSerializer)
|
||||
.serializeNulls()
|
||||
.create()
|
||||
|
||||
return BinanceMessageData.Transfer(
|
||||
outputAddress = output.address,
|
||||
amount = amount,
|
||||
address = input.address,
|
||||
data = gson.toJson(order).toByteArray().calculateSha256()
|
||||
)
|
||||
}
|
||||
|
||||
fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade {
|
||||
val address = order.msgs.first().sender
|
||||
|
||||
val tradeData = order.msgs.map {
|
||||
val price = it.price.toBigDecimal()
|
||||
.movePointLeft(Blockchain.Binance.decimals())
|
||||
.stripTrailingZeros()
|
||||
val quantity = it.quantity.toBigDecimal()
|
||||
.movePointLeft(Blockchain.Binance.decimals())
|
||||
.stripTrailingZeros()
|
||||
|
||||
val amount = price * quantity
|
||||
val symbol = it.symbol.substringBefore("-")
|
||||
|
||||
TradeData(
|
||||
price = "$price ${Blockchain.Binance.currency}",
|
||||
quantity = "$quantity $symbol",
|
||||
amount = "$amount ${Blockchain.Binance.currency}",
|
||||
symbol = symbol
|
||||
)
|
||||
}
|
||||
val gson = GsonBuilder()
|
||||
.registerTypeAdapter(tradeOrderSerializer)
|
||||
.serializeNulls()
|
||||
.create()
|
||||
val serialized = gson.toJson(order)
|
||||
Timber.d(serialized)
|
||||
|
||||
return BinanceMessageData.Trade(
|
||||
tradeData = tradeData,
|
||||
address = address,
|
||||
data = serialized.toByteArray().calculateSha256()
|
||||
)
|
||||
}
|
||||
val gson = GsonBuilder()
|
||||
.registerTypeAdapter(tradeOrderSerializer)
|
||||
.serializeNulls()
|
||||
.create()
|
||||
|
||||
return BinanceMessageData.Transfer(
|
||||
outputAddress = output.address,
|
||||
amount = amount,
|
||||
address = input.address,
|
||||
data = gson.toJson(order).toByteArray().calculateSha256(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade {
|
||||
val address = order.msgs.first().sender
|
||||
|
||||
val tradeData = order.msgs.map {
|
||||
val price = it.price.toBigDecimal()
|
||||
.movePointLeft(Blockchain.Binance.decimals())
|
||||
.stripTrailingZeros()
|
||||
val quantity = it.quantity.toBigDecimal()
|
||||
.movePointLeft(Blockchain.Binance.decimals())
|
||||
.stripTrailingZeros()
|
||||
|
||||
val amount = price * quantity
|
||||
val symbol = it.symbol.substringBefore("-")
|
||||
|
||||
TradeData(
|
||||
price = "$price ${Blockchain.Binance.currency}",
|
||||
quantity = "$quantity $symbol",
|
||||
amount = "$amount ${Blockchain.Binance.currency}",
|
||||
symbol = symbol,
|
||||
)
|
||||
}
|
||||
val gson = GsonBuilder()
|
||||
.registerTypeAdapter(tradeOrderSerializer)
|
||||
.serializeNulls()
|
||||
.create()
|
||||
val serialized = gson.toJson(order)
|
||||
Timber.d(serialized)
|
||||
|
||||
return BinanceMessageData.Trade(
|
||||
tradeData = tradeData,
|
||||
address = address,
|
||||
data = serialized.toByteArray().calculateSha256(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,50 +9,28 @@ import com.google.gson.JsonParser
|
|||
import com.google.gson.annotations.SerializedName
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.trustwallet.walletconnect.JSONRPC_VERSION
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
|
||||
|
||||
class EthSignHelper {
|
||||
companion object {
|
||||
private val gson: Gson by lazy {
|
||||
GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.serializeNulls()
|
||||
.create()
|
||||
}
|
||||
object EthSignHelper {
|
||||
private val gson: Gson by lazy {
|
||||
GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.serializeNulls()
|
||||
.create()
|
||||
}
|
||||
|
||||
fun parseCustomRequest(data: String): CustomJsonRpcRequest {
|
||||
return gson.fromJson<CustomJsonRpcRequest>(data)
|
||||
}
|
||||
|
||||
fun tryToParseEthTypedMessage(request: CustomJsonRpcRequest): WCEthereumSignMessage? {
|
||||
return if (request.method == WCMethodExtended.ETH_SIGN_TYPE_DATA_V4) {
|
||||
WCEthereumSignMessage(
|
||||
listOf(
|
||||
request.params[0].asString,
|
||||
request.params[1].asString,
|
||||
),
|
||||
WCEthereumSignMessage.WCSignType.TYPED_MESSAGE
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun tryToParseEthTypedMessageString(message: String): String? {
|
||||
return try {
|
||||
val messageString = message
|
||||
.replace("\\", "")
|
||||
.removePrefix("\"")
|
||||
.removeSuffix("\"")
|
||||
val messageJson = JsonParser().parse(messageString)
|
||||
val filteredMap = gson.fromJson<Map<*,*>>(messageJson)
|
||||
.filterKeys { it == "domain" || it == "message"}
|
||||
|
||||
gson.toJson(filteredMap)
|
||||
} catch (exception: Exception) {
|
||||
null
|
||||
}
|
||||
fun tryToParseEthTypedMessageString(message: String): String? {
|
||||
return try {
|
||||
val messageString = message
|
||||
.replace("\\", "")
|
||||
.removePrefix("\"")
|
||||
.removeSuffix("\"")
|
||||
val messageJson = JsonParser().parse(messageString)
|
||||
val filteredMap = gson.fromJson<Map<*, *>>(messageJson)
|
||||
.filterKeys { it == "domain" || it == "message" }
|
||||
|
||||
gson.toJson(filteredMap)
|
||||
} catch (exception: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +39,7 @@ data class CustomJsonRpcRequest(
|
|||
val id: Long,
|
||||
val jsonrpc: String = JSONRPC_VERSION,
|
||||
val method: WCMethodExtended?,
|
||||
val params: JsonArray
|
||||
val params: JsonArray,
|
||||
) {
|
||||
|
||||
fun blockchainFromChainId(): Blockchain? {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.tap.domain.walletconnect
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
|
|
@ -43,6 +43,7 @@ import java.util.*
|
|||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.set
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class WalletConnectManager {
|
||||
|
||||
private var cardId: String? = null
|
||||
|
|
@ -126,6 +127,7 @@ class WalletConnectManager {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun setupConnectionTimeoutCheck(session: WCSession) {
|
||||
scope.launch {
|
||||
delay(20_000)
|
||||
|
|
@ -345,6 +347,7 @@ class WalletConnectManager {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
fun setListeners(client: WCClient) {
|
||||
client.onSessionRequest = { id: Long, peer: WCPeerMeta ->
|
||||
Timber.d("OnSessionRequest: $peer")
|
||||
|
|
@ -490,12 +493,11 @@ class WalletConnectManager {
|
|||
}
|
||||
|
||||
companion object {
|
||||
private val tangemPeerMeta =
|
||||
WCPeerMeta(name = "Tangem Wallet", url = "https://tangem.com")
|
||||
const val WC_SCHEME = "wc"
|
||||
|
||||
private val tangemPeerMeta = WCPeerMeta(name = "Tangem Wallet", url = "https://tangem.com")
|
||||
|
||||
fun isCorrectWcUri(string: String): Boolean = WCSession.from(string) != null
|
||||
|
||||
const val WC_SCHEME = "wc"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -521,6 +523,7 @@ data class WalletConnectActiveData(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class RetryInterceptor : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request: Request = chain.request()
|
||||
|
|
|
|||
|
|
@ -3,46 +3,39 @@ package com.tangem.tap.domain.walletconnect
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.trustwallet.walletconnect.models.WCPeerMeta
|
||||
|
||||
class WalletConnectNetworkUtils {
|
||||
|
||||
companion object {
|
||||
|
||||
fun parseBlockchain(
|
||||
chainId: Int?,
|
||||
peer: WCPeerMeta,
|
||||
): Blockchain? {
|
||||
return when {
|
||||
peer.url.contains("pancakeswap.finance") -> {
|
||||
Blockchain.BSC
|
||||
}
|
||||
peer.url.contains("optimism") -> {
|
||||
Blockchain.Optimism
|
||||
}
|
||||
chainId != null -> {
|
||||
Blockchain.fromChainId(chainId)
|
||||
}
|
||||
peer.url.contains("matic.network") || peer.name == "Polygon" -> {
|
||||
Blockchain.Polygon
|
||||
}
|
||||
peer.url.contains("binance.org") || peer.name.contains("Binance") -> {
|
||||
if (peer.icons.firstOrNull()?.contains("testnet") == true) {
|
||||
Blockchain.BinanceTestnet
|
||||
} else {
|
||||
Blockchain.Binance
|
||||
}
|
||||
}
|
||||
peer.name.contains("BSC") -> {
|
||||
Blockchain.BSC
|
||||
}
|
||||
peer.url.contains("honeyswap.1hive.eth.limo") -> {
|
||||
// Check if something's changed after this bug report:
|
||||
// https://github.com/1Hive/honeyswap-interface/issues/83
|
||||
Blockchain.Gnosis
|
||||
}
|
||||
else -> {
|
||||
Blockchain.Ethereum
|
||||
object WalletConnectNetworkUtils {
|
||||
fun parseBlockchain(chainId: Int?, peer: WCPeerMeta): Blockchain? {
|
||||
return when {
|
||||
peer.url.contains("pancakeswap.finance") -> {
|
||||
Blockchain.BSC
|
||||
}
|
||||
peer.url.contains("optimism") -> {
|
||||
Blockchain.Optimism
|
||||
}
|
||||
chainId != null -> {
|
||||
Blockchain.fromChainId(chainId)
|
||||
}
|
||||
peer.url.contains("matic.network") || peer.name == "Polygon" -> {
|
||||
Blockchain.Polygon
|
||||
}
|
||||
peer.url.contains("binance.org") || peer.name.contains("Binance") -> {
|
||||
if (peer.icons.firstOrNull()?.contains("testnet") == true) {
|
||||
Blockchain.BinanceTestnet
|
||||
} else {
|
||||
Blockchain.Binance
|
||||
}
|
||||
}
|
||||
peer.name.contains("BSC") -> {
|
||||
Blockchain.BSC
|
||||
}
|
||||
peer.url.contains("honeyswap.1hive.eth.limo") -> {
|
||||
// Check if something's changed after this bug report:
|
||||
// https://github.com/1Hive/honeyswap-interface/issues/83
|
||||
Blockchain.Gnosis
|
||||
}
|
||||
else -> {
|
||||
Blockchain.Ethereum
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,9 +22,9 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
|
|
@ -48,6 +48,7 @@ import java.math.BigDecimal
|
|||
|
||||
class WalletConnectSdkHelper {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun prepareTransactionData(
|
||||
transaction: WCEthereumTransaction,
|
||||
session: WalletConnectSession,
|
||||
|
|
@ -101,7 +102,7 @@ class WalletConnectSdkHelper {
|
|||
gasAmount = fee.toFormattedString(decimals),
|
||||
totalAmount = total.toFormattedString(decimals),
|
||||
balance = balance.toFormattedString(decimals),
|
||||
isEnoughFundsToSend = (balance - total) >= BigDecimal.ZERO,
|
||||
isEnoughFundsToSend = balance - total >= BigDecimal.ZERO,
|
||||
session = session.session,
|
||||
id = id,
|
||||
type = type,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ import org.rekotlin.StoreSubscriber
|
|||
*/
|
||||
abstract class BaseStoreFragment(layoutId: Int) : BaseFragment(layoutId) {
|
||||
|
||||
abstract fun subscribeToStore()
|
||||
protected val storeSubscribersList = mutableListOf<StoreSubscriber<*>>()
|
||||
|
||||
abstract fun subscribeToStore()
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
subscribeToStore()
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ object DemoHelper {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class DemoConfig {
|
||||
|
||||
val demoBlockchains = listOf(
|
||||
|
|
@ -114,6 +115,7 @@ class DemoConfig {
|
|||
return (releaseDemoCardIds + testDemoCardIds).distinct()
|
||||
}
|
||||
|
||||
@Suppress("ClassOrdering")
|
||||
private val releaseDemoCardIds = mutableListOf(
|
||||
// === Not from the Google Sheet table ===
|
||||
"FB10000000000196", // Note BTC
|
||||
|
|
@ -476,20 +478,19 @@ class DemoConfig {
|
|||
"AB02000000058187",
|
||||
)
|
||||
|
||||
@Suppress("ClassOrdering")
|
||||
private val testDemoCardIds = listOf(
|
||||
"FB20000000000186", // Note ETH
|
||||
"FB10000000000196", // Note BTC
|
||||
"FB30000000000176", // Wallet
|
||||
)
|
||||
|
||||
@Suppress("ClassOrdering")
|
||||
private val debugTestDemoCardIds = listOf<String>(
|
||||
)
|
||||
}
|
||||
|
||||
class DemoTransactionSender(
|
||||
private val walletManager: WalletManager,
|
||||
private val sender: TransactionSender = walletManager as TransactionSender
|
||||
) : TransactionSender {
|
||||
class DemoTransactionSender(private val walletManager: WalletManager) : TransactionSender {
|
||||
|
||||
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
|
|
@ -502,6 +503,7 @@ class DemoTransactionSender(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
|
||||
val dataToSign = randomString(32).toByteArray()
|
||||
val signerResponse = signer.sign(
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ internal class DemoOnboardingNoteMiddleware : DemoMiddleware {
|
|||
currency = Currency.Blockchain(walletManager.wallet.blockchain, null),
|
||||
state = ProgressState.Done,
|
||||
error = null,
|
||||
criticalError = null
|
||||
criticalError = null,
|
||||
)
|
||||
walletManager.wallet.setAmount(balanceAmount)
|
||||
|
||||
|
|
@ -5,8 +5,8 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
|
|
@ -146,6 +146,7 @@ class DetailsMiddleware {
|
|||
}
|
||||
|
||||
class ManageSecurityMiddleware {
|
||||
@Suppress("ComplexMethod")
|
||||
fun handle(action: DetailsAction.ManageSecurity) {
|
||||
when (action) {
|
||||
is DetailsAction.ManageSecurity.OpenSecurity -> {
|
||||
|
|
|
|||
|
|
@ -15,10 +15,8 @@ import com.tangem.tap.tangemSdkManager
|
|||
import org.rekotlin.Action
|
||||
import java.util.*
|
||||
|
||||
class DetailsReducer {
|
||||
companion object {
|
||||
fun reduce(action: Action, state: AppState): DetailsState = internalReduce(action, state)
|
||||
}
|
||||
object DetailsReducer {
|
||||
fun reduce(action: Action, state: AppState): DetailsState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, state: AppState): DetailsState {
|
||||
|
|
@ -104,8 +102,7 @@ private fun prepareSecurityOptions(card: CardDTO): ManageSecurityState {
|
|||
private fun isResetToFactoryAllowedByCard(card: CardDTO): Boolean {
|
||||
val notAllowedByAnyWallet = card.wallets.any { it.settings.isPermanent }
|
||||
val notAllowedByCard = notAllowedByAnyWallet ||
|
||||
(card.isWalletDataSupported && (!card.isTangemNote && !card.settings.isBackupAllowed)) ||
|
||||
card.isSaltPay
|
||||
card.isWalletDataSupported && !card.isTangemNote && !card.settings.isBackupAllowed || card.isSaltPay
|
||||
return !notAllowedByCard
|
||||
}
|
||||
|
||||
|
|
@ -188,6 +185,7 @@ private fun prepareAllowedSecurityOptions(
|
|||
return allowedSecurityOptions
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun CardDTO.toCardInfo(): CardInfo {
|
||||
val cardId = this.cardId.chunked(4).joinToString(separator = " ")
|
||||
val issuer = this.issuer.name
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
private fun handle(state: () -> AppState?, action: Action) {
|
||||
if (DemoHelper.tryHandle(state, action)) return
|
||||
|
||||
|
|
@ -49,10 +50,8 @@ class WalletConnectMiddleware {
|
|||
walletConnectManager.restoreSessions(action.scanResponse)
|
||||
}
|
||||
is WalletConnectAction.HandleDeepLink -> {
|
||||
if (!action.wcUri.isNullOrBlank()) {
|
||||
if (WalletConnectManager.isCorrectWcUri(action.wcUri)) {
|
||||
store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
|
||||
}
|
||||
if (!action.wcUri.isNullOrBlank() && WalletConnectManager.isCorrectWcUri(action.wcUri)) {
|
||||
store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.StartWalletConnect -> {
|
||||
|
|
|
|||
|
|
@ -2,47 +2,44 @@ package com.tangem.tap.features.details.redux.walletconnect
|
|||
|
||||
import org.rekotlin.Action
|
||||
|
||||
class WalletConnectReducer {
|
||||
companion object {
|
||||
fun reduce(
|
||||
action: Action, state: WalletConnectState,
|
||||
): WalletConnectState {
|
||||
if (action !is WalletConnectAction) return state
|
||||
object WalletConnectReducer {
|
||||
fun reduce(
|
||||
action: Action, state: WalletConnectState,
|
||||
): WalletConnectState {
|
||||
if (action !is WalletConnectAction) return state
|
||||
|
||||
return when (action) {
|
||||
is WalletConnectAction.ResetState -> return WalletConnectState()
|
||||
is WalletConnectAction.ApproveSession.Success -> {
|
||||
state.copy(
|
||||
loading = false,
|
||||
sessions = state.sessions + action.session,
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.OpenSession -> {
|
||||
state.copy(loading = true)
|
||||
}
|
||||
is WalletConnectAction.SetNewSessionData -> {
|
||||
state.copy(newSessionData = action.newSession)
|
||||
}
|
||||
is WalletConnectAction.SetSessionsRestored ->
|
||||
WalletConnectState(sessions = action.sessions)
|
||||
is WalletConnectAction.RemoveSession -> {
|
||||
val sessions =
|
||||
state.sessions.filterNot { it.session.toUri() == action.session.toUri() }
|
||||
state.copy(sessions = sessions)
|
||||
}
|
||||
is WalletConnectAction.UnsupportedCard -> state.copy(loading = false)
|
||||
is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false)
|
||||
is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false)
|
||||
is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false)
|
||||
is WalletConnectAction.UpdateBlockchain -> state.copy(
|
||||
sessions = state.sessions
|
||||
.filterNot { it.peerId == action.updatedSession.peerId }
|
||||
+ action.updatedSession
|
||||
return when (action) {
|
||||
is WalletConnectAction.ResetState -> return WalletConnectState()
|
||||
is WalletConnectAction.ApproveSession.Success -> {
|
||||
state.copy(
|
||||
loading = false,
|
||||
sessions = state.sessions + action.session,
|
||||
)
|
||||
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.OpenSession -> {
|
||||
state.copy(loading = true)
|
||||
}
|
||||
is WalletConnectAction.SetNewSessionData -> {
|
||||
state.copy(newSessionData = action.newSession)
|
||||
}
|
||||
is WalletConnectAction.SetSessionsRestored ->
|
||||
WalletConnectState(sessions = action.sessions)
|
||||
is WalletConnectAction.RemoveSession -> {
|
||||
val sessions =
|
||||
state.sessions.filterNot { it.session.toUri() == action.session.toUri() }
|
||||
state.copy(sessions = sessions)
|
||||
}
|
||||
is WalletConnectAction.UnsupportedCard -> state.copy(loading = false)
|
||||
is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false)
|
||||
is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false)
|
||||
is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false)
|
||||
is WalletConnectAction.UpdateBlockchain -> state.copy(
|
||||
sessions = state.sessions
|
||||
.filterNot { it.peerId == action.updatedSession.peerId }
|
||||
+ action.updatedSession,
|
||||
)
|
||||
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ data class WalletForSession(
|
|||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = (walletPublicKey?.contentHashCode() ?: 0)
|
||||
var result = walletPublicKey?.contentHashCode() ?: 0
|
||||
result = 31 * result + (derivedPublicKey?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (derivationPath?.hashCode() ?: 0)
|
||||
result = 31 * result + isTestNet.hashCode()
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ private fun AppSettings(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun AppSettingsElement(
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ fun CardSettingsScreen(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
fun CardSettingsReadCard(
|
||||
onScanCardClick: () -> Unit,
|
||||
|
|
@ -114,6 +115,7 @@ fun CardSettingsReadCard(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
@Composable
|
||||
fun CardSettings(
|
||||
state: CardSettingsScreenState,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
package com.tangem.tap.features.details.ui.cardsettings
|
||||
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.common.getTwinCardIdForUser
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.redux.CardSettingsState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Store
|
||||
|
||||
class CardSettingsViewModel(private val store: Store<AppState>) {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue