Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-21 15:05:17 +04:00
parent 9a8ff1830d
commit a0798cfef2
252 changed files with 247 additions and 5857 deletions

View file

@ -20,7 +20,7 @@ class GlobalLayoutStateHandler<T : View>(
if (attachImmediately) attach() if (attachImmediately) attach()
} }
fun attach() { private fun attach() {
if (isAttached) { if (isAttached) {
Timber.d("Already attached") Timber.d("Already attached")
return return

View file

@ -11,20 +11,6 @@ fun postUi(ms: Long = 0, func: Runnable) {
if (ms == 0L) uiHandler.post { func.run() } else uiHandler.postDelayed(func, ms) if (ms == 0L) uiHandler.post { func.run() } else uiHandler.postDelayed(func, ms)
} }
fun postBackground(ms: Long = 0, func: Runnable) {
if (ms == 0L) backgroundHandler.post { func.run() } else backgroundHandler.postDelayed(func, ms)
}
fun postUiDelayBg(ms: Long, func: Runnable) { fun postUiDelayBg(ms: Long, func: Runnable) {
backgroundHandler.postDelayed({ uiHandler.post(func) }, ms) backgroundHandler.postDelayed({ uiHandler.post(func) }, ms)
}
fun post(ms: Long = 0, func: Runnable) {
if (ms == 0L) {
func.run()
} else {
val currentLooper = Looper.myLooper() ?: return
Handler(currentLooper).postDelayed(func, ms)
}
} }

View file

@ -1,34 +0,0 @@
package com.tangem.tap.common
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
/**
[REDACTED_AUTHOR]
*/
open class ShimmerRecyclerAdapter(
private val viewHolderViewFactory: (ViewGroup) -> ViewGroup,
) : ListAdapter<ShimmerData, ShimmerVH>(DiffUtilCallback) {
override fun getItemId(position: Int): Long {
return if (currentList.isEmpty()) 0 else currentList[position].hashCode().toLong()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShimmerVH {
return ShimmerVH(viewHolderViewFactory.invoke(parent))
}
override fun onBindViewHolder(holder: ShimmerVH, position: Int) {}
object DiffUtilCallback : DiffUtil.ItemCallback<ShimmerData>() {
override fun areContentsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem
override fun areItemsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem
}
}
class ShimmerVH(viewGroup: ViewGroup) : RecyclerView.ViewHolder(viewGroup)
@Suppress("UnusedPrivateMember")
data class ShimmerData(private val any: String = "")

View file

@ -8,11 +8,8 @@ import androidx.appcompat.widget.LinearLayoutCompat
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.wallet.BuildConfig
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
@ -22,18 +19,6 @@ object TestActions {
// It used only for the test actions in debug or debug_beta builds // It used only for the test actions in debug or debug_beta builds
var testAmountInjectionForWalletManagerEnabled = false var testAmountInjectionForWalletManagerEnabled = false
/**
* @param isTestView - true must be used if you want to show or hide your view depends on BuildConfig
*/
fun initFor(view: View, actions: List<TestAction>, isTestView: Boolean = false) {
if (!BuildConfig.TEST_ACTION_ENABLED) return
if (isTestView) view.show(BuildConfig.TEST_ACTION_ENABLED)
view.setOnClickListener {
store.dispatchDialogShow(AppDialog.TestActionsDialog(actions))
}
}
} }
typealias TestAction = Pair<String, () -> Unit> typealias TestAction = Pair<String, () -> Unit>

View file

@ -22,10 +22,6 @@ class AnalyticsFactory {
filters.add(filter) filters.add(filter)
} }
fun addParamsInterceptor(interceptor: ParamsInterceptor) {
interceptors.add(interceptor)
}
fun build(analytics: Analytics, data: AnalyticsHandlerBuilder.Data) { fun build(analytics: Analytics, data: AnalyticsHandlerBuilder.Data) {
builders.mapNotNull { it.build(data) }.forEach { analytics.addHandler(it.id(), it) } builders.mapNotNull { it.build(data) }.forEach { analytics.addHandler(it.id(), it) }
filters.forEach { analytics.addFilter(it) } filters.forEach { analytics.addFilter(it) }

View file

@ -1,10 +1,10 @@
package com.tangem.tap.common.analytics.converters package com.tangem.tap.common.analytics.converters
import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.Converter
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.features.demo.DemoTransactionSender import com.tangem.tap.features.demo.DemoTransactionSender
import com.tangem.utils.converter.Converter
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]

View file

@ -1,9 +1,9 @@
package com.tangem.tap.common.analytics.converters package com.tangem.tap.common.analytics.converters
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.common.Converter
import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.CardTypesResolver
import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.utils.converter.Converter
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]

View file

@ -1,9 +1,9 @@
package com.tangem.tap.common.analytics.converters package com.tangem.tap.common.analytics.converters
import com.shopify.buy3.Storefront import com.shopify.buy3.Storefront
import com.tangem.common.Converter
import com.tangem.tap.common.analytics.events.Shop import com.tangem.tap.common.analytics.events.Shop
import com.tangem.tap.features.shop.domain.models.ProductType import com.tangem.tap.features.shop.domain.models.ProductType
import com.tangem.utils.converter.Converter
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]

View file

@ -1,19 +0,0 @@
package com.tangem.tap.common.analytics.converters
import com.tangem.common.Converter
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.common.analytics.events.Basic
/**
[REDACTED_AUTHOR]
*/
class TopUpEventConverter : Converter<Pair<UserWalletId, CardTypesResolver>, Basic.ToppedUp?> {
override fun convert(value: Pair<UserWalletId, CardTypesResolver>): Basic.ToppedUp? {
val (userWalletId, resolver) = value
val paramCardCurrency = ParamCardCurrencyConverter().convert(resolver) ?: return null
return Basic.ToppedUp(userWalletId, paramCardCurrency)
}
}

View file

@ -1,16 +1,14 @@
package com.tangem.tap.common.analytics.events package com.tangem.tap.common.analytics.events
import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.details.redux.SecurityOption
sealed class AnalyticsParam { sealed class AnalyticsParam {
sealed class CurrencyType(val value: String) { sealed class CurrencyType(val value: String) {
class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol) class Currency(currency: com.tangem.tap.domain.model.Currency) : CurrencyType(currency.currencySymbol)
class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency) class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency)
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol) class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
class FiatCurrency(fiatCurrency: AppCurrency) : CurrencyType(fiatCurrency.code)
class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol) class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
} }
@ -23,15 +21,11 @@ sealed class AnalyticsParam {
sealed class CardBalanceState(val value: String) { sealed class CardBalanceState(val value: String) {
object Empty : CardBalanceState("Empty") object Empty : CardBalanceState("Empty")
object Full : CardBalanceState("Full") object Full : CardBalanceState("Full")
object CustomToken : CardBalanceState("Custom Token")
object BlockchainError : CardBalanceState("Blockchain Error")
object NoRate : CardBalanceState("No Rate")
companion object companion object
} }
sealed class RateApp(val value: String) { sealed class RateApp(val value: String) {
object Liked : RateApp("Liked") object Liked : RateApp("Liked")
object Disliked : RateApp("Disliked")
object Closed : RateApp("Close") object Closed : RateApp("Close")
} }
@ -42,7 +36,6 @@ sealed class AnalyticsParam {
sealed class UserCode(val value: String) { sealed class UserCode(val value: String) {
object AccessCode : UserCode("Access Code") object AccessCode : UserCode("Access Code")
object Passcode : UserCode("Passcode")
} }
sealed class SecurityMode(val value: String) { sealed class SecurityMode(val value: String) {

View file

@ -13,13 +13,6 @@ sealed class Basic(
error: Throwable? = null, error: Throwable? = null,
) : AnalyticsEvent("Basic", event, params, error) { ) : AnalyticsEvent("Basic", event, params, error) {
class BalanceLoaded(balance: AnalyticsParam.CardBalanceState) : Basic(
event = "Balance Loaded",
params = mapOf(
AnalyticsParam.BALANCE to balance.value,
),
)
class CardWasScanned( class CardWasScanned(
source: AnalyticsParam.ScannedFrom, source: AnalyticsParam.ScannedFrom,
) : Basic( ) : Basic(

View file

@ -12,23 +12,14 @@ sealed class MainScreen(
class ScreenOpened : MainScreen("Screen opened") class ScreenOpened : MainScreen("Screen opened")
class ButtonScanCard : MainScreen("Button - Scan Card") class ButtonScanCard : MainScreen("Button - Scan Card")
class ButtonMyWallets : MainScreen("Button - My Wallets")
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen( class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen(
event = "Enable Biometric", event = "Enable Biometric",
params = mapOf("State" to state.value), params = mapOf("State" to state.value),
) )
class MainCurrencyChanged(currencyType: AnalyticsParam.CurrencyType) : MainScreen(
event = "Main Currency Changed",
params = mapOf("Currency Type" to currencyType.value),
)
class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen( class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen(
event = "Notice - Rate The App Button Tapped", event = "Notice - Rate The App Button Tapped",
params = mapOf("Result" to result.value), params = mapOf("Result" to result.value),
) )
class NoticeBackupYourWalletTapped : MainScreen("Notice - Backup Your Wallet Tapped")
class NoticeScanYourCardTapped : MainScreen("Notice - Scan Your Card Tapped")
} }

View file

@ -1,19 +0,0 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.models.AnalyticsEvent
sealed class MyWallets(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("My Wallets", event, params) {
class MyWalletsScreenOpened : MyWallets(event = "My Wallets Screen Opened")
sealed class Button {
class ScanNewCard : MyWallets(event = "Button - Scan New Card")
class UnlockWithBiometrics : MyWallets(event = "Button - Unlock all with Face ID")
class EditWalletTapped : MyWallets(event = "Button - Edit Wallet Tapped")
class DeleteWalletTapped : MyWallets(event = "Button - Delete Wallet Tapped")
class WalletUnlockTapped : MyWallets(event = "Button - Wallet Unlock Tapped")
}
}

View file

@ -1,21 +0,0 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.models.AnalyticsEvent
/**
[REDACTED_AUTHOR]
*/
sealed class Portfolio(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Portfolio", event, params) {
class Refreshed : Portfolio("Refreshed")
class ButtonManageTokens : Portfolio("Button - Manage Tokens")
class TokenTapped : Portfolio("Token is Tapped")
class OrganizeTokens : Portfolio("Button - Organize Tokens")
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.common.analytics.events package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.common.analytics.events.AnalyticsParam.CurrencyType
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
@ -13,50 +12,6 @@ sealed class Token(
error: Throwable? = null, error: Throwable? = null,
) : AnalyticsEvent(category, event, params, error) { ) : AnalyticsEvent(category, event, params, error) {
class Refreshed : Token("Token", "Refreshed")
class ButtonExplore : Token("Token", "Button - Explore")
class ButtonRemoveToken(type: CurrencyType) : Token(
"Token",
"Button - Remove Token",
params = mapOf("Token" to type.value),
)
class ButtonBuy(type: CurrencyType) : Token(
category = "Token",
event = "Button - Buy",
params = mapOf("Token" to type.value),
)
class ButtonSell(type: CurrencyType) : Token(
category = "Token",
event = "Button - Sell",
params = mapOf("Token" to type.value),
)
class ButtonExchange(type: CurrencyType) : Token(
category = "Token",
event = "Button - Exchange",
params = mapOf("Token" to type.value),
)
class ButtonSend(type: CurrencyType) : Token(
category = "Token",
event = "Button - Send",
params = mapOf("Token" to type.value),
)
class Bought(type: CurrencyType) : Token(
category = "Token",
event = "Token Bought",
params = mapOf("Token" to type.value),
)
object ShowWalletAddress : Token(
category = "Token",
event = "Button - Show the Wallet Address",
)
sealed class Receive( sealed class Receive(
event: String, event: String,
params: Map<String, String> = mapOf(), params: Map<String, String> = mapOf(),

View file

@ -3,8 +3,8 @@ package com.tangem.tap.common.analytics.handlers.amplitude
import android.app.Application import android.app.Application
import com.amplitude.api.Amplitude import com.amplitude.api.Amplitude
import com.amplitude.api.AmplitudeClient import com.amplitude.api.AmplitudeClient
import com.tangem.common.Converter
import com.tangem.core.analytics.api.EventLogger import com.tangem.core.analytics.api.EventLogger
import com.tangem.utils.converter.Converter
import org.json.JSONObject import org.json.JSONObject
/** /**

View file

@ -2,7 +2,6 @@ package com.tangem.tap.common.analytics.handlers.firebase
import android.os.Bundle import android.os.Bundle
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase import com.google.firebase.ktx.Firebase
@ -29,8 +28,4 @@ internal class FirebaseClient : FirebaseAnalyticsClient {
} }
private fun Map<String, String>.toBundle(): Bundle = bundleOf(*this.toList().toTypedArray()) private fun Map<String, String>.toBundle(): Bundle = bundleOf(*this.toList().toTypedArray())
companion object {
const val ORDER_EVENT = FirebaseAnalytics.Event.PURCHASE
}
} }

View file

@ -1,67 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
@Suppress("MagicNumber")
@Composable
fun TextAutoSize(
text: String,
fontSizeRange: FontSizeRange,
modifier: Modifier = Modifier,
textStyle: TextStyle = LocalTextStyle.current,
) {
val fontSizeValue = remember { mutableStateOf(fontSizeRange.max.value) }
val readyToDraw = remember { mutableStateOf(false) }
val textState = remember { mutableStateOf(text) }
if (textState.value != text) {
readyToDraw.value = false
fontSizeValue.value = fontSizeRange.max.value
textState.value = text
}
Text(
modifier = modifier.drawWithContent { if (readyToDraw.value) drawContent() },
text = text,
softWrap = false,
style = textStyle,
fontSize = fontSizeValue.value.sp,
onTextLayout = {
if (it.hasVisualOverflow) {
val nextFontSizeValue = fontSizeValue.value - fontSizeRange.step.value
if (nextFontSizeValue <= fontSizeRange.min.value) {
fontSizeValue.value = fontSizeRange.min.value
readyToDraw.value = true
} else {
fontSizeValue.value = nextFontSizeValue * 0.8f
}
} else {
readyToDraw.value = true
}
},
)
}
data class FontSizeRange(
val min: TextUnit,
val max: TextUnit,
val step: TextUnit = DEFAULT_TEXT_STEP,
) {
init {
require(min < max) { "min should be less than max, $this" }
require(step.value > 0) { "step should be greater than 0, $this" }
}
companion object {
private val DEFAULT_TEXT_STEP = 1.sp
}
}

View file

@ -1,14 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
/**
* Used for disable ripple if button is enable = false
*/
@Composable
fun ToggledRippleTheme(isEnabled: Boolean, content: @Composable () -> Unit) {
val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme()
CompositionLocalProvider(theme) { content() }
}

View file

@ -1,47 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.tangem.domain.common.util.ValueDebouncer
/**
[REDACTED_AUTHOR]
* This is an empty compose view. It just remember the ValueDebouncer inside of itself.
*/
@Composable
fun <T> valueDebouncerAsState(
initialValue: T,
onValueChange: (T) -> Unit,
debounce: Long = 600,
onEmitValueReceive: (T) -> Unit = {},
): ValueDebouncer<T> {
return remember {
ValueDebouncer(
initialValue = initialValue,
debounceDuration = debounce,
onEmitValueReceived = { emitValue ->
emitValue?.let { onEmitValueReceive(it) }
},
onValueChanged = { changedValue ->
changedValue?.let { onValueChange(it) }
},
)
}
}
@Composable
fun <T> valueDebouncerNullableAsState(
initialValue: T?,
onValueChange: (T?) -> Unit,
debounce: Long = 400,
onEmitValueReceive: (T?) -> Unit = {},
): ValueDebouncer<T?> {
return remember {
ValueDebouncer(
initialValue = initialValue,
debounceDuration = debounce,
onEmitValueReceived = onEmitValueReceive,
onValueChanged = onValueChange,
)
}
}

View file

@ -1,33 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
/**
[REDACTED_AUTHOR]
*/
@Composable
fun ErrorView(text: String, modifier: Modifier = Modifier, style: TextStyle = LocalTextStyle.current) {
Text(
text,
color = MaterialTheme.colors.error,
modifier = modifier,
style = style,
)
}
@Preview
@Composable
private fun ErrorViewTest() {
Box(Modifier.padding(16.dp)) {
ErrorView(text = "Some error description")
}
}

View file

@ -1,17 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material.ripple.RippleTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
/**
[REDACTED_AUTHOR]
*/
class NoRippleTheme : RippleTheme {
@Composable
override fun defaultColor() = Color.Unspecified
@Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(0.0f, 0.0f, 0.0f, 0.0f)
}

View file

@ -1,165 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.text.isDigitsOnly
/**
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun PinCodeWidget(
config: PinViewConfig = tangemPinConfig,
onPinChange: (String, Boolean) -> Unit = { pin, isLastSymbolEntered -> },
) {
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
val rTextFieldValue = remember { mutableStateOf(TextFieldValue("")) }
val indexedSymbols: List<String?> = createPinSymbolsList(config.pinsCount, rTextFieldValue.value.text)
fun isLastSymbolEntered(): Boolean = rTextFieldValue.value.text.length == config.pinsCount
fun handleOnTextFieldValueChanged(value: TextFieldValue) {
if (!value.text.isDigitsOnly()) return
if (value.text.length <= config.pinsCount) {
rTextFieldValue.value = value
onPinChange(value.text, isLastSymbolEntered())
}
}
Box(
modifier = config.modifier
.pointerInput(Unit) {
detectTapGestures {
focusRequester.requestFocus()
keyboardController?.show()
}
},
) {
Row {
for (index in 0 until config.pinsCount) {
PinElement(
config = config,
pinSymbol = indexedSymbols[index] ?: "",
)
}
}
TextField(
modifier = Modifier
.alpha(0f)
.size(1.dp)
.align(Alignment.Center)
.focusRequester(focusRequester),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Decimal,
imeAction = if (isLastSymbolEntered()) ImeAction.Done else ImeAction.Next,
),
keyboardActions = KeyboardActions(
onDone = { keyboardController?.hide() },
),
value = rTextFieldValue.value,
onValueChange = ::handleOnTextFieldValueChanged,
)
}
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
@Composable
private fun PinElement(config: PinViewConfig, pinSymbol: String) {
Box(Modifier.padding(config.pinBoxPadding)) {
Box(config.pinBoxModifier) {
Text(
text = pinSymbol,
modifier = config.pinTextModifier.align(Alignment.Center),
style = config.pinsTextStyle ?: LocalTextStyle.current,
)
}
}
}
private fun createPinSymbolsList(size: Int, text: String): List<String?> = List(size) {
try {
text[it].toString()
} catch (ex: IndexOutOfBoundsException) {
null
}
}
data class PinViewConfig(
val modifier: Modifier = Modifier,
val pinBoxModifier: Modifier = Modifier,
val pinBoxPadding: Dp = 0.dp,
val pinTextModifier: Modifier = Modifier,
val pinsCount: Int = 4,
val pinsTextStyle: TextStyle? = null,
)
private val tangemPinConfig = PinViewConfig(
modifier = Modifier
.wrapContentSize(),
pinBoxModifier = Modifier
.width(42.dp)
.height(56.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color(0xFFF0F0F0)),
pinBoxPadding = 6.dp,
pinTextModifier = Modifier,
pinsCount = 4,
pinsTextStyle = TextStyle(
fontWeight = FontWeight(500),
fontSize = 24.sp,
),
)
@Preview
@Composable
private fun PinCodeWidgetPreview() {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
PinCodeWidget(tangemPinConfig)
}
}

View file

@ -1,49 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.common.module.ModuleMessage
import com.tangem.core.ui.components.SpacerH8
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
fun AddCustomTokenWarning(warning: ModuleMessage, converter: ModuleMessageConverter, modifier: Modifier = Modifier) {
Surface(
modifier = modifier,
shape = MaterialTheme.shapes.small,
color = colorResource(id = R.color.warning_warning),
elevation = 4.dp,
) {
Column(
modifier = Modifier.padding(16.dp),
) {
Text(
text = stringResource(id = R.string.common_warning),
color = colorResource(id = R.color.white),
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
)
SpacerH8()
Text(
text = converter.convert(warning).message,
color = colorResource(id = R.color.white),
fontSize = 13.sp,
lineHeight = 18.sp,
)
}
}
}

View file

@ -1,21 +0,0 @@
package com.tangem.tap.common.compose.extensions
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.getFromClipboard
/**
[REDACTED_AUTHOR]
*/
@Suppress("ComposableFunctionName")
@Composable
fun copyToClipboard(value: Any, label: String = "") {
LocalContext.current.copyToClipboard(value, label)
}
@Suppress("ComposableFunctionName")
@Composable
fun getFromClipboard(default: CharSequence? = null): CharSequence? {
return LocalContext.current.getFromClipboard(default)
}

View file

@ -14,6 +14,4 @@ fun Dp.toPx(): Float {
return with(LocalDensity.current) { currentDp.toPx() } return with(LocalDensity.current) { currentDp.toPx() }
} }
fun DpSize.halfWidth(): Dp = this.width / 2
fun DpSize.halfHeight(): Dp = this.height / 2 fun DpSize.halfHeight(): Dp = this.height / 2

View file

@ -1,33 +0,0 @@
package com.tangem.tap.common.compose.extensions
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalView
import com.tangem.tap.common.extensions.hideKeyboard
@Composable
fun LazyListState.OnBottomReached(loadMoreThreshold: Int, onLoadMore: () -> Unit) {
require(loadMoreThreshold >= 0)
val shouldLoadMore by remember {
derivedStateOf {
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()
?: return@derivedStateOf false
lastVisibleItem.index >= layoutInfo.totalItemsCount - 1 - loadMoreThreshold
}
}
LaunchedEffect(shouldLoadMore) {
if (shouldLoadMore) onLoadMore()
}
}
@Composable
fun LazyListState.HideKeyboardOnScroll() {
if (isScrollInProgress) {
LocalView.current.hideKeyboard()
}
}

View file

@ -1,14 +0,0 @@
package com.tangem.tap.common.compose.extensions
import androidx.compose.runtime.MutableState
/**
[REDACTED_AUTHOR]
*/
fun <T> MutableState<List<T>>.addAndNotify(value: T) {
this.value = this.value.toMutableList().apply { add(value) }
}
fun <T> MutableState<List<T>>.removeAndNotify(value: T) {
this.value = this.value.toMutableList().apply { remove(value) }
}

View file

@ -5,7 +5,6 @@ import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.tangem.sdk.extensions.dpToPx
import com.tangem.sdk.extensions.pxToDp import com.tangem.sdk.extensions.pxToDp
/** /**
@ -17,8 +16,5 @@ fun Painter.dpSize(): DpSize = DpSize(
intrinsicSize.height.pxToDp().dp, intrinsicSize.height.pxToDp().dp,
) )
@Composable
private fun Float.dpToPx(): Float = LocalContext.current.dpToPx(this)
@Composable @Composable
private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this) private fun Float.pxToDp(): Float = LocalContext.current.pxToDp(this)

View file

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

View file

@ -1,7 +1,6 @@
package com.tangem.tap.common.extensions package com.tangem.tap.common.extensions
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@Suppress("MagicNumber") @Suppress("MagicNumber")
@ -9,9 +8,4 @@ fun Bitmap.toByteArray(): ByteArray {
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
this.compress(Bitmap.CompressFormat.JPEG, 20, stream) this.compress(Bitmap.CompressFormat.JPEG, 20, stream)
return stream.toByteArray() return stream.toByteArray()
}
@Suppress("MagicNumber")
fun ByteArray.toBitmap(): Bitmap {
return BitmapFactory.decodeByteArray(this, 0, this.size)
} }

View file

@ -7,10 +7,6 @@ import android.net.*
import androidx.annotation.* import androidx.annotation.*
import androidx.core.content.* import androidx.core.content.*
fun Context.isPermissionGranted(permission: String): Boolean {
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}
/** /**
* Get uri to any resource type via given Resource Instance * Get uri to any resource type via given Resource Instance
* @param resId - resource id * @param resId - resource id

View file

@ -1,10 +0,0 @@
package com.tangem.tap.common.extensions
import coil.request.ImageRequest
/**
[REDACTED_AUTHOR]
*/
fun ImageRequest.Builder.cardImageData(any: Any?): ImageRequest.Builder = apply {
data(any)
}

View file

@ -1,11 +0,0 @@
package com.tangem.tap.common.extensions
import android.widget.ImageView
import androidx.annotation.DrawableRes
/**
[REDACTED_AUTHOR]
*/
fun ImageView.setDrawable(@DrawableRes resId: Int) {
setImageDrawable(context.getDrawableCompat(resId))
}

View file

@ -1,17 +1,11 @@
package com.tangem.tap.common.extensions package com.tangem.tap.common.extensions
import android.text.Spanned
import android.text.SpannedString
import android.text.style.RelativeSizeSpan
import androidx.core.text.buildSpannedString
import com.tangem.common.extensions.isZero import com.tangem.common.extensions.isZero
import timber.log.Timber
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode import java.math.RoundingMode
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.DecimalFormatSymbols import java.text.DecimalFormatSymbols
import java.text.NumberFormat import java.util.Locale
import java.util.*
// TODO: move extensions to utils // TODO: move extensions to utils
fun BigDecimal.toFormattedString( fun BigDecimal.toFormattedString(
@ -29,105 +23,6 @@ fun BigDecimal.toFormattedString(
return df.format(this) return df.format(this)
} }
/**
* To formatted crypto currency string
* Specific method because there is no crypto currency codes in Locale
*/
@Suppress("MagicNumber")
fun BigDecimal.toFormattedCryptoCurrencyString(
decimals: Int,
currency: String,
roundingMode: RoundingMode = RoundingMode.DOWN,
limitNumberOfDecimals: Boolean = true,
): String {
val decimalsForRounding = if (limitNumberOfDecimals) {
if (decimals > 8) 8 else decimals
} else {
decimals
}
try {
val locale = Locale.getDefault()
val formatter = NumberFormat.getCurrencyInstance(locale)
// first create currency instance for "USD" with Locale default to replace currency to crypto later
Currency.getInstance("USD")?.let { currencyTmp ->
formatter.currency = currencyTmp
formatter.maximumFractionDigits = decimalsForRounding
formatter.minimumFractionDigits = 0
formatter.isGroupingUsed = true
formatter.roundingMode = roundingMode
// cause formatter created for USD, replace Currency with Crypto symbol on right by Locale place
return formatter.format(this).replace(currencyTmp.getSymbol(locale), "$currency")
}
} catch (e: IllegalArgumentException) {
Timber.e(e, "can't parse currency")
}
// if something went wrong - use old way to format
val formattedAmount = this.toFormattedString(
decimals = decimalsForRounding,
roundingMode = roundingMode,
locale = Locale.getDefault(),
)
return "$formattedAmount $currency"
}
fun BigDecimal.toFiatRateString(fiatCurrencyName: String, fiatCode: String): String {
try {
val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault())
Currency.getInstance(fiatCode)?.let { currency ->
formatter.currency = currency
formatter.maximumFractionDigits = 2
formatter.roundingMode = RoundingMode.HALF_UP
return formatter.format(this).replace(currency.symbol, "$fiatCurrencyName")
}
} catch (e: IllegalArgumentException) {
Timber.e(e, "can't parse currency")
}
val value = this
.setScale(2, RoundingMode.HALF_UP)
.formatWithSpaces()
return "$value $fiatCurrencyName"
}
fun BigDecimal.toFiatString(
rateValue: BigDecimal,
fiatCurrencyName: String,
fiatCode: String,
formatWithSpaces: Boolean = false,
): String {
val fiatValue = rateValue.multiply(this)
return fiatValue.toFormattedFiatValue(
fiatCurrencyName = fiatCurrencyName,
fiatCode = fiatCode,
formatWithSpaces = formatWithSpaces,
)
}
fun BigDecimal.toFiatValue(rateValue: BigDecimal): BigDecimal {
val fiatValue = rateValue.multiply(this)
return fiatValue.setScale(2, RoundingMode.HALF_UP)
}
fun BigDecimal.toFormattedFiatValue(
fiatCurrencyName: String,
fiatCode: String,
formatWithSpaces: Boolean = false,
): String {
try {
val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault())
Currency.getInstance(fiatCode)?.let { currency ->
formatter.currency = currency
formatter.maximumFractionDigits = 2
formatter.roundingMode = RoundingMode.HALF_UP
return formatter.format(this).replace(currency.symbol, "$fiatCurrencyName")
}
} catch (e: IllegalArgumentException) {
Timber.e(e, "can't parse currency")
}
val fiatValue = this.setScale(2, RoundingMode.HALF_UP)
.let { if (formatWithSpaces) it.formatWithSpaces() else it }
return "$fiatValue$fiatCurrencyName"
}
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString() fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
// 0.00 -> 0.00 // 0.00 -> 0.00
@ -146,70 +41,4 @@ fun BigDecimal.setPrecision(precision: Int, roundingMode: RoundingMode = Roundin
return this.setScale(scale() - precision() + precision, roundingMode) return this.setScale(scale() - precision() + precision, roundingMode)
} }
fun BigDecimal.isPositive(): Boolean = this.compareTo(BigDecimal.ZERO) == 1 fun BigDecimal.isPositive(): Boolean = this.signum() == 1
fun BigDecimal.isNegative(): Boolean = this.compareTo(BigDecimal.ZERO) == -1
fun BigDecimal.isGreaterThan(value: BigDecimal): Boolean = this.compareTo(value) == 1
fun BigDecimal.isLessThan(value: BigDecimal): Boolean = this.compareTo(value) == -1
fun BigDecimal.isGreaterThanOrEqual(value: BigDecimal): Boolean {
val compareResult = this.compareTo(value)
return compareResult == 1 || compareResult == 0
}
fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean {
val compareResult = this.compareTo(value)
return compareResult == -1 || compareResult == 0
}
fun BigDecimal.formatAmountAsSpannedString(
currencySymbol: String,
reminderPartSizeProportion: Float = 0.7f,
): SpannedString {
val amount = this
.setScale(2, RoundingMode.HALF_UP)
.formatWithSpaces()
val integer = amount.substringBefore('.')
val reminder = amount.substringAfter('.')
// test formatter log Log.e("TEST ", BigDecimal("1234567890987654321.1234567890987654321").formatWithSpaces())
return buildSpannedString {
append(integer)
append('.')
append(
"$reminder $currencySymbol",
RelativeSizeSpan(reminderPartSizeProportion),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
)
}
}
@Suppress("MagicNumber")
fun BigDecimal.formatWithSpaces(): String {
val str = this.toString()
var integerStr = str.substringBefore('.')
val reminderStr = str.substringAfter('.')
val packets = arrayListOf<String>()
var index: Int = integerStr.length
while (0 < index) {
if (index <= 3) {
packets.add(integerStr)
break
}
index -= 3
packets.add(integerStr.substring(startIndex = index))
integerStr = integerStr.substring(startIndex = 0, endIndex = index)
}
return buildString {
packets.reversed().forEachIndexed { index, packet ->
append(packet)
if (index != packets.lastIndex) append(' ')
}
if (reminderStr.isNotBlank()) {
append('.')
append(reminderStr)
}
}
}

View file

@ -46,10 +46,6 @@ suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet, sendAna
state.globalState.tapWalletManager.onWalletSelected(userWallet, sendAnalyticsEvent) state.globalState.tapWalletManager.onWalletSelected(userWallet, sendAnalyticsEvent)
} }
fun Store<*>.dispatchToastNotification(resId: Int) {
dispatchOnMain(GlobalAction.ShowToastNotification(resId))
}
fun Store<*>.dispatchErrorNotification(error: TapError) { fun Store<*>.dispatchErrorNotification(error: TapError) {
dispatchOnMain(GlobalAction.ShowErrorNotification(error)) dispatchOnMain(GlobalAction.ShowErrorNotification(error))
} }

View file

@ -1,41 +1,12 @@
package com.tangem.tap.common.extensions package com.tangem.tap.common.extensions
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.net.Uri
import android.text.Spannable
import android.text.style.ForegroundColorSpan
import androidx.core.content.ContextCompat
import androidx.core.text.toSpannable
import com.google.zxing.BarcodeFormat import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import java.util.* import java.util.Hashtable
fun String?.ellipsizeBeforeSpace(allowedSize: Int): String {
if (this.isNullOrBlank()) return ""
val size = this.length
val sizeDifference = size - allowedSize
val endIndex = this.indexOf(" ")
val startIndex = endIndex - sizeDifference
val newString = this.removeRange(startIndex, endIndex)
return newString.substring(0 until startIndex) + "..." +
newString.substring(startIndex until newString.length)
}
fun String.colorSegment(context: Context, color: Int, startIndex: Int = 0, endIndex: Int = this.length): Spannable {
return this.toSpannable()
.also { spannable ->
spannable.setSpan(
ForegroundColorSpan(ContextCompat.getColor(context, color)),
startIndex,
endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
)
}
}
@Suppress("MagicNumber") @Suppress("MagicNumber")
fun String.toQrCode(): Bitmap { fun String.toQrCode(): Bitmap {
@ -58,8 +29,6 @@ fun String.toQrCode(): Bitmap {
return bmp return bmp
} }
fun String.urlEncode(): String = Uri.encode(this)
fun String.removePrefixOrNull(prefix: String): String? = when { fun String.removePrefixOrNull(prefix: String): String? = when {
startsWith(prefix) -> substring(prefix.length) startsWith(prefix) -> substring(prefix.length)
else -> null else -> null

View file

@ -1,24 +1,3 @@
package com.tangem.tap.common.extensions package com.tangem.tap.common.extensions
/**
[REDACTED_AUTHOR]
*/
fun StringBuilder.appendIf(value: String, predicate: (String) -> Boolean): StringBuilder {
if (predicate(value)) this.append(value)
return this
}
fun StringBuilder.appendIfNotNull(value: String?, prefix: String? = null, postfix: String? = null): StringBuilder {
if (value == null) return this
prefix?.let { append(it) }
append(value)
postfix?.let { append(it) }
return this
}
fun String.appendIfNotNull(value: String?, prefix: String? = null, postfix: String? = null): String {
return StringBuilder(this).apply { appendIfNotNull(value, prefix, postfix) }.toString()
}
fun StringBuilder.breakLine(count: Int = 1): StringBuilder = append("\n".repeat(count)) fun StringBuilder.breakLine(count: Int = 1): StringBuilder = append("\n".repeat(count))

View file

@ -1,31 +0,0 @@
package com.tangem.tap.common.extensions
import android.content.Context
import android.view.View
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
fun Context.toast(message: String, length: Int = Toast.LENGTH_LONG) {
Toast.makeText(this, message, length).show()
}
fun Context.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
Toast.makeText(this, this.getString(messageRes), length).show()
}
fun View.toast(message: String, length: Int = Toast.LENGTH_LONG) {
context.toast(message, length)
}
fun View.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
context.toast(context.getString(messageRes), length)
}
fun Fragment.toast(message: String, length: Int = Toast.LENGTH_LONG) {
context?.toast(message, length)
}
fun Fragment.toast(@StringRes messageRes: Int, length: Int = Toast.LENGTH_LONG) {
context?.let { it.toast(it.getString(messageRes), length) }
}

View file

@ -1,31 +0,0 @@
package com.tangem.tap.common.extensions
import android.graphics.Color
import androidx.annotation.ColorInt
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) {
defaultColor
}
}
}
@Suppress("MagicNumber")
@ColorInt
fun Token.getTextColor(isTestnet: Boolean = false): Int = when {
isTestnet -> Color.WHITE
this.getColor().luminance > 0.5 -> Color.BLACK
else -> Color.WHITE
}

View file

@ -1,24 +0,0 @@
package com.tangem.tap.common.extensions
import androidx.transition.Transition
/**
[REDACTED_AUTHOR]
*/
inline fun Transition.addListener(
crossinline onStart: (animator: Transition) -> Unit = {},
crossinline onEnd: (animator: Transition) -> Unit = {},
crossinline onCancel: (animator: Transition) -> Unit = {},
crossinline onPause: (animator: Transition) -> Unit = {},
crossinline onRepeat: (animator: Transition) -> Unit = {},
): Transition.TransitionListener {
val listener = object : Transition.TransitionListener {
override fun onTransitionStart(transition: Transition) = onStart(transition)
override fun onTransitionEnd(transition: Transition) = onEnd(transition)
override fun onTransitionCancel(transition: Transition) = onCancel(transition)
override fun onTransitionPause(transition: Transition) = onPause(transition)
override fun onTransitionResume(transition: Transition) = onRepeat(transition)
}
addListener(listener)
return listener
}

View file

@ -3,25 +3,12 @@
package com.tangem.tap.common.extensions package com.tangem.tap.common.extensions
import android.app.Activity import android.app.Activity
import android.content.ClipData import android.content.*
import android.content.ClipboardManager
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup import androidx.annotation.*
import android.view.inputmethod.InputMethodManager
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.google.android.material.card.MaterialCardView
fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? { fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? {
return ContextCompat.getDrawable(this, drawableResId) return ContextCompat.getDrawable(this, drawableResId)
@ -72,48 +59,9 @@ fun View.hide(invokeBeforeStateChanged: (() -> Unit)? = null) {
this.visibility = View.GONE this.visibility = View.GONE
} }
fun View.invisible(invisible: Boolean = true, invokeBeforeStateChanged: (() -> Unit)? = null) {
if (invisible) {
if (this.visibility == View.INVISIBLE) return
invokeBeforeStateChanged?.invoke()
this.visibility = View.INVISIBLE
} else {
this.show(invokeBeforeStateChanged)
}
}
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 tailrec fun Context?.getActivity(): Activity? = this as? Activity
?: (this as? ContextWrapper)?.baseContext?.getActivity() ?: (this as? ContextWrapper)?.baseContext?.getActivity()
fun MaterialCardView.setMargins(
marginLeftDp: Int = 16,
marginTopDp: Int = 8,
marginRightDp: Int = 16,
marginBottomDp: Int = 8,
) {
val params = this.layoutParams
(params as ViewGroup.MarginLayoutParams).setMargins(
context.dpToPixels(marginLeftDp),
context.dpToPixels(marginTopDp),
context.dpToPixels(marginRightDp),
context.dpToPixels(marginBottomDp),
)
this.layoutParams = params
}
fun View.hideKeyboard() {
val inputMethodManager =
context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
inputMethodManager?.hideSoftInputFromWindow(this.windowToken, 0)
}
fun Context.copyToClipboard(value: Any, label: String = "") { fun Context.copyToClipboard(value: Any, label: String = "") {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return
@ -140,37 +88,6 @@ fun Context.shareText(text: String) {
startActivity(shareIntent) startActivity(shareIntent)
} }
fun Fragment.shareText(text: String) {
requireContext().shareText(text)
}
fun View.getString(resId: Int, vararg formatArgs: Any?): String { fun View.getString(resId: Int, vararg formatArgs: Any?): String {
return context.getString(resId, *formatArgs) return context.getString(resId, *formatArgs)
} }
fun View.animateVisibility(
show: Boolean,
durationMillis: Long = SHORT_ANIMATION_DURATION,
hiddenVisibility: Int = View.GONE,
) {
if (show) {
if (this.visibility == View.VISIBLE) return
this.animate()
.alpha(1f)
.setDuration(durationMillis)
.withStartAction {
this.alpha = 0f
this.isVisible = true
}
} else {
if (this.visibility == hiddenVisibility) return
this.animate()
.alpha(0f)
.setDuration(durationMillis)
.withStartAction {
this.visibility = hiddenVisibility
}
}
}
private const val SHORT_ANIMATION_DURATION = 80L

View file

@ -3,15 +3,9 @@ package com.tangem.tap.common.extensions
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewParent
import androidx.core.view.forEach
import androidx.transition.AutoTransition import androidx.transition.AutoTransition
import androidx.transition.Transition import androidx.transition.Transition
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.tangem.tap.common.GlobalLayoutStateHandler
import timber.log.Timber
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
@ -20,30 +14,6 @@ fun ViewGroup.inflate(viewToInflate: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(context).inflate(viewToInflate, this, attachToRoot) return LayoutInflater.from(context).inflate(viewToInflate, this, attachToRoot)
} }
fun ViewParent?.beginDelayedTransition(transition: Transition = AutoTransition()) {
if (this == null) Timber.e("Can't invoke beginDelayedTransition, because parent is NULL")
(this as? ViewGroup)?.beginDelayedTransition(transition)
}
fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) { fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) {
TransitionManager.beginDelayedTransition(this, transition) TransitionManager.beginDelayedTransition(this, transition)
}
fun View.beginDelayedTransition(transition: Transition = AutoTransition()) {
(this as? ViewGroup)?.beginDelayedTransition(transition)
}
fun ChipGroup.fitChipsByGroupWidth() {
val layoutStateHandler = GlobalLayoutStateHandler(this)
layoutStateHandler.onStateChanged = stateHandler@{
if (it.childCount < 2) {
layoutStateHandler.detach()
return@stateHandler
}
val spacingBetweenViews = it.chipSpacingHorizontal * (it.childCount - 1)
val width = (it.width - spacingBetweenViews) / it.childCount
it.forEach { chip -> (chip as? Chip)?.width = width }
layoutStateHandler.detach()
}
} }

View file

@ -2,17 +2,6 @@ package com.tangem.tap.common.extensions
import android.webkit.WebView import android.webkit.WebView
/**
[REDACTED_AUTHOR]
*/
fun WebView.configureSettings() {
resumeTimers()
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
}
}
fun WebView.stop() { fun WebView.stop() {
stopLoading() stopLoading()
pauseTimers() pauseTimers()

View file

@ -63,6 +63,7 @@ class ScanQrCodeActivity : AppCompatActivity() {
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode != PERMISSION_REQUEST_CODE) return if (requestCode != PERMISSION_REQUEST_CODE) return
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) { if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {

View file

@ -3,8 +3,8 @@ package com.tangem.tap.common.redux
import com.tangem.common.extensions.VoidCallback import com.tangem.common.extensions.VoidCallback
import com.tangem.core.navigation.StateDialog import com.tangem.core.navigation.StateDialog
import com.tangem.tap.common.TestAction import com.tangem.tap.common.TestAction
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.domain.model.WalletAddressData import com.tangem.tap.domain.model.WalletAddressData
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.wallet.R import com.tangem.wallet.R
/** /**

View file

@ -66,7 +66,7 @@ data class AppState(
private val domainState: DomainState private val domainState: DomainState
get() = domainStore.state get() = domainStore.state
val domainNetworks: NetworkServices private val domainNetworks: NetworkServices
get() = domainState.globalState.networkServices get() = domainState.globalState.networkServices
val featureRepositoryProvider: FeatureRepositoryProvider val featureRepositoryProvider: FeatureRepositoryProvider

View file

@ -20,22 +20,14 @@ class NotificationsHandler(coordinatorLayout: CoordinatorLayout) {
private val basicCoordinatorLayout = WeakReference(coordinatorLayout) private val basicCoordinatorLayout = WeakReference(coordinatorLayout)
private var baseLayout = basicCoordinatorLayout private var baseLayout = basicCoordinatorLayout
fun replaceBaseLayout(coordinatorLayout: CoordinatorLayout) { private fun showNotification(message: String) {
baseLayout = WeakReference(coordinatorLayout)
}
fun returnBaseLayout() {
baseLayout = basicCoordinatorLayout
}
fun showNotification(message: String) {
baseLayout.get()?.let { layout -> baseLayout.get()?.let { layout ->
Snackbar.make(layout, message, Snackbar.LENGTH_LONG) Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
.also { snackbar -> snackbar.show() } .also { snackbar -> snackbar.show() }
} }
} }
fun showDebugNotification(message: String) { private fun showDebugNotification(message: String) {
baseLayout.get()?.let { layout -> baseLayout.get()?.let { layout ->
Snackbar.make(layout, message, Snackbar.LENGTH_LONG) Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
.also { snackbar -> .also { snackbar ->

View file

@ -1,7 +0,0 @@
package com.tangem.tap.common.redux
import org.rekotlin.Action
abstract class Request : Action {
abstract suspend fun execute()
}

View file

@ -14,7 +14,6 @@ import com.tangem.tap.common.feedback.FeedbackManager
import com.tangem.tap.common.redux.DebugErrorAction import com.tangem.tap.common.redux.DebugErrorAction
import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.ToastNotificationAction
import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
@ -25,7 +24,6 @@ sealed class GlobalAction : Action {
// notifications // notifications
data class ShowNotification(override val messageResource: Int) : GlobalAction(), NotificationAction data class ShowNotification(override val messageResource: Int) : GlobalAction(), NotificationAction
data class ShowToastNotification(override val messageResource: Int) : GlobalAction(), ToastNotificationAction
data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction
data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction

View file

@ -169,7 +169,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
return Result.success(products) return Result.success(products)
} }
suspend fun applyPromoCode(promoCode: String, productType: ProductType): Result<TangemProduct> { private suspend fun applyPromoCode(promoCode: String, productType: ProductType): Result<TangemProduct> {
val checkout = checkouts[productType] ?: return Result.failure(Exception("No checkout")) val checkout = checkouts[productType] ?: return Result.failure(Exception("No checkout"))
val result = if (promoCode.isBlank()) { val result = if (promoCode.isBlank()) {

View file

@ -19,26 +19,7 @@ import kotlin.coroutines.suspendCoroutine
@Suppress("LargeClass") @Suppress("LargeClass")
class ShopifyService(private val application: Application, val shop: ShopifyShop) { class ShopifyService(private val application: Application, val shop: ShopifyShop) {
val client: GraphClient by lazy { initClient() } private val client: GraphClient by lazy { initClient() }
suspend fun getShopName(): Result<String> {
val query = query { rootQuery: QueryRootQuery ->
rootQuery
.shop { shopQuery: ShopQuery ->
shopQuery
.name()
}
}
return when (val result = queryAsync(query)) {
is GraphCallResult.Success -> {
val name = result.response.data!!.shop.name
Result.success(name)
}
is GraphCallResult.Failure -> {
Result.failure(result.error)
}
}
}
@Suppress("MagicNumber") @Suppress("MagicNumber")
suspend fun getProducts(collectionTitleFilter: String? = null): Result<List<Product>> { suspend fun getProducts(collectionTitleFilter: String? = null): Result<List<Product>> {

View file

@ -28,12 +28,12 @@ abstract class BaseTruncate : Truncate {
protected var hasBeenTruncated = false protected var hasBeenTruncated = false
override fun apply(tv: TextView, text: String, with: String): String { override fun apply(tv: TextView, text: String, with: String): String {
val roughLength = getRoughFitLength(tv, text, with) val roughLength = getRoughFitLength(tv, text)
val fittedText = preciseFitting(tv, roughTruncate(text, roughLength), with) val fittedText = preciseFitting(tv, roughTruncate(text, roughLength), with)
return if (hasBeenTruncated) attachWith(fittedText, with) else fittedText return if (hasBeenTruncated) attachWith(fittedText, with) else fittedText
} }
protected fun getRoughFitLength(tv: TextView, text: String, with: String): Int { private fun getRoughFitLength(tv: TextView, text: String): Int {
val existingSpace = tv.measuredWidth - (tv.paddingStart + tv.paddingEnd) val existingSpace = tv.measuredWidth - (tv.paddingStart + tv.paddingEnd)
val textWillTakeSpace = tv.paint.measureText(text) val textWillTakeSpace = tv.paint.measureText(text)
val overSizeRatio: Float = textWillTakeSpace / existingSpace val overSizeRatio: Float = textWillTakeSpace / existingSpace
@ -44,7 +44,7 @@ abstract class BaseTruncate : Truncate {
return maxLengthOfText.toInt() return maxLengthOfText.toInt()
} }
protected fun preciseFitting(tv: TextView, text: String, with: String): String { private fun preciseFitting(tv: TextView, text: String, with: String): String {
if (!hasBeenTruncated) return text if (!hasBeenTruncated) return text
val spaceForText = tv.measuredWidth - (tv.paddingStart + tv.paddingEnd) val spaceForText = tv.measuredWidth - (tv.paddingStart + tv.paddingEnd)
@ -126,11 +126,5 @@ fun TextView.truncateWith(text: String, type: TruncateType, with: String = "..."
return truncate.apply(this, text, with) return truncate.apply(this, text, with)
} }
fun TextView.truncateStartWith(text: String, with: String = "..."): String =
this.truncateWith(text, TruncateType.START, with)
fun TextView.truncateMiddleWith(text: String, with: String = "..."): String = 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)

View file

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

View file

@ -27,7 +27,6 @@ import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.derivationsFinder import com.tangem.tap.derivationsFinder
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
@ -126,15 +125,6 @@ class TangemSdkManager(
} }
} }
suspend fun createWallet(cardId: String?): CompletionResult<CardDTO> {
return runTaskAsyncReturnOnMain(
CreateWalletAndRescanTask(),
cardId,
initialMessage = Message(resources.getString(R.string.initial_message_create_wallet_body)),
)
.map { CardDTO(it) }
}
suspend fun derivePublicKeys( suspend fun derivePublicKeys(
cardId: String?, cardId: String?,
derivations: Map<ByteArrayKey, List<DerivationPath>>, derivations: Map<ByteArrayKey, List<DerivationPath>>,

View file

@ -22,11 +22,7 @@ sealed class TapError(
object UnknownError : TapError(R.string.send_error_unknown) object UnknownError : TapError(R.string.send_error_unknown)
open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage)) open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage))
data class UnsupportedState(
val stateError: String,
) : TapError(R.string.common_custom_string, listOf("Unsupported state: $stateError"))
object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle)
object NoInternetConnection : TapError(R.string.wallet_notification_no_internet) object NoInternetConnection : TapError(R.string.wallet_notification_no_internet)
object AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance) object AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance)
data class AmountLowerExistentialDeposit( data class AmountLowerExistentialDeposit(
@ -41,7 +37,6 @@ sealed class TapError(
object DustChange : TapError(R.string.send_error_dust_change) object DustChange : TapError(R.string.send_error_dust_change)
sealed class WalletManager { sealed class WalletManager {
object CreationError : CustomError(customMessage = "Can't create wallet manager")
class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount) class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)
class InternalError(message: String) : CustomError(message) class InternalError(message: String) : CustomError(message)
object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later) object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later)

View file

@ -51,14 +51,12 @@ data class WarningMessage(
} }
enum class Location { enum class Location {
@Json(name = "main")
MainScreen,
@Json(name = "send") @Json(name = "send")
SendScreen, SendScreen,
} }
enum class Origin { enum class Origin {
Local, Remote Remote,
} }
} }

View file

@ -1,8 +1,6 @@
package com.tangem.tap.domain.configurable.warningMessage package com.tangem.tap.domain.configurable.warningMessage
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.utils.extensions.removeBy
import com.tangem.wallet.R
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
/** /**
@ -14,13 +12,6 @@ class WarningMessagesManager {
private val warningsList = CopyOnWriteArrayList<WarningMessage>() private val warningsList = CopyOnWriteArrayList<WarningMessage>()
fun addWarning(warning: WarningMessage) {
if (findWarning(warning) == null) {
warningsList.add(warning)
sortByPriority()
}
}
fun getWarnings(location: WarningMessage.Location, blockchains: List<Blockchain>): List<WarningMessage> { fun getWarnings(location: WarningMessage.Location, blockchains: List<Blockchain>): List<WarningMessage> {
return warningsList.filter { message -> return warningsList.filter { message ->
val messageBlockchains = message.blockchainList val messageBlockchains = message.blockchainList
@ -45,131 +36,7 @@ class WarningMessagesManager {
} }
} }
fun removeWarnings(origin: WarningMessage.Origin) {
warningsList.removeBy { it.origin == origin }
sortByPriority()
}
fun removeWarnings(messageRes: Int) {
warningsList.removeBy { it.messageResId == messageRes }
}
fun containsWarning(warning: WarningMessage) = warning in warningsList
private fun sortByPriority() {
warningsList.sortBy { it.priority.ordinal }
}
private fun findWarning(warning: WarningMessage): WarningMessage? { private fun findWarning(warning: WarningMessage): WarningMessage? {
return warningsList.firstOrNull { it == warning } return warningsList.firstOrNull { it == warning }
} }
companion object {
const val REMAINING_SIGNATURES_WARNING = 10
val devCardWarning = WarningMessage(
title = "",
message = "",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
titleResId = R.string.common_warning,
// messageResId = R.string.alert_developer_card,
origin = WarningMessage.Origin.Local,
)
val alreadySignedHashesWarning = WarningMessage(
title = "",
message = "",
type = WarningMessage.Type.Temporary,
priority = WarningMessage.Priority.Info,
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
titleResId = R.string.common_warning,
// messageResId = R.string.alert_card_signed_transactions,
origin = WarningMessage.Origin.Local,
)
val signedHashesMultiWalletWarning = WarningMessage(
title = "",
message = "",
type = WarningMessage.Type.Temporary,
priority = WarningMessage.Priority.Info,
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
// titleResId = R.string.warning_important_security_info,
// messageResId = R.string.warning_signed_tx_previously,
origin = WarningMessage.Origin.Local,
buttonTextId = R.string.warning_button_learn_more,
titleFormatArg = "\u26A0",
)
val appRatingWarning = WarningMessage(
title = "",
message = "",
type = WarningMessage.Type.AppRating,
priority = WarningMessage.Priority.Info,
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
titleResId = R.string.warning_rate_app_title,
messageResId = R.string.warning_rate_app_message,
origin = WarningMessage.Origin.Local,
)
val onlineVerificationFailed = WarningMessage(
title = "",
message = "",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
titleResId = R.string.warning_failed_to_verify_card_title,
messageResId = R.string.warning_failed_to_verify_card_message,
origin = WarningMessage.Origin.Local,
)
val testCardWarning = WarningMessage(
title = "",
message = "",
type = WarningMessage.Type.TestCard,
priority = WarningMessage.Priority.Critical,
location = listOf(WarningMessage.Location.MainScreen, WarningMessage.Location.SendScreen),
blockchains = null,
titleResId = R.string.common_warning,
messageResId = R.string.warning_testnet_card_message,
origin = WarningMessage.Origin.Local,
)
val demoCardWarning = WarningMessage(
title = "",
message = "",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
titleResId = R.string.common_warning,
// messageResId = R.string.alert_demo_message,
origin = WarningMessage.Origin.Local,
)
fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage {
return WarningMessage(
title = "",
message = "",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
titleResId = R.string.common_warning,
// messageResId = R.string.warning_low_signatures_format,
origin = WarningMessage.Origin.Local,
messageFormatArg = remainingSignatures.toString(),
)
}
// fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
// return warning.messageResId == R.string.alert_card_signed_transactions
// }
}
} }

View file

@ -9,17 +9,6 @@ import com.tangem.domain.userwallets.Artwork
import com.tangem.operations.attestation.CardVerifyAndGetInfo import com.tangem.operations.attestation.CardVerifyAndGetInfo
import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.operations.attestation.OnlineCardVerifier
val CardDTO.remainingSignatures: Int?
get() = this.wallets.firstOrNull()?.remainingSignatures
val CardDTO.isHdWalletAllowedByApp: Boolean
get() = settings.isHDWalletAllowed
@Suppress("UnnecessaryParentheses")
fun CardDTO.hasSignedHashes(): Boolean {
return wallets.any { (it.totalSignedHashes ?: 0) > 0 }
}
fun CardDTO.signedHashesCount(): Int { fun CardDTO.signedHashesCount(): Int {
return wallets.sumOf { it.totalSignedHashes ?: 0 } return wallets.sumOf { it.totalSignedHashes ?: 0 }
} }

View file

@ -1,17 +0,0 @@
package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Token
/**
[REDACTED_AUTHOR]
*/
private val tokenCustomIconUrls = mutableMapOf<String, String>()
fun Token.getCustomIconUrl(): String? {
return tokenCustomIconUrls[this.contractAddress]
}
fun Token.setCustomIconUrl(url: String) {
tokenCustomIconUrls[this.contractAddress] = url
}

View file

@ -0,0 +1,64 @@
package com.tangem.tap.domain.model
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.blockchain.common.Blockchain as SdkBlockchain
import com.tangem.blockchain.common.Token as SdkToken
sealed interface Currency {
val blockchain: SdkBlockchain
val currencySymbol: CryptoCurrencyName
val derivationPath: String?
val decimals
get() = when (this) {
is Blockchain -> blockchain.decimals()
is Token -> token.decimals
}
data class Token(
val token: SdkToken,
override val blockchain: SdkBlockchain,
override val derivationPath: String?,
) : Currency {
override val currencySymbol = token.symbol
}
data class Blockchain(
override val blockchain: SdkBlockchain,
override val derivationPath: String?,
) : Currency {
override val currencySymbol: CryptoCurrencyName = blockchain.currency
}
companion object {
fun fromBlockchainNetwork(blockchainNetwork: BlockchainNetwork, token: SdkToken? = null): Currency {
return if (token != null) {
Token(
token = token,
blockchain = blockchainNetwork.blockchain,
derivationPath = blockchainNetwork.derivationPath,
)
} else {
Blockchain(
blockchain = blockchainNetwork.blockchain,
derivationPath = blockchainNetwork.derivationPath,
)
}
}
fun fromCustomCurrency(customCurrency: CustomCurrency): Currency {
return when (customCurrency) {
is CustomCurrency.CustomBlockchain -> Blockchain(
blockchain = customCurrency.network,
derivationPath = customCurrency.derivationPath?.rawPath,
)
is CustomCurrency.CustomToken -> Token(
token = customCurrency.token,
blockchain = customCurrency.network,
derivationPath = customCurrency.derivationPath?.rawPath,
)
}
}
}
}

View file

@ -1,14 +1,8 @@
package com.tangem.tap.features.wallet.models package com.tangem.tap.domain.model
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.extensions.isAboveZero
import com.tangem.tap.common.extensions.toFormattedString
import java.math.BigDecimal
data class PendingTransaction( data class PendingTransaction(
val transactionData: TransactionData, val transactionData: TransactionData,
@ -20,10 +14,6 @@ data class PendingTransaction(
PendingTransactionType.Unknown -> null PendingTransactionType.Unknown -> null
} }
val amountValue: BigDecimal? = transactionData.amount.value
val amountValueUi: String? = amountValue?.toFormattedString(transactionData.amount.decimals)
val currency: String = transactionData.amount.currencySymbol val currency: String = transactionData.amount.currencySymbol
private fun nullIfUnknown(address: String): String? = if (address == "unknown") null else address private fun nullIfUnknown(address: String): String? = if (address == "unknown") null else address
@ -46,15 +36,6 @@ fun List<TransactionData>.toPendingTransactions(walletAddress: String): List<Pen
return this.mapNotNull { it.toPendingTransaction(walletAddress) } return this.mapNotNull { it.toPendingTransaction(walletAddress) }
} }
fun List<PendingTransaction>.filterByCoin(): List<PendingTransaction> {
return this.filter { it.transactionData.amount.type == AmountType.Coin }
}
fun TransactionData.toPendingTransactionForToken(token: Token, walletAddress: String): PendingTransaction? {
if (this.amount.currencySymbol != token.symbol) return null
return this.toPendingTransaction(walletAddress)
}
fun Wallet.getPendingTransactions(type: PendingTransactionType? = null): List<PendingTransaction> { fun Wallet.getPendingTransactions(type: PendingTransactionType? = null): List<PendingTransaction> {
val txs = recentTransactions.toPendingTransactions(address) val txs = recentTransactions.toPendingTransactions(address)
return when (type) { return when (type) {
@ -63,24 +44,6 @@ fun Wallet.getPendingTransactions(type: PendingTransactionType? = null): List<Pe
} }
} }
fun Wallet.getPendingTransactions(token: Token): List<PendingTransaction> {
return recentTransactions.mapNotNull { it.toPendingTransactionForToken(token, address) }
}
fun Wallet.hasPendingTransactions(): Boolean { fun Wallet.hasPendingTransactions(): Boolean {
return getPendingTransactions().isNotEmpty() return getPendingTransactions().isNotEmpty()
}
fun Wallet.getSendableAmounts(): List<Amount> {
return amounts.values
.filter { it.type != AmountType.Reserve }
.filter { it.isAboveZero() }
}
fun Wallet.hasSendableAmounts(): Boolean {
return getSendableAmounts().isNotEmpty()
}
fun Wallet.isSendableAmount(type: AmountType): Boolean {
return amounts[type]?.isAboveZero() == true
} }

View file

@ -1,32 +0,0 @@
package com.tangem.tap.domain.moduleMessage
/**
[REDACTED_AUTHOR]
* Base interface for conversion outputs from ModuleMessageConverter
*/
interface ConvertedMessage {
val message: String
}
/**
* Dialog message used to construct a android.Dialog
*/
interface DialogMessage : ConvertedMessage {
val title: String
override val message: String
val onPositive: String?
val onNegative: String?
val onNeutral: String?
}
data class ConvertedStringMessage(
override val message: String,
) : ConvertedMessage
data class ConvertedDialogMessage(
override val title: String,
override val message: String,
override val onPositive: String? = null,
override val onNegative: String? = null,
override val onNeutral: String? = null,
) : DialogMessage

View file

@ -1,24 +0,0 @@
package com.tangem.tap.domain.moduleMessage
import android.content.Context
import com.tangem.common.module.ModuleMessage
import com.tangem.common.module.ModuleMessageConverter
import com.tangem.domain.DomainModuleMessage
import com.tangem.tap.domain.moduleMessage.domain.DomainMessageConverter
class ModuleMessageConverter(
private val context: Context,
) : ModuleMessageConverter<ModuleMessage, ConvertedMessage> {
override fun convert(message: ModuleMessage): ConvertedMessage {
val convertedMessage = when (message) {
is DomainModuleMessage -> DomainMessageConverter(context).convert(message)
else -> null
}
return convertedMessage ?: convertUnknownMessage(message)
}
private fun convertUnknownMessage(message: ModuleMessage): ConvertedMessage {
return ConvertedStringMessage("Unknown message: ${message::class.java.simpleName}")
}
}

View file

@ -1,34 +0,0 @@
package com.tangem.tap.domain.moduleMessage.domain
import android.content.Context
import com.tangem.common.module.ModuleMessageConverter
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.DomainModuleError
import com.tangem.domain.DomainModuleMessage
import com.tangem.tap.domain.moduleMessage.ConvertedMessage
import com.tangem.tap.domain.moduleMessage.domain.converter.AddCustomTokenErrorConverter
/**
[REDACTED_AUTHOR]
*/
class DomainMessageConverter(
private val context: Context,
) : ModuleMessageConverter<DomainModuleMessage, ConvertedMessage?> {
override fun convert(message: DomainModuleMessage): ConvertedMessage? {
return when (message) {
is DomainModuleError -> DomainErrorConverter(context).convert(message)
else -> null
}
}
}
class DomainErrorConverter(
private val context: Context,
) : ModuleMessageConverter<DomainModuleError, ConvertedMessage?> {
override fun convert(message: DomainModuleError): ConvertedMessage? = when (message) {
is AddCustomTokenError -> AddCustomTokenErrorConverter(context).convert(message)
else -> null
}
}

View file

@ -1,42 +0,0 @@
package com.tangem.tap.domain.moduleMessage.domain.converter
import android.content.Context
import com.tangem.common.module.ModuleMessageConverter
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.DomainModuleError
import com.tangem.tap.domain.moduleMessage.ConvertedMessage
import com.tangem.tap.domain.moduleMessage.ConvertedStringMessage
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
internal class AddCustomTokenErrorConverter(
private val context: Context,
) : ModuleMessageConverter<DomainModuleError, ConvertedMessage?> {
@Suppress("MagicNumber")
override fun convert(message: DomainModuleError): ConvertedMessage? {
val customTokenError = message as? AddCustomTokenError ?: throw UnsupportedOperationException()
val rawMessage = when (customTokenError) {
AddCustomTokenError.Warning.PotentialScamToken -> R.string.custom_token_validation_error_not_found
AddCustomTokenError.Warning.TokenAlreadyAdded -> R.string.custom_token_validation_error_already_added
AddCustomTokenError.Warning.UnsupportedSolanaToken -> R.string.alert_manage_tokens_unsupported_message
AddCustomTokenError.InvalidContractAddress -> R.string.custom_token_creation_error_invalid_contract_address
AddCustomTokenError.NetworkIsNotSelected -> R.string.custom_token_creation_error_network_not_selected
AddCustomTokenError.InvalidDerivationPath -> R.string.custom_token_creation_error_invalid_derivation_path
AddCustomTokenError.InvalidDecimalsCount -> {
context.getString(R.string.custom_token_creation_error_wrong_decimals, 30)
}
AddCustomTokenError.FieldIsEmpty -> R.string.custom_token_creation_error_required_field
else -> null
}
return when (rawMessage) {
is Int -> ConvertedStringMessage(context.getString(rawMessage))
is String -> ConvertedStringMessage(rawMessage)
else -> null
}
}
}

View file

@ -1,89 +0,0 @@
package com.tangem.tap.domain.statePrinter
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.domain.redux.state.StringStateConverter
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep
import com.tangem.tap.store
import timber.log.Timber
import java.math.BigInteger
/**
[REDACTED_AUTHOR]
*/
class OnboardingWalletStateConverter : StringStateConverter<AppState> {
private val converter = MoshiJsonConverter(
MoshiJsonConverter.getTangemSdkAdapters() + internalAdapters(),
MoshiJsonConverter.getTangemSdkTypedAdapters(),
)
private fun internalAdapters(): List<Any> {
return listOf(
BackupStepAdapter(),
BigIntegerAdapter(),
)
}
override fun convert(stateHolder: AppState): String {
val model = stateHolder.onboardingWalletState
val json = converter.prettyPrint(model)
return json
}
}
fun printOnboardingWalletState() {
val stringState = OnboardingWalletStateConverter().convert(store.state)
Timber.d(stringState)
}
class BackupStepAdapter {
@ToJson
fun toJson(src: BackupStep): String {
return when (src) {
BackupStep.AddBackupCards -> "AddBackupCards"
BackupStep.EnterAccessCode -> "EnterAccessCode"
BackupStep.Finished -> "Finished"
BackupStep.InitBackup -> "InitBackup"
BackupStep.ReenterAccessCode -> "ReenterAccessCode"
BackupStep.ScanOriginCard -> "ScanOriginCard"
BackupStep.SetAccessCode -> "SetAccessCode"
is BackupStep.WriteBackupCard -> "WriteBackupCard[${src.cardNumber}]"
BackupStep.WritePrimaryCard -> "WritePrimaryCard"
}
}
@Suppress("MagicNumber")
@FromJson
fun fromJson(json: String): BackupStep {
return when (json) {
"AddBackupCards" -> BackupStep.AddBackupCards
"EnterAccessCode" -> BackupStep.EnterAccessCode
"Finished" -> BackupStep.Finished
"InitBackup" -> BackupStep.InitBackup
"ReenterAccessCode" -> BackupStep.ReenterAccessCode
"ScanOriginCard" -> BackupStep.ScanOriginCard
"SetAccessCode" -> BackupStep.SetAccessCode
"WriteBackupCard[1]" -> BackupStep.WriteBackupCard(1)
"WriteBackupCard[2]" -> BackupStep.WriteBackupCard(2)
"WriteBackupCard[3]" -> BackupStep.WriteBackupCard(3)
"WritePrimaryCard" -> BackupStep.WritePrimaryCard
else -> throw UnsupportedOperationException()
}
}
}
class BigIntegerAdapter {
@ToJson
fun toJson(src: BigInteger): String {
return src.toString()
}
@FromJson
fun fromJson(json: String): BigInteger {
return BigInteger(json)
}
}

View file

@ -1,44 +0,0 @@
package com.tangem.tap.domain.statePrinter
import com.tangem.common.extensions.toHexString
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.domain.redux.state.StringStateConverter
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.store
import timber.log.Timber
/**
[REDACTED_AUTHOR]
*/
class ScanResponseConverter : StringStateConverter<AppState> {
private val converter = MoshiJsonConverter.INSTANCE
override fun convert(stateHolder: AppState): String {
val scanResponse = stateHolder.globalState.scanResponse ?: return "NULL"
val scanResponseMap = mutableMapOf<String, Any?>()
val derivedKeysMap = mutableMapOf<String, Any?>()
scanResponse.derivedKeys.forEach { (keyWalletPubKey, mapExPubKeys) ->
val exPubKeysMap = mapExPubKeys.entries.map { (derivationPath, exPubKey) ->
mapOf(
"derivationPath" to derivationPath.rawPath,
"extendedPublicKey" to mapOf(
"publicKey" to exPubKey.publicKey.toHexString(),
"chainCode" to exPubKey.chainCode.toHexString(),
),
)
}
derivedKeysMap[keyWalletPubKey.bytes.toHexString()] = exPubKeysMap
}
scanResponseMap["derivedKeys"] = derivedKeysMap
val json = converter.prettyPrint(scanResponseMap)
return json
}
}
fun printScanResponseState() {
val stringState = ScanResponseConverter().convert(store.state)
Timber.d(stringState)
}

View file

@ -1,41 +0,0 @@
package com.tangem.tap.domain.tasks
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.CompletionCallback
import com.tangem.operations.CommandResponse
import com.tangem.operations.sign.SignHashCommand
class TangemSignHashResponse(
val signature: ByteArray,
val totalSignedHashes: Int?,
val remainingSignatures: Int?,
) : CommandResponse
class SignHashTask(
private val hash: ByteArray,
private val publicKey: Wallet.PublicKey,
) : CardSessionRunnable<TangemSignHashResponse> {
override fun run(session: CardSession, callback: CompletionCallback<TangemSignHashResponse>) {
SignHashCommand(hash, publicKey.seedKey, publicKey.derivationPath).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
callback(
CompletionResult.Success(
TangemSignHashResponse(
response.data.signature,
response.data.totalSignedHashes,
session.environment.card?.wallet(publicKey.seedKey)?.remainingSignatures,
),
),
)
}
is CompletionResult.Failure ->
callback(CompletionResult.Failure(response.error))
}
}
}
}

View file

@ -309,7 +309,5 @@ private class CreateWalletTangemWallet(
} }
} }
private companion object { private companion object
val CURVES_FOR_WALLETS = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519)
}
} }

View file

@ -13,9 +13,7 @@ internal data class UserWalletEncryptionKey(
if (other !is UserWalletEncryptionKey) return false if (other !is UserWalletEncryptionKey) return false
if (walletId != other.walletId) return false if (walletId != other.walletId) return false
if (!encryptionKey.contentEquals(other.encryptionKey)) return false return encryptionKey.contentEquals(other.encryptionKey)
return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {

View file

@ -1,9 +1 @@
package com.tangem.tap.domain.userWalletList.utils package com.tangem.tap.domain.userWalletList.utils
internal fun List<ByteArray>.containsBA(element: ByteArray?): Boolean {
this.forEach {
if (it.contentEquals(element)) return true
}
return false
}

View file

@ -1,14 +1,9 @@
package com.tangem.tap.domain.walletconnect package com.tangem.tap.domain.walletconnect
import com.github.salomonbrys.kotson.fromJson import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.toMap
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonParser import com.google.gson.JsonParser
import com.google.gson.annotations.SerializedName
import com.tangem.blockchain.common.Blockchain
import com.trustwallet.walletconnect.JSONRPC_VERSION
object EthSignHelper { object EthSignHelper {
private val gson: Gson by lazy { private val gson: Gson by lazy {
@ -33,68 +28,4 @@ object EthSignHelper {
null null
} }
} }
}
data class CustomJsonRpcRequest(
val id: Long,
val jsonrpc: String = JSONRPC_VERSION,
val method: WCMethodExtended?,
val params: JsonArray,
) {
fun blockchainFromChainId(): Blockchain? {
return try {
val hex = params[0].asJsonObject.toMap()[CHAIN_ID_KEY]?.asString ?: ""
Blockchain.fromChainId(Integer.decode(hex))
} catch (exception: Exception) {
null
}
}
companion object {
const val CHAIN_ID_KEY = "chainId"
}
}
enum class WCMethodExtended {
@SerializedName("wc_sessionRequest")
SESSION_REQUEST,
@SerializedName("wc_sessionUpdate")
SESSION_UPDATE,
@SerializedName("eth_sign")
ETH_SIGN,
@SerializedName("personal_sign")
ETH_PERSONAL_SIGN,
@SerializedName("eth_signTypedData")
ETH_SIGN_TYPE_DATA,
@SerializedName("eth_signTypedData_v4")
ETH_SIGN_TYPE_DATA_V4,
@SerializedName("eth_signTransaction")
ETH_SIGN_TRANSACTION,
@SerializedName("eth_sendTransaction")
ETH_SEND_TRANSACTION,
@SerializedName("bnb_sign")
BNB_SIGN,
@SerializedName("bnb_tx_confirmation")
BNB_TRANSACTION_CONFIRM,
@SerializedName("get_accounts")
GET_ACCOUNTS,
@SerializedName("trust_signTransaction")
SIGN_TRANSACTION,
@SerializedName("wallet_switchEthereumChain")
SWITCH_CHAIN,
;
} }

View file

@ -36,7 +36,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.collections.set import kotlin.collections.set
@ -191,7 +191,7 @@ class WalletConnectManager {
} }
} }
fun removeSimilarSessions(activeData: WalletConnectActiveData) { private fun removeSimilarSessions(activeData: WalletConnectActiveData) {
val sessionsToRemove = sessions.filter { val sessionsToRemove = sessions.filter {
it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true && it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true &&
it.value.peerMeta?.url == activeData.peerMeta?.url && it.value.peerMeta?.url == activeData.peerMeta?.url &&
@ -206,7 +206,7 @@ class WalletConnectManager {
activeData.client.rejectRequest(id) activeData.client.rejectRequest(id)
} }
fun acceptRequest(topic: String, id: Long, data: String) { private fun acceptRequest(topic: String, id: Long, data: String) {
val activeData = sessions[topic] ?: return val activeData = sessions[topic] ?: return
activeData.client.approveRequest(id, data) activeData.client.approveRequest(id, data)
} }
@ -368,7 +368,7 @@ class WalletConnectManager {
} }
@Suppress("LongMethod", "ComplexMethod") @Suppress("LongMethod", "ComplexMethod")
fun setListeners(client: WCClient) { private fun setListeners(client: WCClient) {
client.onSessionRequest = { id: Long, peer: WCPeerMeta -> client.onSessionRequest = { id: Long, peer: WCPeerMeta ->
Timber.d("OnSessionRequest: $peer") Timber.d("OnSessionRequest: $peer")
val session = client.session val session = client.session

View file

@ -25,11 +25,6 @@ class WalletConnectRepository(val context: Application) {
saveSessions(sessions) saveSessions(sessions)
} }
fun removeSession(session: WalletConnectSession) {
val sessions = loadSavedSessions().filterNot { it == session }
saveSessions(sessions)
}
fun removeSession(session: WCSession) { fun removeSession(session: WCSession) {
val sessions = loadSavedSessions().filterNot { it.session == session } val sessions = loadSavedSessions().filterNot { it.session == session }
saveSessions(sessions) saveSessions(sessions)

View file

@ -31,14 +31,16 @@ import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.details.redux.walletconnect.* import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData
import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType
import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData
import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager import com.tangem.tap.tangemSdkManager
import com.tangem.tap.userWalletsListManager import com.tangem.tap.userWalletsListManager
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.*
import timber.log.Timber import timber.log.Timber
import java.math.BigDecimal import java.math.BigDecimal

View file

@ -1,6 +0,0 @@
package com.tangem.tap.domain.walletconnect2.domain.models
data class ChainWithDerivation(
val chain: String,
val derivationPath: String?,
)

View file

@ -1,6 +1,7 @@
package com.tangem.tap.domain.walletconnect2.domain.models.binance package com.tangem.tap.domain.walletconnect2.domain.models.binance
import com.squareup.moshi.* import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@Suppress("LongParameterList") @Suppress("LongParameterList")
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@ -21,12 +22,6 @@ class WcBinanceCancelOrder(
msgs: List<Message>, msgs: List<Message>,
) : WcBinanceOrder<WcBinanceCancelOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) { ) : WcBinanceOrder<WcBinanceCancelOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
enum class MessageKey(val key: String) {
REFID("refid"),
SENDER("sender"),
SYMBOL("symbol"),
}
data class Message( data class Message(
val refid: String, val refid: String,
val sender: String, val sender: String,

View file

@ -1,6 +1,6 @@
package com.tangem.tap.domain.walletconnect2.domain.models.binance package com.tangem.tap.domain.walletconnect2.domain.models.binance
import com.github.salomonbrys.kotson.* import com.github.salomonbrys.kotson.jsonSerializer
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@ -42,19 +42,6 @@ class WcBinanceTradeOrder(
) )
} }
val tradeOrderDeserializer = jsonDeserializer {
WcBinanceTradeOrder.Message(
id = it.json[WcBinanceTradeOrder.MessageKey.ID.key].string,
orderType = it.json[WcBinanceTradeOrder.MessageKey.ORDER_TYPE.key].int,
price = it.json[WcBinanceTradeOrder.MessageKey.PRICE.key].long,
quantity = it.json[WcBinanceTradeOrder.MessageKey.QUANTITY.key].long,
sender = it.json[WcBinanceTradeOrder.MessageKey.SENDER.key].string,
side = it.json[WcBinanceTradeOrder.MessageKey.SIDE.key].int,
symbol = it.json[WcBinanceTradeOrder.MessageKey.SYMBOL.key].string,
timeInforce = it.json[WcBinanceTradeOrder.MessageKey.TIME_INFORCE.key].int,
)
}
val tradeOrderSerializer = jsonSerializer<WcBinanceTradeOrder.Message> { val tradeOrderSerializer = jsonSerializer<WcBinanceTradeOrder.Message> {
val jsonObject = JsonObject() val jsonObject = JsonObject()
jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.ID.key, it.src.id) jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.ID.key, it.src.id)

View file

@ -1,17 +0,0 @@
package com.tangem.tap.domain.walletconnect2.domain.models.binance
data class WcBinanceTradePair(val from: String, val to: String) {
companion object {
fun from(symbol: String): WcBinanceTradePair? {
val pair = symbol.split("_")
return if (pair.size > 1) {
val firstParts = pair[0].split("-")
val secondParts = pair[1].split("-")
WcBinanceTradePair(firstParts[0], secondParts[0])
} else {
null
}
}
}
}

View file

@ -17,11 +17,6 @@ class WcBinanceTransferOrder(
msgs: List<Message>, msgs: List<Message>,
) : WcBinanceOrder<WcBinanceTransferOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) { ) : WcBinanceOrder<WcBinanceTransferOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
enum class MessageKey(val key: String) {
INPUTS("inputs"),
OUTPUTS("outputs"),
}
data class Message( data class Message(
val inputs: List<Item>, val inputs: List<Item>,
val outputs: List<Item>, val outputs: List<Item>,

View file

@ -23,7 +23,7 @@ import com.tangem.wallet.R
*/ */
abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackPressedHandler { abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackPressedHandler {
protected lateinit var mainView: View private lateinit var mainView: View
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View file

@ -23,9 +23,9 @@ import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager import com.tangem.tap.tangemSdkManager

View file

@ -615,10 +615,6 @@ internal class AddCustomTokenViewModel @Inject constructor(
} }
} }
private fun DerivationPath?.isSameDerivationPath(rawDerivationPath: String?): Boolean {
return this == rawDerivationPath?.let { DerivationPath(it) }
}
private fun handleContractAddressErrorValidation(type: AddCustomTokenError) { private fun handleContractAddressErrorValidation(type: AddCustomTokenError) {
when { when {
isNetworkSelected() && type == AddCustomTokenError.InvalidContractAddress -> { isNetworkSelected() && type == AddCustomTokenError.InvalidContractAddress -> {

View file

@ -6,8 +6,8 @@ import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.entities.ProgressState import com.tangem.tap.common.entities.ProgressState
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.scope import com.tangem.tap.scope
import com.tangem.tap.store import com.tangem.tap.store
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View file

@ -74,8 +74,6 @@ sealed class WalletConnectAction : Action {
) : ) :
WalletConnectAction() WalletConnectAction()
data class SetDataToSend(val transactionData: WcTransactionData) : WalletConnectAction()
data class HandlePersonalSignRequest( data class HandlePersonalSignRequest(
val message: WCEthereumSignMessage, val message: WCEthereumSignMessage,
val session: WalletConnectSession, val session: WalletConnectSession,

View file

@ -73,9 +73,7 @@ data class WalletForSession(
} else if (other.derivedPublicKey != null) return false } else if (other.derivedPublicKey != null) return false
if (derivationPath != other.derivationPath) return false if (derivationPath != other.derivationPath) return false
if (isTestNet != other.isTestNet) return false if (isTestNet != other.isTestNet) return false
if (blockchain != other.blockchain) return false return blockchain == other.blockchain
return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {

View file

@ -122,7 +122,6 @@ internal data class SocialNetworkLink(
internal sealed class EventError { internal sealed class EventError {
object Empty : EventError() object Empty : EventError()
data class DemoReferralNotAvailable(val onErrorShow: () -> Unit) : EventError()
} }
sealed class SocialNetwork(val id: String, val iconRes: Int) { sealed class SocialNetwork(val id: String, val iconRes: Int) {

View file

@ -1,10 +1,6 @@
package com.tangem.tap.features.home package com.tangem.tap.features.home
import android.content.Context
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.PHONE_TYPE_CDMA
import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.intl.Locale
import java.lang.ref.WeakReference
/** /**
[REDACTED_AUTHOR] [REDACTED_AUTHOR]
@ -13,39 +9,8 @@ interface RegionProvider {
fun getRegion(): String? fun getRegion(): String?
} }
class RegionService(
private val providers: List<RegionProvider>,
) : RegionProvider {
override fun getRegion(): String? {
for (provider in providers) {
val region = provider.getRegion()
if (region != null) return region
}
return null
}
}
class TelephonyManagerRegionProvider(context: Context) : RegionProvider {
private val wContext: WeakReference<Context> = WeakReference(context)
override fun getRegion(): String? {
val tm = wContext.get()?.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager ?: return null
val region = when (tm.phoneType) {
PHONE_TYPE_CDMA -> {
// Result may be unreliable
tm.networkCountryIso
}
else -> tm.networkCountryIso
}
return region.ifEmpty { return null }
}
}
class LocaleRegionProvider : RegionProvider { class LocaleRegionProvider : RegionProvider {
override fun getRegion(): String = Locale.current.region override fun getRegion(): String = Locale.current.region
} }
const val RUSSIA_COUNTRY_CODE = "ru" const val RUSSIA_COUNTRY_CODE = "ru"
const val BELARUS_COUNTRY_CODE = "by"

View file

@ -37,7 +37,6 @@ import timber.log.Timber
object HomeMiddleware { object HomeMiddleware {
val handler = homeMiddleware val handler = homeMiddleware
const val BUY_WALLET_URL = "https://tangem.com/ru/resellers/"
const val NEW_BUY_WALLET_URL = "https://buy.tangem.com/" const val NEW_BUY_WALLET_URL = "https://buy.tangem.com/"
} }

View file

@ -3,7 +3,6 @@ package com.tangem.tap.features.home.redux
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.entities.ProgressState
import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.send.redux.states.ButtonState
import org.rekotlin.StateType import org.rekotlin.StateType
import java.util.Locale import java.util.Locale
@ -17,9 +16,6 @@ data class HomeState(
val firstStory: Stories val firstStory: Stories
get() = stories[0] get() = stories[0]
val btnScanStateInProgress: Boolean
get() = btnScanState.progressState == ProgressState.Loading
fun stepOf(story: Stories): Int = stories.indexOf(story) fun stepOf(story: Stories): Int = stories.indexOf(story)
fun onCountryCodeUpdate(homeState: HomeState, countryCode: String) { fun onCountryCodeUpdate(homeState: HomeState, countryCode: String) {

View file

@ -15,15 +15,11 @@ class IntentProcessor {
intentHandlers.add(handler) intentHandlers.add(handler)
} }
fun removeIntentHandler(handler: IntentHandler) {
intentHandlers.remove(handler)
}
fun removeAll() { fun removeAll() {
intentHandlers.clear() intentHandlers.clear()
} }
suspend fun handleIntent(intent: Intent?) { fun handleIntent(intent: Intent?) {
intentHandlers.forEach { intentHandlers.forEach {
it.handleIntent(intent) it.handleIntent(intent)
} }

View file

@ -14,9 +14,9 @@ import com.tangem.tap.common.extensions.isPositive
import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.domain.model.hasPendingTransactions
import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.hasPendingTransactions
import timber.log.Timber import timber.log.Timber
import java.math.BigDecimal import java.math.BigDecimal
@ -25,11 +25,10 @@ import java.math.BigDecimal
*/ */
class OnboardingManager( class OnboardingManager(
var scanResponse: ScanResponse, var scanResponse: ScanResponse,
val usedCardsPrefStorage: UsedCardsPrefStorage, private val usedCardsPrefStorage: UsedCardsPrefStorage,
) { ) {
var cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null private var cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null
private set
suspend fun loadArtworkUrl(): String { suspend fun loadArtworkUrl(): String {
val cardInfo = cardInfo val cardInfo = cardInfo
@ -85,10 +84,6 @@ class OnboardingManager(
usedCardsPrefStorage.activationFinished(cardId) usedCardsPrefStorage.activationFinished(cardId)
} }
fun isActivationFinished(cardId: String): Boolean {
return usedCardsPrefStorage.isActivationFinished(cardId)
}
fun isActivationStarted(cardId: String): Boolean { fun isActivationStarted(cardId: String): Boolean {
return usedCardsPrefStorage.isActivationStarted(cardId) return usedCardsPrefStorage.isActivationStarted(cardId)
} }

View file

@ -96,69 +96,69 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
} }
private fun setupNoneState() = with(mainBinding.onboardingActionContainer) { private fun setupNoneState() = with(mainBinding.onboardingActionContainer) {
btnMainAction.isVisible = false btnMainAction?.isVisible = false
btnAlternativeAction.isVisible = false btnAlternativeAction?.isVisible = false
btnRefreshBalanceWidget.show(false) btnRefreshBalanceWidget.show(false)
tvHeader.isVisible = false tvHeader.isVisible = false
tvBody.isVisible = false tvBody?.isVisible = false
btnMainAction.text = "" btnMainAction?.text = ""
btnMainAction.icon = null btnMainAction?.icon = null
btnAlternativeAction.text = "" btnAlternativeAction?.text = ""
btnAlternativeAction.icon = null btnAlternativeAction?.icon = null
tvHeader.text = "" tvHeader.text = ""
tvBody.text = "" tvBody?.text = ""
} }
private fun setupCreateWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) { private fun setupCreateWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet) btnMainAction?.setText(R.string.onboarding_create_wallet_button_create_wallet)
btnMainAction.setIconResource(R.drawable.ic_tangem_24) btnMainAction?.setIconResource(R.drawable.ic_tangem_24)
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
store.dispatch(OnboardingNoteAction.CreateWallet) store.dispatch(OnboardingNoteAction.CreateWallet)
} }
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean) btnAlternativeAction?.setText(R.string.onboarding_button_what_does_it_mean)
btnAlternativeAction.setOnClickListener { } btnAlternativeAction?.setOnClickListener { }
btnRefreshBalanceWidget.mainView.setOnClickListener(null) btnRefreshBalanceWidget.mainView.setOnClickListener(null)
tvHeader.setText(R.string.onboarding_create_wallet_header) tvHeader.setText(R.string.onboarding_create_wallet_header)
tvBody.setText(R.string.onboarding_create_wallet_body) tvBody?.setText(R.string.onboarding_create_wallet_body)
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable( mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
requireContext().getDrawableCompat(R.drawable.shape_circle), requireContext().getDrawableCompat(R.drawable.shape_circle),
) )
updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet) updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet)
btnAlternativeAction.isVisible = false // temporary btnAlternativeAction?.isVisible = false // temporary
} }
private fun setupTopUpWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) { private fun setupTopUpWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
if (state.isBuyAllowed) { if (state.isBuyAllowed) {
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto) btnMainAction?.setText(R.string.onboarding_top_up_button_but_crypto)
btnMainAction.icon = null btnMainAction?.icon = null
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
store.dispatch(OnboardingNoteAction.TopUp) store.dispatch(OnboardingNoteAction.TopUp)
} }
btnAlternativeAction.isVisible = true btnAlternativeAction?.isVisible = true
btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address) btnAlternativeAction?.setText(R.string.onboarding_top_up_button_show_wallet_address)
btnAlternativeAction.setOnClickListener { btnAlternativeAction?.setOnClickListener {
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog) store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
} }
} else { } else {
btnMainAction.setText(R.string.onboarding_button_receive_crypto) btnMainAction?.setText(R.string.onboarding_button_receive_crypto)
btnMainAction.icon = null btnMainAction?.icon = null
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog) store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
} }
btnAlternativeAction.isVisible = false btnAlternativeAction?.isVisible = false
} }
tvHeader.setText(R.string.onboarding_topup_title) tvHeader.setText(R.string.onboarding_topup_title)
if (state.balanceNonCriticalError == null) { if (state.balanceNonCriticalError == null) {
tvBody.setText(R.string.onboarding_top_up_body) tvBody?.setText(R.string.onboarding_top_up_body)
} else { } else {
state.walletBalance.amountToCreateAccount?.let { amount -> state.walletBalance.amountToCreateAccount?.let { amount ->
val tvBodyMessage = getString( val tvBodyMessage = getString(
@ -166,7 +166,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
amount, amount,
state.walletBalance.currency.currencySymbol, state.walletBalance.currency.currencySymbol,
) )
tvBody.text = tvBodyMessage tvBody?.text = tvBodyMessage
} }
} }
@ -183,20 +183,20 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
} }
private fun setupDoneState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) { private fun setupDoneState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
btnMainAction.setText(R.string.common_continue) btnMainAction?.setText(R.string.common_continue)
btnMainAction.icon = null btnMainAction?.icon = null
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
showConfetti(false) showConfetti(false)
store.dispatch(OnboardingNoteAction.Done) store.dispatch(OnboardingNoteAction.Done)
} }
btnAlternativeAction.isVisible = false btnAlternativeAction?.isVisible = false
btnAlternativeAction.text = "" btnAlternativeAction?.text = ""
btnAlternativeAction.setOnClickListener { } btnAlternativeAction?.setOnClickListener { }
btnRefreshBalanceWidget.mainView.setOnClickListener(null) btnRefreshBalanceWidget.mainView.setOnClickListener(null)
tvHeader.setText(R.string.onboarding_done_header) tvHeader.setText(R.string.onboarding_done_header)
tvBody.setText(R.string.onboarding_done_body) tvBody?.setText(R.string.onboarding_done_body)
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable( mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8), requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8),

View file

@ -15,11 +15,11 @@ import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.scope import com.tangem.tap.scope
import com.tangem.tap.store import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager import com.tangem.tap.tangemSdkManager

View file

@ -71,19 +71,19 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
} }
private fun setupCreateWalletState() = with(mainBinding.onboardingActionContainer) { private fun setupCreateWalletState() = with(mainBinding.onboardingActionContainer) {
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet) btnMainAction?.setText(R.string.onboarding_create_wallet_button_create_wallet)
btnMainAction.setIconResource(R.drawable.ic_tangem_24) btnMainAction?.setIconResource(R.drawable.ic_tangem_24)
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
store.dispatch(OnboardingOtherCardsAction.CreateWallet) store.dispatch(OnboardingOtherCardsAction.CreateWallet)
} }
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean) btnAlternativeAction?.setText(R.string.onboarding_button_what_does_it_mean)
btnAlternativeAction.setOnClickListener { } btnAlternativeAction?.setOnClickListener { }
tvHeader.setText(R.string.onboarding_create_wallet_header) tvHeader.setText(R.string.onboarding_create_wallet_header)
tvBody.setText(R.string.onboarding_create_wallet_body) tvBody?.setText(R.string.onboarding_create_wallet_body)
btnAlternativeAction.isVisible = false // temporary btnAlternativeAction?.isVisible = false // temporary
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable( mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
requireContext().getDrawableCompat(R.drawable.shape_circle), requireContext().getDrawableCompat(R.drawable.shape_circle),
@ -92,19 +92,19 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
} }
private fun setupDoneState() = with(mainBinding.onboardingActionContainer) { private fun setupDoneState() = with(mainBinding.onboardingActionContainer) {
btnMainAction.setText(R.string.common_continue) btnMainAction?.setText(R.string.common_continue)
btnMainAction.icon = null btnMainAction?.icon = null
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
showConfetti(false) showConfetti(false)
store.dispatch(OnboardingOtherCardsAction.Done) store.dispatch(OnboardingOtherCardsAction.Done)
} }
btnAlternativeAction.isVisible = false btnAlternativeAction?.isVisible = false
btnAlternativeAction.text = "" btnAlternativeAction?.text = ""
btnAlternativeAction.setOnClickListener { } btnAlternativeAction?.setOnClickListener { }
tvHeader.setText(R.string.onboarding_done_header) tvHeader.setText(R.string.onboarding_done_header)
tvBody.setText(R.string.onboarding_done_body) tvBody?.setText(R.string.onboarding_done_body)
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable( mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8), requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8),

