Updated on 2026-08-14

This commit is contained in:
Tangem 2023-01-20 18:52:52 +08:00
parent e6fcb9e2fe
commit 40c37826bf
198 changed files with 791 additions and 704 deletions

View file

@ -9,8 +9,8 @@ import java.math.RoundingMode
[REDACTED_AUTHOR]
*/
class CurrencyConverter(
private val rateValue: BigDecimal,
private val decimals: Int
private val rateValue: BigDecimal,
private val decimals: Int
) {
private val roundingMode = RoundingMode.HALF_UP

View file

@ -140,7 +140,9 @@ class DialogManager : StoreSubscriber<GlobalState> {
is WalletDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create(
title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol),
message = context.getString(
state.dialog.messageRes, state.dialog.currencySymbol, state.dialog.currencyTitle,
state.dialog.messageRes,
state.dialog.currencySymbol,
state.dialog.currencyTitle,
),
context = context,
)

View file

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

View file

@ -34,8 +34,8 @@ class IntentHandler {
fun handleBackgroundScan(intent: Intent?) {
if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action ||
NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action
)
NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action
)
) {
val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
if (tag != null) {

View file

@ -14,12 +14,15 @@ object TangemSdkErrorMapper {
is TangemSdkError.SerializeCommandError -> TangemSdkError.SerializeCommandError()
is TangemSdkError.DeserializeApduFailed -> TangemSdkError.DeserializeApduFailed()
is TangemSdkError.EncodingFailedTypeMismatch -> TangemSdkError.EncodingFailedTypeMismatch(
error.customMessage)
error.customMessage
)
is TangemSdkError.EncodingFailed -> TangemSdkError.EncodingFailed(error.customMessage)
is TangemSdkError.DecodingFailedMissingTag -> TangemSdkError.DecodingFailedMissingTag(
error.customMessage)
error.customMessage
)
is TangemSdkError.DecodingFailedTypeMismatch -> TangemSdkError.DecodingFailedTypeMismatch(
error.customMessage)
error.customMessage
)
is TangemSdkError.DecodingFailed -> TangemSdkError.DecodingFailed(error.customMessage)
is TangemSdkError.InvalidResponse -> TangemSdkError.InvalidResponse()
is TangemSdkError.UnknownStatus -> TangemSdkError.UnknownStatus(error.statusWord)
@ -113,5 +116,4 @@ object TangemSdkErrorMapper {
is TangemSdkError.BiometricsAuthenticationFailed -> error
}
}
}

View file

@ -33,4 +33,4 @@ sealed class Basic(
event = "Scan",
error = error,
)
}
}

View file

@ -114,7 +114,7 @@ private fun OutlinedProgressTextField(
)
logger.log("RECOMPOSE ---------------------------------------------------------------START [${logger.count}]")
logger.log("RECOMPOSE --data: fieldData.value: [${fieldData}]")
logger.log("RECOMPOSE --data: fieldData.value: [$fieldData]")
logger.log("RECOMPOSE --data: textValueState.value: [${textValueState.value}]")
logger.log("RECOMPOSE --data: textDebouncer.emittedValue = [${textDebouncer.emittedValue}]")
logger.log("RECOMPOSE --data: textDebouncer.debounced = [${textDebouncer.debounced}]")
@ -131,17 +131,28 @@ private fun OutlinedProgressTextField(
logger.log("$isNotUserInput: пользователь ВВОДИТ данные -> внешние данные игнорируем, ждем RECOMPOSE")
} else {
logger.log("$isNotUserInput: пользователь НЕ вводит данные -> пытаемся обработать внешние данные")
if (textValueState.value != textDebouncer.emittedValue || textValueState.value != textDebouncer.debounced) {
if (textValueState.value != textDebouncer.emittedValue ||
textValueState.value != textDebouncer.debounced
) {
logger.log("$isNotUserInput: даннные в поле не соответствуют данным из textDebouncer")
if (textDebouncer.emittedValue.isEmpty() && textDebouncer.debounced.isEmpty()) {
logger.log("$isNotUserInput: даннные в textDebouncer ПУСТЫ -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]")
logger.log(
"$isNotUserInput: даннные в textDebouncer ПУСТЫ -> start RECOMPOSE новые данные для " +
"textValueState.value = [${fieldData.value}]",
)
textValueState.value = fieldData.value
} else {
logger.log("$isNotUserInput: даннные в textDebouncer НЕ ПУСТЫ -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]")
logger.log(
"$isNotUserInput: даннные в textDebouncer НЕ ПУСТЫ -> start RECOMPOSE новые данные для " +
"textValueState.value = [${fieldData.value}]",
)
textValueState.value = fieldData.value
}
} else {
logger.log("$isNotUserInput: в пустое поле вставляются данные -> start RECOMPOSE новые данные для textValueState.value = [${fieldData.value}]")
logger.log(
"$isNotUserInput: в пустое поле вставляются данные -> start RECOMPOSE новые данные для " +
"textValueState.value = [${fieldData.value}]",
)
textValueState.value = fieldData.value
}
}

View file

@ -16,7 +16,7 @@ fun TitleSubtitle(
title: String,
subtitle: String
) {
Column() {
Column {
Text(text = title)
Text(
text = subtitle,

View file

@ -49,6 +49,5 @@ fun AddCustomTokenWarning(
lineHeight = 18.sp
)
}
}
}

View file

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

View file

@ -26,11 +26,11 @@ fun Activity.sendEmail(
) {
fun createEmailShareIntent(recipient: String, subject: String, text: String, file: File? = null): Intent {
val builder = ShareCompat.IntentBuilder.from(this)
.setType("message/rfc822")
.setEmailTo(arrayOf(recipient))
.setSubject(subject)
.setText(text)
file?.let { builder.setStream(FileProvider.getUriForFile(this, "${packageName}.provider", it)) }
.setType("message/rfc822")
.setEmailTo(arrayOf(recipient))
.setSubject(subject)
.setText(text)
file?.let { builder.setStream(FileProvider.getUriForFile(this, "$packageName.provider", it)) }
return builder.intent
}
@ -41,17 +41,17 @@ fun Activity.sendEmail(
val emailFilterIntentResults = packageManager.queryIntentActivities(emailFilterIntent, 0)
val targetedIntents = originalIntentResults
.filter { originalResult ->
emailFilterIntentResults.any {
originalResult.activityInfo.packageName == it.activityInfo.packageName
}
.filter { originalResult ->
emailFilterIntentResults.any {
originalResult.activityInfo.packageName == it.activityInfo.packageName
}
.map {
createEmailShareIntent(email, subject, message, file).apply {
setPackage(it.activityInfo.packageName)
}
}
.map {
createEmailShareIntent(email, subject, message, file).apply {
setPackage(it.activityInfo.packageName)
}
.toMutableList()
}
.toMutableList()
try {
val chooserIntent = Intent.createChooser(targetedIntents.removeAt(0), "Send mail...")
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())

View file

@ -3,4 +3,4 @@ package com.tangem.tap.common.extensions
import android.content.res.AssetManager
fun AssetManager.readJsonFileToString(fileName: String): String =
this.open("$fileName.json").bufferedReader().readText()
this.open("$fileName.json").bufferedReader().readText()

View file

@ -35,8 +35,8 @@ fun Context.isPermissionGranted(permission: String): Boolean {
fun Context.resourceUri(@AnyRes resId: Int): Uri {
return Uri.parse(
ContentResolver.SCHEME_ANDROID_RESOURCE +
"://" + resources.getResourcePackageName(resId)
+ '/' + resources.getResourceTypeName(resId)
+ '/' + resources.getResourceEntryName(resId),
"://" + resources.getResourcePackageName(resId) +
'/' + resources.getResourceTypeName(resId) +
'/' + resources.getResourceEntryName(resId),
)
}

View file

@ -11,9 +11,11 @@ import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.*
//todo move extensions to utils
// 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()
@ -27,7 +29,9 @@ fun BigDecimal.toFormattedString(
@Suppress("MagicNumber")
fun BigDecimal.toFormattedCurrencyString(
decimals: Int, currency: String, roundingMode: RoundingMode = RoundingMode.DOWN,
decimals: Int,
currency: String,
roundingMode: RoundingMode = RoundingMode.DOWN,
limitNumberOfDecimals: Boolean = true,
): String {
val decimalsForRounding = if (limitNumberOfDecimals) {
@ -36,7 +40,8 @@ fun BigDecimal.toFormattedCurrencyString(
decimals
}
val formattedAmount = this.toFormattedString(
decimals = decimalsForRounding, roundingMode = roundingMode,
decimals = decimalsForRounding,
roundingMode = roundingMode,
)
return "$formattedAmount $currency"
}
@ -70,7 +75,7 @@ fun BigDecimal.toFormattedFiatValue(
): String {
val fiatValue = this.setScale(2, RoundingMode.HALF_UP)
.let { if (formatWithSpaces) it.formatWithSpaces() else it }
return "${fiatValue} $fiatCurrencyName"
return "$fiatValue $fiatCurrencyName"
}
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()

View file

@ -18,7 +18,7 @@ fun EditText.update(text: String?) {
val textLength = text?.length ?: 0
//prevent cursor jumping while editing a text
// prevent cursor jumping while editing a text
val cursorPosition = if (selectionEnd > textLength) textLength else selectionEnd
this.setText(text)
if (!isFocused || textLength == 0) return

View file

@ -86,7 +86,9 @@ fun View.invisible(invisible: Boolean = true, invokeBeforeStateChanged: (() -> U
fun Context.dpToPixels(dp: Int): Int =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics,
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
this.resources.displayMetrics,
).toInt()
fun Context.pixelsToDp(pixels: Int): Int {

View file

@ -4,5 +4,5 @@ package com.tangem.tap.common.feature
[REDACTED_AUTHOR]
*/
interface Feature {
fun featureIsSwitchedOn():Boolean
fun featureIsSwitchedOn(): Boolean
}

View file

@ -30,13 +30,11 @@ fun createCoilImageLoader(
}
)
.build()
}
}
.build()
}
private class CoilTimberLogger : Logger {
override var level: Int = Log.DEBUG

View file

@ -72,7 +72,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
store.state.globalState.warningManager?.let {
if (it.hideWarning(action.warning)) {
if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
//TODO: No appropriate warningMessage identification. Make it better later
// TODO: No appropriate warningMessage identification. Make it better later
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
}

View file

@ -11,7 +11,6 @@ import org.rekotlin.Action
@Suppress("LongMethod", "ComplexMethod")
fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolder): GlobalState {
if (action !is GlobalAction) return state.globalState
val globalState = state.globalState

View file

@ -9,7 +9,6 @@ object NavigationReducer {
}
private fun internalReduce(action: Action, state: AppState): NavigationState {
val navigationAction = action as? NavigationAction ?: return state.navigationState
val navState = state.navigationState

View file

@ -88,7 +88,8 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
fun buyWithGooglePay(productType: ProductType) {
val totalPrice = checkouts[productType]!!.totalPriceV2.amount
googlePayService.payWithGooglePay(
totalPriceCents = totalPrice, currencyCode = checkouts[productType]!!.currencyCode.name,
totalPriceCents = totalPrice,
currencyCode = checkouts[productType]!!.currencyCode.name,
merchantID = shopifyService.shop.merchantID,
)
}
@ -189,7 +190,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
appliedDiscount = it.getAppliedDiscount(),
),
)
)
}
return Result.failure(result.exceptionOrNull()!!)
}

View file

@ -25,7 +25,6 @@ class GooglePayService(private val paymentsClient: PaymentsClient, private val a
// var responseCallback: ((Result<PaymentData>) -> Unit)? = null
suspend fun checkIfGooglePayAvailable(): Result<Boolean> {
val isReadyToPayJson = GooglePayUtil.isReadyToPayRequest() ?: return Result.success(false)
val request = IsReadyToPayRequest.fromJson(isReadyToPayJson.toString())
@ -60,7 +59,9 @@ 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,
)
}

View file

@ -52,7 +52,6 @@ object GooglePayUtil {
private fun baseCardPaymentMethod(): JSONObject {
return JSONObject().apply {
val parameters = JSONObject().apply {
put("allowedAuthMethods", allowedCardAuthMethods)
put("allowedCardNetworks", allowedCardNetworks)

View file

@ -64,7 +64,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
}
suspend fun checkout(pollUntilOrder: Boolean, checkoutID: ID): Result<Checkout> {
val query = query { rootQuery: QueryRootQuery ->
rootQuery
.node(checkoutID) { query ->
@ -76,7 +75,8 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
}
}
val retryHandler = RetryHandler.build<QueryRoot>(
1, TimeUnit.SECONDS,
delay = 1,
timeUnit = TimeUnit.SECONDS,
) {
this.retryWhen { result ->
when (result) {
@ -109,7 +109,6 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
checkoutItems: List<CheckoutItem>,
checkoutID: ID? = null,
): Result<Checkout> {
val storefrontLineItems: MutableList<CheckoutLineItemInput> = checkoutItems
.map { CheckoutLineItemInput(it.quantity, it.id) }.toMutableList()
@ -117,7 +116,8 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
mutation { mutationQuery: MutationQuery ->
mutationQuery
.checkoutLineItemsReplace(
storefrontLineItems, checkoutID,
storefrontLineItems,
checkoutID,
) { payloadQuery: CheckoutLineItemsReplacePayloadQuery ->
payloadQuery
.checkout { checkoutQuery: CheckoutQuery ->
@ -159,7 +159,8 @@ 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 ->
@ -199,11 +200,11 @@ class ShopifyService(private val application: Application, val shop: ShopifyShop
payment: TokenizedPaymentInputV3,
checkoutID: ID,
): Result<Checkout> {
val query = mutation { mutationQuery: MutationQuery ->
mutationQuery
.checkoutCompleteWithTokenizedPaymentV3(
checkoutID, payment,
checkoutID,
payment,
) { payloadQuery: CheckoutCompleteWithTokenizedPaymentV3PayloadQuery ->
payloadQuery
.payment { paymentQuery: PaymentQuery ->

View file

@ -17,15 +17,15 @@ import com.tangem.wallet.R
[REDACTED_AUTHOR]
*/
class MaxAmountSnackbar(
parent: ViewGroup,
content: MaxAmountSnackbarView
parent: ViewGroup,
content: MaxAmountSnackbarView
) : BaseTransientBottomBar<MaxAmountSnackbar>(parent, content, content) {
companion object {
fun make(view: View, onClick: () -> Unit): MaxAmountSnackbar {
val parent = view.findSuitableParent() ?: throw IllegalArgumentException(
"No suitable parent found from the given view. Please provide a valid view."
"No suitable parent found from the given view. Please provide a valid view."
)
val inflater = LayoutInflater.from(view.context)
val customView = inflater.inflate(R.layout.view_snackbar_max_amount, parent, false) as MaxAmountSnackbarView
@ -57,14 +57,13 @@ class MaxAmountSnackbar(
} while (view != null)
return fallback
}
}
}
class MaxAmountSnackbarView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), ContentViewCallback {
init {
@ -72,10 +71,9 @@ class MaxAmountSnackbarView @JvmOverloads constructor(
clipToPadding = false
}
override fun animateContentIn(delay: Int, duration: Int) {
}
override fun animateContentOut(delay: Int, duration: Int) {
}
}
}

View file

@ -12,9 +12,10 @@ class DecimalDigitsInputFilter(
digitsAfterDecimal: Int,
private val decimalSeparator: String,
) : InputFilter {
@Suppress("MaxLineLength")
private val pattern: Pattern =
Pattern.compile("(([1-9]{1}[0-9]{0,${digitsBeforeDecimal - 1}})?||[0]{1})((\\$decimalSeparator[0-9]{0,$digitsAfterDecimal})?)||(\\$decimalSeparator)?")
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,
@ -56,4 +57,4 @@ class DecimalDigitsInputFilter(
}
}
}
}
}

View file

@ -16,9 +16,9 @@ interface Truncate {
companion object {
fun create(type: TruncateType): Truncate {
return when (type) {
TruncateType.START -> TruncateStart()
TruncateType.MIDDLE -> TruncateMiddle()
TruncateType.END -> TruncateEnd()
TruncateType.START -> TruncateStart()
TruncateType.MIDDLE -> TruncateMiddle()
TruncateType.END -> TruncateEnd()
}
}
}
@ -127,10 +127,10 @@ fun TextView.truncateWith(text: String, type: TruncateType, with: String = "..."
}
fun TextView.truncateStartWith(text: String, with: String = "..."): String =
this.truncateWith(text, TruncateType.START, with)
this.truncateWith(text, TruncateType.START, with)
fun TextView.truncateMiddleWith(text: String, with: String = "..."): String =
this.truncateWith(text, TruncateType.MIDDLE, with)
this.truncateWith(text, TruncateType.MIDDLE, with)
fun TextView.truncateEndWith(text: String, with: String = "..."): String =
this.truncateWith(text, TruncateType.END, with)
this.truncateWith(text, TruncateType.END, with)

View file

@ -67,9 +67,18 @@ class RefreshBalanceWidget(
}
ProgressState.Loading -> {
progressViewAnimation = RotateAnimation(
0f, 360f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f,
/* fromDegrees = */
0f,
/* toDegrees = */
360f,
/* pivotXType = */
Animation.RELATIVE_TO_SELF,
/* pivotXValue = */
0.5f,
/* pivotYType = */
Animation.RELATIVE_TO_SELF,
/* pivotYValue = */
0.5f,
)
progressViewAnimation?.duration = 700
progressViewAnimation?.interpolator = AccelerateInterpolator()
@ -101,10 +110,14 @@ class ShowAnimation : AnimationSet(true) {
init {
addAnimation(
ScaleAnimation(
0f, 1f,
0f, 1f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f,
0f,
1f,
0f,
1f,
Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f,
),
)
addAnimation(AlphaAnimation(0f, 1f))

View file

@ -1,33 +1,13 @@
package com.tangem.tap.common.transitions
import androidx.transition.*
/**
[REDACTED_AUTHOR]
*/
class FrontCardEnterTransition : TransitionSet() {
init {
ordering = ORDERING_TOGETHER;
addTransition(Fade())
addTransition(ChangeImageTransform()) // изменения внутри ImageView
addTransition(ChangeTransform()) // изменение размеров, углов накона
addTransition(ChangeBounds()) // изменение положения
}
}
class FrontCardExitTransition : TransitionSet() {
init {
ordering = ORDERING_TOGETHER;
addTransition(Fade())
addTransition(ChangeImageTransform())
addTransition(ChangeTransform())
addTransition(ChangeBounds())
}
}
import androidx.transition.ChangeBounds
import androidx.transition.ChangeTransform
import androidx.transition.Fade
import androidx.transition.TransitionSet
class HomeToOnboardingTransition : TransitionSet() {
init {
ordering = ORDERING_TOGETHER;
ordering = ORDERING_TOGETHER
addTransition(Fade())
addTransition(ChangeTransform())
addTransition(ChangeBounds())
@ -36,7 +16,7 @@ class HomeToOnboardingTransition : TransitionSet() {
class InternalNoteLayoutTransition : TransitionSet() {
init {
ordering = ORDERING_TOGETHER;
ordering = ORDERING_TOGETHER
addTransition(ChangeTransform())
addTransition(ChangeBounds())
addTransition(Fade())

View file

@ -39,7 +39,6 @@ object SimpleCancelableAlertDialog {
secondaryButtonAction: () -> Unit = {},
context: Context,
): AlertDialog {
return AlertDialog.Builder(context).apply {
setTitle(titleRes?.let { context.getString(it) } ?: title)
setMessage(messageRes?.let { context.getString(it) } ?: message)