Updated on 2026-08-14
This commit is contained in:
parent
e98780ff93
commit
479d142807
283 changed files with 2338 additions and 2730 deletions
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue