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()
}
fun attach() {
private fun attach() {
if (isAttached) {
Timber.d("Already attached")
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)
}
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) {
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.BottomSheetDialog
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.store
import com.tangem.wallet.BuildConfig
/**
[REDACTED_AUTHOR]
@ -22,18 +19,6 @@ object TestActions {
// It used only for the test actions in debug or debug_beta builds
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>

View file

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

View file

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

View file

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

View file

@ -1,9 +1,9 @@
package com.tangem.tap.common.analytics.converters
import com.shopify.buy3.Storefront
import com.tangem.common.Converter
import com.tangem.tap.common.analytics.events.Shop
import com.tangem.tap.features.shop.domain.models.ProductType
import com.tangem.utils.converter.Converter
/**
[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
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.tap.features.details.redux.SecurityOption
sealed class AnalyticsParam {
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 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)
}
@ -23,15 +21,11 @@ sealed class AnalyticsParam {
sealed class CardBalanceState(val value: String) {
object Empty : CardBalanceState("Empty")
object Full : CardBalanceState("Full")
object CustomToken : CardBalanceState("Custom Token")
object BlockchainError : CardBalanceState("Blockchain Error")
object NoRate : CardBalanceState("No Rate")
companion object
}
sealed class RateApp(val value: String) {
object Liked : RateApp("Liked")
object Disliked : RateApp("Disliked")
object Closed : RateApp("Close")
}
@ -42,7 +36,6 @@ sealed class AnalyticsParam {
sealed class UserCode(val value: String) {
object AccessCode : UserCode("Access Code")
object Passcode : UserCode("Passcode")
}
sealed class SecurityMode(val value: String) {

View file

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

View file

@ -12,23 +12,14 @@ sealed class MainScreen(
class ScreenOpened : MainScreen("Screen opened")
class ButtonScanCard : MainScreen("Button - Scan Card")
class ButtonMyWallets : MainScreen("Button - My Wallets")
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen(
event = "Enable Biometric",
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(
event = "Notice - Rate The App Button Tapped",
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
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.tap.common.analytics.events.AnalyticsParam.CurrencyType
/**
[REDACTED_AUTHOR]
@ -13,50 +12,6 @@ sealed class Token(
error: Throwable? = null,
) : 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(
event: String,
params: Map<String, String> = mapOf(),

View file

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

View file

@ -2,7 +2,6 @@ package com.tangem.tap.common.analytics.handlers.firebase
import android.os.Bundle
import androidx.core.os.bundleOf
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
@ -29,8 +28,4 @@ internal class FirebaseClient : FirebaseAnalyticsClient {
}
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() }
}
fun DpSize.halfWidth(): Dp = this.width / 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.unit.DpSize
import androidx.compose.ui.unit.dp
import com.tangem.sdk.extensions.dpToPx
import com.tangem.sdk.extensions.pxToDp
/**
@ -17,8 +16,5 @@ fun Painter.dpSize(): DpSize = DpSize(
intrinsicSize.height.pxToDp().dp,
)
@Composable
private fun Float.dpToPx(): Float = LocalContext.current.dpToPx(this)
@Composable
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
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import java.io.ByteArrayOutputStream
@Suppress("MagicNumber")
@ -9,9 +8,4 @@ fun Bitmap.toByteArray(): ByteArray {
val stream = ByteArrayOutputStream()
this.compress(Bitmap.CompressFormat.JPEG, 20, stream)
return stream.toByteArray()
}
@Suppress("MagicNumber")
fun ByteArray.toBitmap(): Bitmap {
return BitmapFactory.decodeByteArray(this, 0, this.size)
}

View file

@ -7,10 +7,6 @@ import android.net.*
import androidx.annotation.*
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
* @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
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 timber.log.Timber
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.text.NumberFormat
import java.util.*
import java.util.Locale
// TODO: move extensions to utils
fun BigDecimal.toFormattedString(
@ -29,105 +23,6 @@ fun BigDecimal.toFormattedString(
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()
// 0.00 -> 0.00
@ -146,70 +41,4 @@ fun BigDecimal.setPrecision(precision: Int, roundingMode: RoundingMode = Roundin
return this.setScale(scale() - precision() + precision, roundingMode)
}
fun BigDecimal.isPositive(): Boolean = this.compareTo(BigDecimal.ZERO) == 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)
}
}
}
fun BigDecimal.isPositive(): Boolean = this.signum() == 1

View file

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

View file

@ -1,41 +1,12 @@
package com.tangem.tap.common.extensions
import android.content.Context
import android.graphics.Bitmap
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.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import java.util.*
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,
)
}
}
import java.util.Hashtable
@Suppress("MagicNumber")
fun String.toQrCode(): Bitmap {
@ -58,8 +29,6 @@ fun String.toQrCode(): Bitmap {
return bmp
}
fun String.urlEncode(): String = Uri.encode(this)
fun String.removePrefixOrNull(prefix: String): String? = when {
startsWith(prefix) -> substring(prefix.length)
else -> null

View file

@ -1,24 +1,3 @@
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))

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
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.*
import android.graphics.drawable.Drawable
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
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.annotation.*
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import com.google.android.material.card.MaterialCardView
fun Context.getDrawableCompat(@DrawableRes drawableResId: Int): Drawable? {
return ContextCompat.getDrawable(this, drawableResId)
@ -72,48 +59,9 @@ fun View.hide(invokeBeforeStateChanged: (() -> Unit)? = null) {
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
?: (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 = "") {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return
@ -140,37 +88,6 @@ fun Context.shareText(text: String) {
startActivity(shareIntent)
}
fun Fragment.shareText(text: String) {
requireContext().shareText(text)
}
fun View.getString(resId: Int, vararg formatArgs: Any?): String {
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.View
import android.view.ViewGroup
import android.view.ViewParent
import androidx.core.view.forEach
import androidx.transition.AutoTransition
import androidx.transition.Transition
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]
@ -20,30 +14,6 @@ fun ViewGroup.inflate(viewToInflate: Int, attachToRoot: Boolean = false): View {
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()) {
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
/**
[REDACTED_AUTHOR]
*/
fun WebView.configureSettings() {
resumeTimers()
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
}
}
fun WebView.stop() {
stopLoading()
pauseTimers()

View file

@ -63,6 +63,7 @@ class ScanQrCodeActivity : AppCompatActivity() {
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode != PERMISSION_REQUEST_CODE) return
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.core.navigation.StateDialog
import com.tangem.tap.common.TestAction
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.domain.model.WalletAddressData
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.wallet.R
/**

View file

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

View file

@ -20,22 +20,14 @@ class NotificationsHandler(coordinatorLayout: CoordinatorLayout) {
private val basicCoordinatorLayout = WeakReference(coordinatorLayout)
private var baseLayout = basicCoordinatorLayout
fun replaceBaseLayout(coordinatorLayout: CoordinatorLayout) {
baseLayout = WeakReference(coordinatorLayout)
}
fun returnBaseLayout() {
baseLayout = basicCoordinatorLayout
}
fun showNotification(message: String) {
private fun showNotification(message: String) {
baseLayout.get()?.let { layout ->
Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
.also { snackbar -> snackbar.show() }
}
}
fun showDebugNotification(message: String) {
private fun showDebugNotification(message: String) {
baseLayout.get()?.let { layout ->
Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
.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.ErrorAction
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.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
@ -25,7 +24,6 @@ sealed class GlobalAction : Action {
// notifications
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 DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction

View file

@ -169,7 +169,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
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 result = if (promoCode.isBlank()) {

View file

@ -19,26 +19,7 @@ import kotlin.coroutines.suspendCoroutine
@Suppress("LargeClass")
class ShopifyService(private val application: Application, val shop: ShopifyShop) {
val client: GraphClient by lazy { initClient() }
suspend fun getShopName(): Result<String> {
val query = query { rootQuery: QueryRootQuery ->
rootQuery
.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)
}
}
}
private val client: GraphClient by lazy { initClient() }
@Suppress("MagicNumber")
suspend fun getProducts(collectionTitleFilter: String? = null): Result<List<Product>> {

View file

@ -28,12 +28,12 @@ abstract class BaseTruncate : Truncate {
protected var hasBeenTruncated = false
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)
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 textWillTakeSpace = tv.paint.measureText(text)
val overSizeRatio: Float = textWillTakeSpace / existingSpace
@ -44,7 +44,7 @@ abstract class BaseTruncate : Truncate {
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
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)
}
fun TextView.truncateStartWith(text: String, with: String = "..."): String =
this.truncateWith(text, TruncateType.START, with)
fun TextView.truncateMiddleWith(text: String, with: String = "..."): String =
this.truncateWith(text, TruncateType.MIDDLE, with)
fun TextView.truncateEndWith(text: String, with: String = "..."): String =
this.truncateWith(text, TruncateType.END, with)
this.truncateWith(text, TruncateType.MIDDLE, 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.tap.common.analytics.events.Basic
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.CreateProductWalletTaskResponse
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(
cardId: String?,
derivations: Map<ByteArrayKey, List<DerivationPath>>,

View file

@ -22,11 +22,7 @@ sealed class TapError(
object UnknownError : TapError(R.string.send_error_unknown)
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 AmountExceedsBalance : TapError(R.string.send_validation_amount_exceeds_balance)
data class AmountLowerExistentialDeposit(
@ -41,7 +37,6 @@ sealed class TapError(
object DustChange : TapError(R.string.send_error_dust_change)
sealed class WalletManager {
object CreationError : CustomError(customMessage = "Can't create wallet manager")
class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)
class InternalError(message: String) : CustomError(message)
object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later)

View file

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

View file

@ -1,8 +1,6 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.tangem.blockchain.common.Blockchain
import com.tangem.utils.extensions.removeBy
import com.tangem.wallet.R
import java.util.concurrent.CopyOnWriteArrayList
/**
@ -14,13 +12,6 @@ class WarningMessagesManager {
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> {
return warningsList.filter { message ->
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? {
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.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 {
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.TransactionStatus
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(
val transactionData: TransactionData,
@ -20,10 +14,6 @@ data class PendingTransaction(
PendingTransactionType.Unknown -> null
}
val amountValue: BigDecimal? = transactionData.amount.value
val amountValueUi: String? = amountValue?.toFormattedString(transactionData.amount.decimals)
val currency: String = transactionData.amount.currencySymbol
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) }
}
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> {
val txs = recentTransactions.toPendingTransactions(address)
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 {
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 {
val CURVES_FOR_WALLETS = listOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519)
}
private companion object
}

View file

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

View file

@ -1,9 +1 @@
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
}
package com.tangem.tap.domain.userWalletList.utils

View file

@ -1,14 +1,9 @@
package com.tangem.tap.domain.walletconnect
import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.toMap
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
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 {
private val gson: Gson by lazy {
@ -33,68 +28,4 @@ object EthSignHelper {
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.Response
import timber.log.Timber
import java.util.*
import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlin.collections.set
@ -191,7 +191,7 @@ class WalletConnectManager {
}
}
fun removeSimilarSessions(activeData: WalletConnectActiveData) {
private fun removeSimilarSessions(activeData: WalletConnectActiveData) {
val sessionsToRemove = sessions.filter {
it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true &&
it.value.peerMeta?.url == activeData.peerMeta?.url &&
@ -206,7 +206,7 @@ class WalletConnectManager {
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
activeData.client.approveRequest(id, data)
}
@ -368,7 +368,7 @@ class WalletConnectManager {
}
@Suppress("LongMethod", "ComplexMethod")
fun setListeners(client: WCClient) {
private fun setListeners(client: WCClient) {
client.onSessionRequest = { id: Long, peer: WCPeerMeta ->
Timber.d("OnSessionRequest: $peer")
val session = client.session

View file

@ -25,11 +25,6 @@ class WalletConnectRepository(val context: Application) {
saveSessions(sessions)
}
fun removeSession(session: WalletConnectSession) {
val sessions = loadSavedSessions().filterNot { it == session }
saveSessions(sessions)
}
fun removeSession(session: WCSession) {
val sessions = loadSavedSessions().filterNot { it.session == session }
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.WcBinanceTransferOrder
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.TransactionRequestDialogData
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.tap.userWalletsListManager
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.*
import timber.log.Timber
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
import com.squareup.moshi.*
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@Suppress("LongParameterList")
@JsonClass(generateAdapter = true)
@ -21,12 +22,6 @@ class WcBinanceCancelOrder(
msgs: List<Message>,
) : 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(
val refid: String,
val sender: String,

View file

@ -1,6 +1,6 @@
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.squareup.moshi.Json
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 jsonObject = JsonObject()
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>,
) : WcBinanceOrder<WcBinanceTransferOrder.Message>(accountNumber, chainId, data, memo, sequence, source, msgs) {
enum class MessageKey(val key: String) {
INPUTS("inputs"),
OUTPUTS("outputs"),
}
data class Message(
val inputs: 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 {
protected lateinit var mainView: View
private lateinit var mainView: View
override fun onCreate(savedInstanceState: Bundle?) {
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.redux.global.GlobalAction
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.tokens.legacy.redux.TokensMiddleware
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
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) {
when {
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.models.scan.ScanResponse
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.wallet.models.Currency
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.launch

View file

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

View file

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

View file

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

View file

@ -1,10 +1,6 @@
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 java.lang.ref.WeakReference
/**
[REDACTED_AUTHOR]
@ -13,39 +9,8 @@ interface RegionProvider {
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 {
override fun getRegion(): String = Locale.current.region
}
const val RUSSIA_COUNTRY_CODE = "ru"
const val BELARUS_COUNTRY_CODE = "by"
const val RUSSIA_COUNTRY_CODE = "ru"

View file

@ -37,7 +37,6 @@ import timber.log.Timber
object HomeMiddleware {
val handler = homeMiddleware
const val BUY_WALLET_URL = "https://tangem.com/ru/resellers/"
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.mutableStateOf
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.entities.ProgressState
import com.tangem.tap.features.send.redux.states.ButtonState
import org.rekotlin.StateType
import java.util.Locale
@ -17,9 +16,6 @@ data class HomeState(
val firstStory: Stories
get() = stories[0]
val btnScanStateInProgress: Boolean
get() = btnScanState.progressState == ProgressState.Loading
fun stepOf(story: Stories): Int = stories.indexOf(story)
fun onCountryCodeUpdate(homeState: HomeState, countryCode: String) {

View file

@ -15,15 +15,11 @@ class IntentProcessor {
intentHandlers.add(handler)
}
fun removeIntentHandler(handler: IntentHandler) {
intentHandlers.remove(handler)
}
fun removeAll() {
intentHandlers.clear()
}
suspend fun handleIntent(intent: Intent?) {
fun handleIntent(intent: Intent?) {
intentHandlers.forEach {
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.domain.TapError
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.wallet.models.Currency
import com.tangem.tap.features.wallet.models.hasPendingTransactions
import timber.log.Timber
import java.math.BigDecimal
@ -25,11 +25,10 @@ import java.math.BigDecimal
*/
class OnboardingManager(
var scanResponse: ScanResponse,
val usedCardsPrefStorage: UsedCardsPrefStorage,
private val usedCardsPrefStorage: UsedCardsPrefStorage,
) {
var cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null
private set
private var cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null
suspend fun loadArtworkUrl(): String {
val cardInfo = cardInfo
@ -85,10 +84,6 @@ class OnboardingManager(
usedCardsPrefStorage.activationFinished(cardId)
}
fun isActivationFinished(cardId: String): Boolean {
return usedCardsPrefStorage.isActivationFinished(cardId)
}
fun isActivationStarted(cardId: String): Boolean {
return usedCardsPrefStorage.isActivationStarted(cardId)
}

View file

@ -96,69 +96,69 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
}
private fun setupNoneState() = with(mainBinding.onboardingActionContainer) {
btnMainAction.isVisible = false
btnAlternativeAction.isVisible = false
btnMainAction?.isVisible = false
btnAlternativeAction?.isVisible = false
btnRefreshBalanceWidget.show(false)
tvHeader.isVisible = false
tvBody.isVisible = false
tvBody?.isVisible = false
btnMainAction.text = ""
btnMainAction.icon = null
btnAlternativeAction.text = ""
btnAlternativeAction.icon = null
btnMainAction?.text = ""
btnMainAction?.icon = null
btnAlternativeAction?.text = ""
btnAlternativeAction?.icon = null
tvHeader.text = ""
tvBody.text = ""
tvBody?.text = ""
}
private fun setupCreateWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
btnMainAction.setOnClickListener {
btnMainAction?.setText(R.string.onboarding_create_wallet_button_create_wallet)
btnMainAction?.setIconResource(R.drawable.ic_tangem_24)
btnMainAction?.setOnClickListener {
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
store.dispatch(OnboardingNoteAction.CreateWallet)
}
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
btnAlternativeAction.setOnClickListener { }
btnAlternativeAction?.setText(R.string.onboarding_button_what_does_it_mean)
btnAlternativeAction?.setOnClickListener { }
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
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(
requireContext().getDrawableCompat(R.drawable.shape_circle),
)
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) {
if (state.isBuyAllowed) {
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
btnMainAction.icon = null
btnMainAction.setOnClickListener {
btnMainAction?.setText(R.string.onboarding_top_up_button_but_crypto)
btnMainAction?.icon = null
btnMainAction?.setOnClickListener {
store.dispatch(OnboardingNoteAction.TopUp)
}
btnAlternativeAction.isVisible = true
btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address)
btnAlternativeAction.setOnClickListener {
btnAlternativeAction?.isVisible = true
btnAlternativeAction?.setText(R.string.onboarding_top_up_button_show_wallet_address)
btnAlternativeAction?.setOnClickListener {
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
}
} else {
btnMainAction.setText(R.string.onboarding_button_receive_crypto)
btnMainAction.icon = null
btnMainAction.setOnClickListener {
btnMainAction?.setText(R.string.onboarding_button_receive_crypto)
btnMainAction?.icon = null
btnMainAction?.setOnClickListener {
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
}
btnAlternativeAction.isVisible = false
btnAlternativeAction?.isVisible = false
}
tvHeader.setText(R.string.onboarding_topup_title)
if (state.balanceNonCriticalError == null) {
tvBody.setText(R.string.onboarding_top_up_body)
tvBody?.setText(R.string.onboarding_top_up_body)
} else {
state.walletBalance.amountToCreateAccount?.let { amount ->
val tvBodyMessage = getString(
@ -166,7 +166,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
amount,
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) {
btnMainAction.setText(R.string.common_continue)
btnMainAction.icon = null
btnMainAction.setOnClickListener {
btnMainAction?.setText(R.string.common_continue)
btnMainAction?.icon = null
btnMainAction?.setOnClickListener {
showConfetti(false)
store.dispatch(OnboardingNoteAction.Done)
}
btnAlternativeAction.isVisible = false
btnAlternativeAction.text = ""
btnAlternativeAction.setOnClickListener { }
btnAlternativeAction?.isVisible = false
btnAlternativeAction?.text = ""
btnAlternativeAction?.setOnClickListener { }
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
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(
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.global.GlobalAction
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.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager

View file

@ -71,19 +71,19 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
}
private fun setupCreateWalletState() = with(mainBinding.onboardingActionContainer) {
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
btnMainAction.setOnClickListener {
btnMainAction?.setText(R.string.onboarding_create_wallet_button_create_wallet)
btnMainAction?.setIconResource(R.drawable.ic_tangem_24)
btnMainAction?.setOnClickListener {
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
store.dispatch(OnboardingOtherCardsAction.CreateWallet)
}
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
btnAlternativeAction.setOnClickListener { }
btnAlternativeAction?.setText(R.string.onboarding_button_what_does_it_mean)
btnAlternativeAction?.setOnClickListener { }
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(
requireContext().getDrawableCompat(R.drawable.shape_circle),
@ -92,19 +92,19 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCards
}
private fun setupDoneState() = with(mainBinding.onboardingActionContainer) {
btnMainAction.setText(R.string.common_continue)
btnMainAction.icon = null
btnMainAction.setOnClickListener {
btnMainAction?.setText(R.string.common_continue)
btnMainAction?.icon = null
btnMainAction?.setOnClickListener {
showConfetti(false)
store.dispatch(OnboardingOtherCardsAction.Done)
}
btnAlternativeAction.isVisible = false
btnAlternativeAction.text = ""
btnAlternativeAction.setOnClickListener { }
btnAlternativeAction?.isVisible = false
btnAlternativeAction?.text = ""
btnAlternativeAction?.setOnClickListener { }
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(
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.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.preferencesStorage
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope

View file

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

View file

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

View file

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

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