Updated on 2026-08-14

This commit is contained in:
Tangem 2023-04-07 20:39:24 +08:00
parent 069605e151
commit 36516c900f
234 changed files with 902 additions and 1618 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -14,14 +14,14 @@ object TangemSdkErrorMapper {
is TangemSdkError.SerializeCommandError -> TangemSdkError.SerializeCommandError()
is TangemSdkError.DeserializeApduFailed -> TangemSdkError.DeserializeApduFailed()
is TangemSdkError.EncodingFailedTypeMismatch -> TangemSdkError.EncodingFailedTypeMismatch(
error.customMessage
error.customMessage,
)
is TangemSdkError.EncodingFailed -> TangemSdkError.EncodingFailed(error.customMessage)
is TangemSdkError.DecodingFailedMissingTag -> TangemSdkError.DecodingFailedMissingTag(
error.customMessage
error.customMessage,
)
is TangemSdkError.DecodingFailedTypeMismatch -> TangemSdkError.DecodingFailedTypeMismatch(
error.customMessage
error.customMessage,
)
is TangemSdkError.DecodingFailed -> TangemSdkError.DecodingFailed(error.customMessage)
is TangemSdkError.InvalidResponse -> TangemSdkError.InvalidResponse()

View file

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

View file

@ -116,10 +116,7 @@ class TopUpController(
}
}
fun send(
scanResponse: ScanResponse,
cardBalanceState: AnalyticsParam.CardBalanceState,
) {
fun send(scanResponse: ScanResponse, cardBalanceState: AnalyticsParam.CardBalanceState) {
UserWalletIdBuilder.scanResponse(scanResponse).build()?.let {
send(it, cardBalanceState, scanResponse.cardTypesResolver)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -46,9 +46,7 @@ fun BigDecimal.toFormattedCurrencyString(
return "$formattedAmount $currency"
}
fun BigDecimal.toFiatRateString(
fiatCurrencyName: String,
): String {
fun BigDecimal.toFiatRateString(fiatCurrencyName: String): String {
val value = this
.setScale(2, RoundingMode.HALF_UP)
.formatWithSpaces()
@ -69,10 +67,7 @@ fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
return fiatValue.setScale(2, RoundingMode.HALF_UP)
}
fun BigDecimal.toFormattedFiatValue(
fiatCurrencyName: String,
formatWithSpaces: Boolean = false,
): String {
fun BigDecimal.toFormattedFiatValue(fiatCurrencyName: String, formatWithSpaces: Boolean = false): String {
val fiatValue = this.setScale(2, RoundingMode.HALF_UP)
.let { if (formatWithSpaces) it.formatWithSpaces() else it }
return "$fiatValue$fiatCurrencyName"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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