View file

@ -21,11 +21,11 @@ import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.preferencesStorage import com.tangem.tap.preferencesStorage
import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope import com.tangem.tap.scope

View file

@ -187,14 +187,14 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
pbBinding.pbState.hide() pbBinding.pbState.hide()
tvHeader.setText(R.string.twins_onboarding_subtitle) tvHeader.setText(R.string.twins_onboarding_subtitle)
tvBody.text = getString( tvBody?.text = getString(
R.string.twins_onboarding_description_format, R.string.twins_onboarding_description_format,
state.cardNumber?.pairIndexNumber(), state.cardNumber?.pairIndexNumber(),
) )
btnMainAction.setText(R.string.common_continue) btnMainAction?.setText(R.string.common_continue)
btnMainAction.icon = null btnMainAction?.icon = null
btnMainAction.setOnClickListener { onMainButtonClick() } btnMainAction?.setOnClickListener { onMainButtonClick() }
} }
private fun setupWarningState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) { private fun setupWarningState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
@ -202,18 +202,18 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide() mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
pbBinding.pbState.hide() pbBinding.pbState.hide()
chbUnderstand.show() chbUnderstand?.show()
tvHeader.setText(R.string.common_warning) tvHeader.setText(R.string.common_warning)
tvBody.setText(R.string.twins_recreate_warning) tvBody?.setText(R.string.twins_recreate_warning)
chbUnderstand.setOnCheckedChangeListener { buttonView, isChecked -> chbUnderstand?.setOnCheckedChangeListener { buttonView, isChecked ->
store.dispatch(TwinCardsAction.SetUserUnderstand(isChecked)) store.dispatch(TwinCardsAction.SetUserUnderstand(isChecked))
} }
btnMainAction.isEnabled = state.userWasUnderstandIfWalletRecreate btnMainAction?.isEnabled = state.userWasUnderstandIfWalletRecreate
btnMainAction.setText(R.string.common_continue) btnMainAction?.setText(R.string.common_continue)
btnMainAction.icon = null btnMainAction?.icon = null
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet)) store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet))
} }
} }
@ -223,7 +223,7 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleLarge.hide() mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleLarge.hide()
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMedium.hide() mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMedium.hide()
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMin.hide() mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMin.hide()
chbUnderstand.hide() chbUnderstand?.hide()
pbBinding.pbState.show() pbBinding.pbState.show()
mainBinding.onboardingTopContainer.onboardingWalletContainer.beginDelayedTransition() mainBinding.onboardingTopContainer.onboardingWalletContainer.beginDelayedTransition()
@ -251,11 +251,11 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
val twinIndexNumber = state.cardNumber?.indexNumber() val twinIndexNumber = state.cardNumber?.indexNumber()
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber) tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
tvBody.setText(R.string.onboarding_twins_interrupt_warning) tvBody?.setText(R.string.onboarding_twins_interrupt_warning)
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber) btnMainAction?.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
btnMainAction.setIconResource(R.drawable.ic_tangem_24) btnMainAction?.setIconResource(R.drawable.ic_tangem_24)
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
store.dispatch( store.dispatch(
TwinCardsAction.Wallet.LaunchFirstStep( TwinCardsAction.Wallet.LaunchFirstStep(
@ -265,8 +265,8 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
) )
} }
btnAlternativeAction.isVisible = false btnAlternativeAction?.isVisible = false
btnAlternativeAction.isClickable = false btnAlternativeAction?.isClickable = false
} }
private fun setupCreateSecondWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) { private fun setupCreateSecondWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
@ -274,12 +274,12 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
val twinPairIndexNumber = state.cardNumber?.pairIndexNumber() val twinPairIndexNumber = state.cardNumber?.pairIndexNumber()
tvHeader.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber) tvHeader.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber)
tvBody.setText(R.string.onboarding_twins_interrupt_warning) tvBody?.setText(R.string.onboarding_twins_interrupt_warning)
btnMainAction.text = btnMainAction?.text =
getString(R.string.twins_recreate_button_format, twinPairIndexNumber) getString(R.string.twins_recreate_button_format, twinPairIndexNumber)
btnMainAction.setIconResource(R.drawable.ic_tangem_24) btnMainAction?.setIconResource(R.drawable.ic_tangem_24)
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
store.dispatch( store.dispatch(
TwinCardsAction.Wallet.LaunchSecondStep( TwinCardsAction.Wallet.LaunchSecondStep(
Message( Message(
@ -300,11 +300,11 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
val twinIndexNumber = state.cardNumber?.indexNumber() val twinIndexNumber = state.cardNumber?.indexNumber()
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber) tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
tvBody.setText(R.string.onboarding_twins_interrupt_warning) tvBody?.setText(R.string.onboarding_twins_interrupt_warning)
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber) btnMainAction?.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
btnMainAction.setIconResource(R.drawable.ic_tangem_24) btnMainAction?.setIconResource(R.drawable.ic_tangem_24)
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
store.dispatch( store.dispatch(
TwinCardsAction.Wallet.LaunchThirdStep( TwinCardsAction.Wallet.LaunchThirdStep(
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)), Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
@ -337,27 +337,27 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
} }
if (state.isBuyAllowed) { if (state.isBuyAllowed) {
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto) btnMainAction?.setText(R.string.onboarding_top_up_button_but_crypto)
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
store.dispatch(TwinCardsAction.TopUp) store.dispatch(TwinCardsAction.TopUp)
} }
btnAlternativeAction.isVisible = true btnAlternativeAction?.isVisible = true
btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address) btnAlternativeAction?.setText(R.string.onboarding_top_up_button_show_wallet_address)
btnAlternativeAction.setOnClickListener { btnAlternativeAction?.setOnClickListener {
store.dispatch(TwinCardsAction.ShowAddressInfoDialog) store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
} }
} else { } else {
btnMainAction.setText(R.string.onboarding_button_receive_crypto) btnMainAction?.setText(R.string.onboarding_button_receive_crypto)
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
store.dispatch(TwinCardsAction.ShowAddressInfoDialog) store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
} }
btnAlternativeAction.isVisible = false btnAlternativeAction?.isVisible = false
} }
tvHeader.setText(R.string.onboarding_topup_title) tvHeader.setText(R.string.onboarding_topup_title)
tvBody.setText(R.string.onboarding_top_up_body) tvBody?.setText(R.string.onboarding_top_up_body)
btnMainAction.icon = null btnMainAction?.icon = null
btnRefreshBalanceWidget.changeState(state.walletBalance.state) btnRefreshBalanceWidget.changeState(state.walletBalance.state)
if (btnRefreshBalanceWidget.isShowing != true) { if (btnRefreshBalanceWidget.isShowing != true) {
@ -375,18 +375,18 @@ class TwinsCardsFragment : BaseOnboardingFragment<TwinCardsState>() {
} }
private fun setupDoneState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) { private fun setupDoneState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
btnMainAction.setText(R.string.common_continue) btnMainAction?.setText(R.string.common_continue)
btnMainAction.icon = null btnMainAction?.icon = null
btnMainAction.setOnClickListener { btnMainAction?.setOnClickListener {
store.dispatch(TwinCardsAction.Confetti.Hide) store.dispatch(TwinCardsAction.Confetti.Hide)
store.dispatch(TwinCardsAction.Done) store.dispatch(TwinCardsAction.Done)
} }
btnAlternativeAction.isVisible = false btnAlternativeAction?.isVisible = false
btnAlternativeAction.isClickable = false btnAlternativeAction?.isClickable = false
tvHeader.setText(R.string.onboarding_done_header) tvHeader.setText(R.string.onboarding_done_header)
tvBody.setText(R.string.onboarding_done_body) tvBody?.setText(R.string.onboarding_done_body)
val layout = when (state.mode) { val layout = when (state.mode) {
CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins

View file

@ -250,7 +250,7 @@ class TestBackupAnimation(
} }
@Suppress("MagicNumber") @Suppress("MagicNumber")
fun setStep(step: Int, onStepUpdate: (Int) -> Unit = {}) { private fun setStep(step: Int, onStepUpdate: (Int) -> Unit = {}) {
steps = step steps = step
when (steps) { when (steps) {
0 -> setupCreateWalletState() 0 -> setupCreateWalletState()

View file

@ -140,7 +140,7 @@ class OnboardingWalletFragment :
} }
private fun initCardsWidget(leapfrogWidget: LeapfrogWidget, deviceScaleFactor: Float, isTest: Boolean = false) { private fun initCardsWidget(leapfrogWidget: LeapfrogWidget, deviceScaleFactor: Float, isTest: Boolean = false) {
cardsWidget = WalletCardsWidget(leapfrogWidget, deviceScaleFactor) { 200f * deviceScaleFactor } cardsWidget = WalletCardsWidget(leapfrogWidget, deviceScaleFactor)
animator = if (isTest) { animator = if (isTest) {
TestBackupAnimation(WalletBackupAnimator(cardsWidget), binding) TestBackupAnimation(WalletBackupAnimator(cardsWidget), binding)
} else { } else {

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