Updated on 2026-08-14
This commit is contained in:
commit
422f184c8f
39 changed files with 715 additions and 205 deletions
|
|
@ -4,6 +4,11 @@ apply plugin: 'kotlin-kapt'
|
|||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
|
||||
ext.configVar = [
|
||||
configEnvironment: "CONFIG_ENVIRONMENT",
|
||||
testActionEnabled: "TEST_ACTION_ENABLED",
|
||||
]
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
defaultConfig {
|
||||
|
|
@ -13,19 +18,14 @@ android {
|
|||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
buildConfigField 'Boolean', configVar.testActionEnabled, 'false'
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
viewBinding true
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
debuggable false
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.debug
|
||||
buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"prod\"'
|
||||
}
|
||||
debug {
|
||||
debuggable true
|
||||
minifyEnabled false
|
||||
|
|
@ -34,14 +34,24 @@ android {
|
|||
firebaseCrashlytics {
|
||||
mappingFileUploadEnabled false
|
||||
}
|
||||
buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"dev\"'
|
||||
buildConfigField 'String', configVar.configEnvironment, '\"dev\"'
|
||||
buildConfigField 'Boolean', configVar.testActionEnabled, 'true'
|
||||
}
|
||||
debug_beta {
|
||||
initWith release
|
||||
debuggable false
|
||||
versionNameSuffix "-beta"
|
||||
applicationIdSuffix ".debug"
|
||||
buildConfigField 'String', 'CONFIG_ENVIRONMENT', '\"prod\"'
|
||||
buildConfigField 'String', configVar.configEnvironment, '\"prod\"'
|
||||
buildConfigField 'Boolean', configVar.testActionEnabled, 'false'
|
||||
}
|
||||
release {
|
||||
debuggable false
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.debug
|
||||
buildConfigField 'String', configVar.configEnvironment, '\"prod\"'
|
||||
buildConfigField 'Boolean', configVar.testActionEnabled, 'false'
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"tokens" : [
|
||||
{
|
||||
"id" : "polygon/test",
|
||||
"id" : "polygon-pos/test",
|
||||
"symbol" : "MATIC",
|
||||
"name" : "Polygon",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "polygon-pos",
|
||||
"networkId" : "polygon-pos/test",
|
||||
"address" : "0x0000000000000000000000000000000000001010",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -18,17 +18,17 @@
|
|||
"name" : "Dai Stablecoin",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "polygon-pos",
|
||||
"networkId" : "polygon-pos/test",
|
||||
"address" : "0xcB1e72786A6eb3b44C2a2429e317c8a2462CFeb1",
|
||||
"decimalCount" : 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain",
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"address" : "0xec5dcb5dbf4b114c9d0f65bccab49ec54f6a0867",
|
||||
"decimalCount" : 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain",
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"address" : "0x8a9424745056eb399fd19a0ec26a14316684e274",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
"name" : "Dummy ERC20",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "polygon-pos",
|
||||
"networkId" : "polygon-pos/test",
|
||||
"address" : "0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -52,12 +52,12 @@
|
|||
"name" : "Ether",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "polygon-pos",
|
||||
"networkId" : "polygon-pos/test",
|
||||
"address" : "0x714550C2C1Ea08688607D86ed8EeF4f5E4F22323",
|
||||
"decimalCount" : 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain",
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"address" : "0xd66c6b4f0be8ce5b39d52e0fd1344c389929b378",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
"name" : "Test Token",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "polygon-pos",
|
||||
"networkId" : "polygon-pos/test",
|
||||
"address" : "0x2d7882bedcbfddce29ba99965dd3cdf7fcb10a1e",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -81,17 +81,17 @@
|
|||
"name" : "Tether USD",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "polygon-pos",
|
||||
"networkId" : "polygon-pos/test",
|
||||
"address" : "0x3813e82e6f7098b9583FC0F33a962D02018B6803",
|
||||
"decimalCount" : 6
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain",
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"address" : "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd",
|
||||
"decimalCount" : 18
|
||||
},
|
||||
{
|
||||
"networkId" : "binance-smart-chain",
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"address" : "0x7ef95a0fee0dd31b22626fa2e10ee6a223f8a684",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
"name" : "Wrapped Ether",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "polygon-pos",
|
||||
"networkId" : "polygon-pos/test",
|
||||
"address" : "0xA6FA4fB5f76172d178d61B04b0ecd319C5d1C0aa",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
"name" : "Wrapped Matic",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "polygon-pos",
|
||||
"networkId" : "polygon-pos/test",
|
||||
"address" : "0xd0A1E359811322d97991E03f863a0C30C2cF029C",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -135,7 +135,7 @@
|
|||
"name" : "Tangem Coin A",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "solana",
|
||||
"networkId" : "solana/test",
|
||||
"address" : "22PTNbX31Zuztd6nD82fC8nQdT2hUfWv9XWXKuDkrFqR",
|
||||
"decimalCount" : 9
|
||||
}
|
||||
|
|
@ -147,14 +147,14 @@
|
|||
"name" : "Tangem Coin B",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "solana",
|
||||
"networkId" : "solana/test",
|
||||
"address" : "HmSghNPg6KCk711YJA92aPejt8auyFkvmaED6jbHfUs4",
|
||||
"decimalCount" : 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "binance/test",
|
||||
"id" : "binancecoin/test",
|
||||
"symbol" : "BNB",
|
||||
"name" : "Binance",
|
||||
"contracts" : [
|
||||
|
|
@ -167,14 +167,14 @@
|
|||
"name" : "Hemster - 452",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "binancecoin",
|
||||
"networkId" : "binancecoin/test",
|
||||
"address" : "HEM-452",
|
||||
"decimalCount" : 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id" : "bsc/test",
|
||||
"id" : "binance-smart-chain/test",
|
||||
"symbol" : "BNB",
|
||||
"name" : "Bsc",
|
||||
"contracts" : [
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
"name" : "Binance-Peg BTCB Token",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "binance-smart-chain",
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"address" : "0x6ce8da28e2f864420840cf74474eff5fd80e65b8",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -199,7 +199,7 @@
|
|||
"name" : "Binance-Peg BUSD Token",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "binance-smart-chain",
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"address" : "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -211,7 +211,7 @@
|
|||
"name" : "Binance-Peg USDC Token",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "binance-smart-chain",
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"address" : "0x64544969ed7ebf5f083679233325356ebe738930",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -223,7 +223,7 @@
|
|||
"name" : "Binance-Peg XRP",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "binance-smart-chain",
|
||||
"networkId" : "binance-smart-chain/test",
|
||||
"address" : "0xa83575490d7df4e2f47b7d38ef351a2722ca45b9",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -255,7 +255,7 @@
|
|||
"name" : "Xeenus",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "ethereum",
|
||||
"networkId" : "ethereum/test",
|
||||
"address" : "0x022E292b44B5a146F2e8ee36Ff44D3dd863C915c",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -267,7 +267,7 @@
|
|||
"name" : "Yeenus",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "ethereum",
|
||||
"networkId" : "ethereum/test",
|
||||
"address" : "0xc6fDe3FD2Cc2b173aEC24cc3f267cb3Cd78a26B7",
|
||||
"decimalCount" : 8
|
||||
}
|
||||
|
|
@ -279,7 +279,7 @@
|
|||
"name" : "Zeenus",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "ethereum",
|
||||
"networkId" : "ethereum/test",
|
||||
"address" : "0x1f9061B953bBa0E36BF50F21876132DcF276fC6e",
|
||||
"decimalCount" : 0
|
||||
}
|
||||
|
|
@ -299,7 +299,7 @@
|
|||
"name" : "The Fuji stablecoin",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "avalanche",
|
||||
"networkId" : "avalanche/test",
|
||||
"address" : "0x2058ec2791dD28b6f67DB836ddf87534F4Bbdf22",
|
||||
"decimalCount" : 6
|
||||
}
|
||||
|
|
@ -311,7 +311,7 @@
|
|||
"name" : "To the Moon",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "avalanche",
|
||||
"networkId" : "avalanche/test",
|
||||
"address" : "0x97132C109c6816525F7f338DCb7435E1412A7668",
|
||||
"decimalCount" : 9
|
||||
}
|
||||
|
|
@ -331,7 +331,7 @@
|
|||
"name" : "Fantom USD",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "fantom",
|
||||
"networkId" : "fantom/test",
|
||||
"address" : "0x91ea991bd52EE3C40EdA2509701d905e1Ee54074",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
@ -343,7 +343,7 @@
|
|||
"name" : "Wrapped Fantom",
|
||||
"contracts" : [
|
||||
{
|
||||
"networkId" : "fantom",
|
||||
"networkId" : "fantom/test",
|
||||
"address" : "0xf1277d1Ed8AD466beddF92ef448A132661956621",
|
||||
"decimalCount" : 18
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context)
|
||||
is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
|
||||
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
|
||||
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
|
||||
is TwinCardsAction.Wallet.ShowInterruptDialog -> CreateWalletInterruptDialog.create(state.dialog, context)
|
||||
is WalletConnectDialog.UnsupportedCard ->
|
||||
SimpleAlertDialog.create(
|
||||
|
|
|
|||
75
app/src/main/java/com/tangem/tap/common/TestActions.kt
Normal file
75
app/src/main/java/com/tangem/tap/common/TestActions.kt
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
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]
|
||||
*/
|
||||
|
||||
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 (isTestView) view.show(BuildConfig.TEST_ACTION_ENABLED)
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
|
||||
view.setOnClickListener {
|
||||
store.dispatchDialogShow(AppDialog.TestActionsDialog(actions))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias TestAction = Pair<String, () -> Unit>
|
||||
|
||||
class TestActionsBottomSheetDialog(
|
||||
private val appDialog: AppDialog.TestActionsDialog,
|
||||
context: Context
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(createContentView())
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.peekHeight = 400
|
||||
setOnCancelListener {
|
||||
store.dispatchDialogHide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createContentView(): View {
|
||||
val actionButtons = mutableListOf<View>()
|
||||
appDialog.actionsList.forEach { (actionName, action) ->
|
||||
val view = Button(context).apply {
|
||||
text = actionName
|
||||
setOnClickListener {
|
||||
action()
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
actionButtons.add(view)
|
||||
}
|
||||
|
||||
return LinearLayoutCompat(context).apply {
|
||||
orientation = LinearLayoutCompat.VERTICAL
|
||||
actionButtons.forEach { addView(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
53
app/src/main/java/com/tangem/tap/common/compose/Warning.kt
Normal file
53
app/src/main/java/com/tangem/tap/common/compose/Warning.kt
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
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.tap.common.moduleMessage.ModuleMessageConverter
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun AddCustomTokenWarning(
|
||||
modifier: Modifier = Modifier,
|
||||
warning: ModuleMessage,
|
||||
converter: ModuleMessageConverter
|
||||
) {
|
||||
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),
|
||||
color = colorResource(id = R.color.white),
|
||||
fontSize = 13.sp,
|
||||
lineHeight = 18.sp
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.BlockchainSdkError
|
|||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.amountToCreateAccount
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
|
|
@ -20,8 +21,9 @@ import timber.log.Timber
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
suspend fun WalletManager.safeUpdate(): Result<Wallet> = try {
|
||||
if (isDemoWallet()) {
|
||||
if (isDemoWallet() || TestActions.testAmountInjectionForWalletManagerEnabled) {
|
||||
delay(500)
|
||||
TestActions.testAmountInjectionForWalletManagerEnabled = false
|
||||
Result.Success(wallet)
|
||||
} else {
|
||||
update()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.common.redux
|
||||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.tap.common.TestAction
|
||||
import com.tangem.tap.features.wallet.redux.AddressData
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
|
||||
|
|
@ -17,4 +18,8 @@ sealed class AppDialog : StateDialog {
|
|||
val currency: Currency,
|
||||
val addressData: AddressData,
|
||||
) : AppDialog()
|
||||
|
||||
data class TestActionsDialog(
|
||||
val actionsList: List<TestAction>
|
||||
) : AppDialog()
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.blockchain.blockchains.solana.RentProvider
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.services.Result
|
||||
|
|
@ -15,7 +14,6 @@ import com.tangem.tap.common.ThrottlerWithValues
|
|||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
|
|
@ -25,8 +23,6 @@ import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
|||
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.tap.features.wallet.models.getPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
|
|
@ -60,7 +56,6 @@ class TapWalletManager {
|
|||
}
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
checkForRentWarning(walletManager)
|
||||
dispatchOnMain(WalletAction.LoadWallet.Success(result.data, blockchainNetwork))
|
||||
}
|
||||
is Result.Failure -> {
|
||||
|
|
@ -329,40 +324,6 @@ class TapWalletManager {
|
|||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkForRentWarning(walletManager: WalletManager) {
|
||||
val rentProvider = walletManager as? RentProvider ?: return
|
||||
|
||||
when (val result = rentProvider.minimalBalanceForRentExemption()) {
|
||||
is com.tangem.blockchain.extensions.Result.Success -> {
|
||||
fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean =
|
||||
balance < rentExempt
|
||||
|
||||
val balance = walletManager.wallet.fundsAvailable(AmountType.Coin)
|
||||
val outgoingTxs =
|
||||
walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing)
|
||||
val rentExempt = result.data
|
||||
val show = if (outgoingTxs.isEmpty()) {
|
||||
isNeedToShowWarning(balance, rentExempt)
|
||||
} else {
|
||||
val outgoingAmount = outgoingTxs.sumOf { it.amount ?: BigDecimal.ZERO }
|
||||
val rest = balance.minus(outgoingAmount)
|
||||
isNeedToShowWarning(rest, rentExempt)
|
||||
}
|
||||
if (!show) return
|
||||
|
||||
val currency = walletManager.wallet.blockchain.currency
|
||||
dispatchOnMain(
|
||||
WalletAction.SetWalletRent(
|
||||
blockchain = BlockchainNetwork.fromWalletManager(walletManager),
|
||||
minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"),
|
||||
rentExempt = ("${rentExempt.stripZeroPlainString()} $currency")
|
||||
)
|
||||
)
|
||||
}
|
||||
is com.tangem.blockchain.extensions.Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Wallet.getFirstToken(): Token? {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ data class WarningMessage(
|
|||
val messageFormatArg: String? = null
|
||||
) {
|
||||
val blockchainList: List<Blockchain>? by lazy {
|
||||
blockchains?.map { Blockchain.fromId(it.toUpperCase()) }
|
||||
blockchains?.map { Blockchain.fromId(it.uppercase()) }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.common.extensions.VoidCallback
|
|||
import com.tangem.tap.common.extensions.containsAny
|
||||
import com.tangem.tap.common.extensions.removeBy
|
||||
import com.tangem.wallet.R
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -181,6 +182,24 @@ class WarningMessagesManager(
|
|||
WarningMessage.Origin.Local
|
||||
)
|
||||
|
||||
fun restoreFundsWarning(): WarningMessage = WarningMessage(
|
||||
title = "",
|
||||
message = "",
|
||||
type = WarningMessage.Type.Temporary,
|
||||
priority = WarningMessage.Priority.Warning,
|
||||
listOf(WarningMessage.Location.MainScreen),
|
||||
blockchains = null,
|
||||
titleResId = R.string.alert_funds_restoration_message,
|
||||
messageResId = R.string.alert_funds_restoration_message,
|
||||
origin = WarningMessage.Origin.Local,
|
||||
buttonTextId = R.string.warning_button_learn_more
|
||||
)
|
||||
|
||||
const val REMAINING_SIGNATURES_WARNING = 10
|
||||
|
||||
fun getRestoreFundsGuideUrl(locale: String): String {
|
||||
val code = if (locale == Locale("ru").language) "ru" else "en"
|
||||
return "https://tangem.com/$code/notion"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -152,7 +152,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
.map {
|
||||
val token = it.toToken()
|
||||
val id = currencies
|
||||
.find { it.contracts?.find { it.address == token.contractAddress } != null }
|
||||
.find { it.contracts.find { it.address == token.contractAddress } != null }
|
||||
?.id
|
||||
token.copy(id = id)
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
val fileName = if (isTestNet) "testnet_tokens" else "tokens"
|
||||
val json = context.assets.readJsonFileToString(fileName)
|
||||
return currenciesAdapter.fromJson(json)!!.tokens
|
||||
.map { Currency.fromJsonObject(it, isTestNet) }
|
||||
.map { Currency.fromJsonObject(it) }
|
||||
}
|
||||
|
||||
private fun loadTokensJson(blockchain: Blockchain): String? {
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ data class CurrenciesFromJson(
|
|||
)
|
||||
|
||||
|
||||
fun List<ContractFromJson>.toContracts(isTestNet: Boolean): List<Contract> {
|
||||
return mapNotNull { Contract.fromJsonObject(it, isTestNet) }
|
||||
fun List<ContractFromJson>.toContracts(): List<Contract> {
|
||||
return mapNotNull { Contract.fromJsonObject(it) }
|
||||
}
|
||||
|
||||
data class Currency(
|
||||
|
|
@ -39,23 +39,22 @@ data class Currency(
|
|||
) {
|
||||
|
||||
companion object {
|
||||
fun fromJsonObject(currency: CurrencyFromJson, isTestNet: Boolean): Currency {
|
||||
fun fromJsonObject(currency: CurrencyFromJson): Currency {
|
||||
return Currency(
|
||||
id = currency.id,
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
iconUrl = getIconUrl(currency.id),
|
||||
contracts = prepareListOfContracts(currency.contracts, currency.id, isTestNet)
|
||||
contracts = prepareListOfContracts(currency.contracts, currency.id)
|
||||
)
|
||||
}
|
||||
|
||||
private fun prepareListOfContracts(
|
||||
contractsFromJson: List<ContractFromJson>?,
|
||||
currencyId: String,
|
||||
isTestNet: Boolean
|
||||
): List<Contract> {
|
||||
val mainNetwork = Contract.fromCurrencyId(currencyId, isTestNet)
|
||||
val contracts = contractsFromJson?.toContracts(isTestNet) ?: emptyList()
|
||||
val mainNetwork = Contract.fromCurrencyId(currencyId)
|
||||
val contracts = contractsFromJson?.toContracts() ?: emptyList()
|
||||
return (listOfNotNull(mainNetwork) + contracts).distinct()
|
||||
}
|
||||
}
|
||||
|
|
@ -70,11 +69,10 @@ data class Contract(
|
|||
) {
|
||||
|
||||
companion object {
|
||||
fun fromJsonObject(contract: ContractFromJson, isTestNet: Boolean): Contract? {
|
||||
val networkId = if (isTestNet) contract.networkId + TESTNET else contract.networkId
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return null
|
||||
fun fromJsonObject(contract: ContractFromJson): Contract? {
|
||||
val blockchain = Blockchain.fromNetworkId(contract.networkId) ?: return null
|
||||
return Contract(
|
||||
networkId = networkId,
|
||||
networkId = contract.networkId,
|
||||
blockchain = blockchain,
|
||||
address = contract.address,
|
||||
decimalCount = contract.decimalCount,
|
||||
|
|
@ -82,19 +80,17 @@ data class Contract(
|
|||
)
|
||||
}
|
||||
|
||||
fun fromCurrencyId(currencyId: String, isTestNet: Boolean): Contract? {
|
||||
val networkId = if (isTestNet) currencyId + TESTNET else currencyId
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return null
|
||||
fun fromCurrencyId(currencyId: String): Contract? {
|
||||
val blockchain = Blockchain.fromNetworkId(currencyId) ?: return null
|
||||
return Contract(
|
||||
networkId = networkId,
|
||||
networkId = currencyId,
|
||||
blockchain = blockchain,
|
||||
address = blockchain.currency,
|
||||
decimalCount = blockchain.decimals(),
|
||||
iconUrl = getIconUrl(networkId)
|
||||
iconUrl = getIconUrl(currencyId)
|
||||
)
|
||||
}
|
||||
|
||||
const val TESTNET = "-testnet"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class DemoConfig {
|
|||
)
|
||||
|
||||
val demoCardIds: List<String> by lazy {
|
||||
val demoIds = (releaseDemoCardIds + testDemoCardIds).toMutableList()
|
||||
val demoIds = getReleaseIds().toMutableList()
|
||||
if (BuildConfig.DEBUG) demoIds.addAll(debugTestDemoCardIds)
|
||||
|
||||
return@lazy demoIds.distinct()
|
||||
|
|
@ -103,6 +103,11 @@ class DemoConfig {
|
|||
fun getBalance(blockchain: Blockchain): Amount = walletBalances[blockchain]?.copy()
|
||||
?: Amount(BigDecimal.ZERO, blockchain).copy()
|
||||
|
||||
private fun getReleaseIds(): List<String> {
|
||||
return (releaseDemoCardIds +
|
||||
releaseDemoCardIds_19042022).distinct()
|
||||
}
|
||||
|
||||
private val releaseDemoCardIds = mutableListOf<String>(
|
||||
// Tangem Wallet:
|
||||
"AC01000000041100",
|
||||
|
|
@ -145,14 +150,113 @@ class DemoConfig {
|
|||
"AB02000000051059",
|
||||
)
|
||||
|
||||
// https://tangem.slack.com/archives/GMXC6PP71/p1650360610759269
|
||||
private val releaseDemoCardIds_19042022 = listOf(
|
||||
// Wallet
|
||||
"AC01000000045754",
|
||||
"AC01000000041662",
|
||||
"AC01000000041647",
|
||||
"AC01000000041209",
|
||||
"AC01000000042462",
|
||||
"AC01000000041100",
|
||||
"AC01000000041621",
|
||||
"AC01000000045960",
|
||||
"AC01000000041092",
|
||||
"AC01000000041217",
|
||||
"AC01000000013489",
|
||||
"AC01000000028610",
|
||||
"AC01000000028701",
|
||||
"AC01000000028578",
|
||||
"AC01000000027281",
|
||||
"AC01000000027216",
|
||||
"AC01000000028594",
|
||||
"AC01000000028602",
|
||||
"AC01000000028636",
|
||||
"AC01000000013968",
|
||||
"AC01000000027208",
|
||||
"AC01000000013471",
|
||||
"AC01000000028586",
|
||||
"AC01000000013703",
|
||||
"AC01000000028628",
|
||||
"AC01000000028693",
|
||||
"AC01000000028685",
|
||||
"AC01000000013950",
|
||||
"AC01000000013828",
|
||||
"AC01000000013497",
|
||||
"AC01000000013836",
|
||||
"AC01000000013505",
|
||||
// Note BTC
|
||||
"AB01000000059608",
|
||||
"AB01000000046647",
|
||||
"AB01000000046571",
|
||||
"AB01000000046746",
|
||||
"AB01000000059574",
|
||||
"AB01000000046753",
|
||||
"AB01000000046605",
|
||||
"AB01000000046761",
|
||||
"AB01000000046720",
|
||||
"AB01000000046530",
|
||||
"AB01000000016475",
|
||||
"AB01000000016483",
|
||||
"AB01000000016491",
|
||||
"AB01000000020709",
|
||||
"AB01000000020717",
|
||||
"AB01000000015550",
|
||||
"AB01000000015394",
|
||||
"AB01000000016079",
|
||||
"AB01000000016087",
|
||||
"AB01000000016095",
|
||||
"AB01000000020915",
|
||||
"AB01000000017184",
|
||||
"AB01000000020907",
|
||||
"AB01000000017192",
|
||||
"AB01000000016210",
|
||||
"AB01000000016111",
|
||||
"AB01000000016103",
|
||||
"AB01000000015766",
|
||||
"AB01000000015774",
|
||||
"AB01000000015782",
|
||||
"AB01000000022598",
|
||||
"AB01000000022580",
|
||||
// Note ETH
|
||||
"AB02000000051083",
|
||||
"AB02000000051059",
|
||||
"AB02000000051158",
|
||||
"AB02000000050986",
|
||||
"AB02000000051026",
|
||||
"AB02000000050960",
|
||||
"AB02000000051042",
|
||||
"AB02000000051091",
|
||||
"AB02000000051034",
|
||||
"AB02000000051133",
|
||||
"AB02000000019924",
|
||||
"AB02000000019932",
|
||||
"AB02000000022092",
|
||||
"AB02000000022282",
|
||||
"AB02000000023983",
|
||||
"AB02000000023439",
|
||||
"AB02000000020328",
|
||||
"AB02000000020310",
|
||||
"AB02000000021565",
|
||||
"AB02000000022357",
|
||||
"AB02000000023355",
|
||||
"AB02000000022324",
|
||||
"AB02000000022100",
|
||||
"AB02000000019999",
|
||||
"AB02000000020013",
|
||||
"AB02000000020005",
|
||||
"AB02000000020021",
|
||||
"AB02000000020039",
|
||||
"AB02000000020278",
|
||||
"AB02000000020252",
|
||||
"AB02000000018652",
|
||||
"AB02000000018561",
|
||||
)
|
||||
|
||||
private val testDemoCardIds = listOf(
|
||||
"FB20000000000186", // Note ETH
|
||||
"FB10000000000196", // Note BTC
|
||||
"FB30000000000176", // Wallet
|
||||
//TODO: delete bellow ids before 3.28 release
|
||||
"AB01000000045060", // Note BTC
|
||||
"AB02000000045028", // Note ETH
|
||||
"AC79000000000004", // Wallet 4.46
|
||||
)
|
||||
|
||||
private val debugTestDemoCardIds = listOf<String>(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ sealed class OnboardingWalletAction : Action {
|
|||
sealed class BackupAction : Action {
|
||||
|
||||
object DetermineBackupStep : BackupAction()
|
||||
object IntroduceBackup : BackupAction()
|
||||
object StartBackup : BackupAction()
|
||||
object DismissBackup : BackupAction()
|
||||
|
||||
|
|
|
|||
|
|
@ -111,7 +111,15 @@ private fun handleWalletAction(action: Action) {
|
|||
val newAction = when (val backupState = backupService.currentState) {
|
||||
BackupService.State.FinalizingPrimaryCard -> BackupAction.PrepareToWritePrimaryCard
|
||||
is BackupService.State.FinalizingBackupCard -> BackupAction.PrepareToWriteBackupCard(backupState.index)
|
||||
else -> null
|
||||
else -> {
|
||||
if (walletState.backupState.backupStep == BackupStep.InitBackup ||
|
||||
walletState.backupState.backupStep == BackupStep.Finished
|
||||
) {
|
||||
BackupAction.IntroduceBackup
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
newAction?.let { store.dispatch(it) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ class BackupReducer {
|
|||
): BackupState {
|
||||
|
||||
return when (action) {
|
||||
is BackupAction.IntroduceBackup -> BackupState(
|
||||
backupStep = BackupStep.InitBackup,
|
||||
canSkipBackup = state.canSkipBackup,
|
||||
)
|
||||
BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard)
|
||||
BackupAction.StartAddingBackupCards -> {
|
||||
state.copy(backupStep = BackupStep.AddBackupCards)
|
||||
|
|
@ -104,6 +108,7 @@ class BackupReducer {
|
|||
BackupAction.OnAccessCodeDialogClosed -> {
|
||||
state.copy(backupStep = BackupStep.AddBackupCards)
|
||||
}
|
||||
BackupAction.DiscardBackup -> BackupState()
|
||||
|
||||
BackupAction.WritePrimaryCard -> state
|
||||
is BackupAction.WriteBackupCard -> state
|
||||
|
|
@ -117,7 +122,6 @@ class BackupReducer {
|
|||
is BackupAction.CheckAccessCode -> state
|
||||
BackupAction.DetermineBackupStep -> state
|
||||
BackupAction.CheckForUnfinishedBackup -> state
|
||||
BackupAction.DiscardBackup -> state
|
||||
BackupAction.ResumeBackup -> state
|
||||
BackupAction.DiscardSavedBackup -> state
|
||||
BackupAction.StartBackup -> state
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.domain.AddCustomTokenError
|
||||
import com.tangem.domain.common.form.DataField
|
||||
import com.tangem.domain.common.form.FieldId
|
||||
|
|
@ -22,10 +21,12 @@ import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
|
|||
import com.tangem.domain.features.addCustomToken.redux.ScreenState
|
||||
import com.tangem.domain.features.addCustomToken.redux.ViewStates
|
||||
import com.tangem.domain.redux.domainStore
|
||||
import com.tangem.tap.common.compose.AddCustomTokenWarning
|
||||
import com.tangem.tap.common.compose.ComposeDialogManager
|
||||
import com.tangem.tap.common.compose.ToggledRippleTheme
|
||||
import com.tangem.tap.common.compose.keyboardAsState
|
||||
import com.tangem.tap.common.moduleMessage.ModuleMessageConverter
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestAddCustomTokenActions
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
|
|
@ -51,7 +52,7 @@ fun AddCustomTokenScreen(state: MutableState<AddCustomTokenState>) {
|
|||
LazyColumn(
|
||||
contentPadding = PaddingValues(bottom = 90.dp)
|
||||
) {
|
||||
// item { AddCustomTokenDebugActions() }
|
||||
item { TestAddCustomTokenActions() }
|
||||
item {
|
||||
Surface(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
|
|
@ -110,19 +111,11 @@ fun Warnings(warnings: List<AddCustomTokenError.Warning>) {
|
|||
warnings.lastIndex -> Modifier.padding(16.dp, 8.dp, 16.dp, 16.dp)
|
||||
else -> Modifier.padding(16.dp, 8.dp, 16.dp, 0.dp)
|
||||
}
|
||||
Surface(
|
||||
AddCustomTokenWarning(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = colorResource(id = R.color.warning_warning),
|
||||
elevation = 4.dp,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = warningConverter.convert(item),
|
||||
color = colorResource(id = R.color.white),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
warning = item,
|
||||
converter = warningConverter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.features.tokens.addCustomToken.compose
|
||||
package com.tangem.tap.features.tokens.addCustomToken.compose.test
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Button
|
||||
|
|
@ -25,8 +25,8 @@ import timber.log.Timber
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun AddCustomTokenDebugActions() {
|
||||
if (!BuildConfig.DEBUG) return
|
||||
fun TestAddCustomTokenActions() {
|
||||
if (!BuildConfig.TEST_ACTION_ENABLED) return
|
||||
|
||||
Column() {
|
||||
// deep test
|
||||
|
|
@ -47,10 +47,10 @@ fun AddCustomTokenDebugActions() {
|
|||
@Composable
|
||||
private fun AllInOne() {
|
||||
// validation error
|
||||
ContractAddressButton(
|
||||
name = "invalid",
|
||||
address = "unk"
|
||||
)
|
||||
// ContractAddressButton(
|
||||
// name = "invalid",
|
||||
// address = "unk"
|
||||
// )
|
||||
// active = true
|
||||
ContractAddressButton(
|
||||
name = "true",
|
||||
|
|
@ -71,6 +71,10 @@ private fun AllInOne() {
|
|||
name = "unknown",
|
||||
address = "0x1111111111111111112111111111111111111113"
|
||||
)
|
||||
ContractAddressButton(
|
||||
name = "solana",
|
||||
address = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
|
|
@ -71,6 +72,7 @@ fun CollapsedCurrencyItem(
|
|||
if (contract.address == currency.symbol) {
|
||||
BlockchainNetworkItem(
|
||||
blockchain = Blockchain.fromNetworkId(contract.networkId),
|
||||
isMainNetwork = true,
|
||||
addedBlockchains = addedBlockchains
|
||||
)
|
||||
} else {
|
||||
|
|
@ -95,6 +97,7 @@ fun CollapsedCurrencyItem(
|
|||
} else {
|
||||
BlockchainNetworkItem(
|
||||
blockchain = blockchain,
|
||||
isMainNetwork = false,
|
||||
addedBlockchains = addedBlockchains
|
||||
)
|
||||
}
|
||||
|
|
@ -114,18 +117,39 @@ fun CollapsedCurrencyItem(
|
|||
@Composable
|
||||
fun BlockchainNetworkItem(
|
||||
blockchain: Blockchain?,
|
||||
isMainNetwork: Boolean,
|
||||
addedBlockchains: List<Blockchain>
|
||||
) {
|
||||
val added = addedBlockchains.contains(blockchain)
|
||||
val icon =
|
||||
if (added) blockchain?.getRoundIconRes() else blockchain?.getGreyedOutIconRes()
|
||||
if (icon != null) {
|
||||
Image(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.size(20.dp)
|
||||
)
|
||||
Box(Modifier
|
||||
.size(20.dp)) {
|
||||
Image(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.size(20.dp)
|
||||
)
|
||||
if (isMainNetwork) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.size(7.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(5.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0xFF1ACE80))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.size(5.dp))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -53,17 +54,36 @@ fun NetworkItem(
|
|||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
model = if (added) blockchain.getRoundIconRes() else blockchain.getGreyedOutIconRes(),
|
||||
contentDescription = blockchain.fullName,
|
||||
loading = { CurrencyPlaceholderIcon(blockchain.id) },
|
||||
error = { CurrencyPlaceholderIcon(blockchain.id) },
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.padding(start = 8.dp, top = 16.dp, bottom = 16.dp, end = 6.dp)
|
||||
.size(20.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
Box(modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(start = 8.dp, top = 16.dp, bottom = 16.dp, end = 6.dp)
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
model = if (added) blockchain.getRoundIconRes() else blockchain.getGreyedOutIconRes(),
|
||||
contentDescription = blockchain.fullName,
|
||||
loading = { CurrencyPlaceholderIcon(blockchain.id) },
|
||||
error = { CurrencyPlaceholderIcon(blockchain.id) },
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
)
|
||||
if (isBlockchain) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.size(7.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(5.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0xFF1ACE80))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
|
|
|
|||
|
|
@ -91,6 +91,8 @@ sealed class WalletAction : Action {
|
|||
}
|
||||
|
||||
class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings()
|
||||
|
||||
object RestoreFundsWarningClosed : Warnings()
|
||||
}
|
||||
|
||||
data class LoadFiatRate(
|
||||
|
|
@ -159,4 +161,6 @@ sealed class WalletAction : Action {
|
|||
val minRent: String,
|
||||
val rentExempt: String
|
||||
) : WalletAction()
|
||||
|
||||
data class RemoveWalletRent(val blockchain: BlockchainNetwork) : WalletAction()
|
||||
}
|
||||
|
|
@ -395,6 +395,9 @@ sealed interface Currency {
|
|||
return derivationPath != blockchain.derivationPath(derivationStyle)?.rawPath
|
||||
}
|
||||
|
||||
fun isBlockchain(): Boolean = this is Blockchain
|
||||
|
||||
fun isToken(): Boolean = this is Token
|
||||
|
||||
companion object {
|
||||
fun fromBlockchainNetwork(
|
||||
|
|
@ -402,13 +405,13 @@ sealed interface Currency {
|
|||
token: com.tangem.blockchain.common.Token? = null
|
||||
): Currency {
|
||||
return if (token != null) {
|
||||
Currency.Token(
|
||||
Token(
|
||||
token = token,
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationPath = blockchainNetwork.derivationPath
|
||||
)
|
||||
} else {
|
||||
Currency.Blockchain(
|
||||
Blockchain(
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationPath = blockchainNetwork.derivationPath
|
||||
)
|
||||
|
|
@ -430,7 +433,7 @@ sealed interface Currency {
|
|||
}
|
||||
|
||||
fun fromTokenWithBlockchain(tokenWithBlockchain: TokenWithBlockchain): Token {
|
||||
return Currency.Token(
|
||||
return Token(
|
||||
token = tokenWithBlockchain.token,
|
||||
blockchain = tokenWithBlockchain.blockchain,
|
||||
derivationPath = null
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.blockchains.solana.SolanaWalletManager
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.common.extensions.isZero
|
||||
|
|
@ -130,17 +129,13 @@ class MultiWalletMiddleware {
|
|||
scope.launch(Dispatchers.Main) {
|
||||
val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
|
||||
if (walletState?.getWalletData(blockchainNetwork) == null) {
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddBlockchain(
|
||||
blockchainNetwork, walletManager
|
||||
)
|
||||
)
|
||||
store.dispatch(
|
||||
WalletAction.LoadWallet.Success(
|
||||
wallet = wallet,
|
||||
blockchain = blockchainNetwork
|
||||
)
|
||||
)
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(
|
||||
blockchainNetwork, walletManager
|
||||
))
|
||||
store.dispatch(WalletAction.LoadWallet.Success(
|
||||
wallet = wallet,
|
||||
blockchain = blockchainNetwork
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ package com.tangem.tap.features.wallet.redux.middlewares
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.tangem.blockchain.blockchains.solana.RentProvider
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.isZero
|
||||
|
|
@ -20,9 +22,12 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
|||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.tap.features.wallet.models.getPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.network.NetworkStateChanged
|
||||
import com.tangem.tap.scope
|
||||
|
|
@ -34,6 +39,7 @@ import kotlinx.coroutines.launch
|
|||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
import org.rekotlin.Middleware
|
||||
import java.math.BigDecimal
|
||||
|
||||
class WalletMiddleware {
|
||||
private val tradeCryptoMiddleware = TradeCryptoMiddleware()
|
||||
|
|
@ -77,21 +83,18 @@ class WalletMiddleware {
|
|||
}
|
||||
}
|
||||
is WalletAction.LoadWallet.Success -> {
|
||||
checkForRentWarning(walletState.getWalletManager(action.blockchain))
|
||||
val coinAmount = action.wallet.amounts[AmountType.Coin]?.value
|
||||
if (coinAmount != null && !coinAmount.isZero()) {
|
||||
if (walletState.getWalletData(action.blockchain) == null) {
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddBlockchain(
|
||||
action.blockchain,
|
||||
null
|
||||
)
|
||||
)
|
||||
store.dispatch(
|
||||
WalletAction.LoadWallet.Success(
|
||||
action.wallet,
|
||||
action.blockchain
|
||||
)
|
||||
)
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(
|
||||
action.blockchain,
|
||||
null
|
||||
))
|
||||
store.dispatch(WalletAction.LoadWallet.Success(
|
||||
action.wallet,
|
||||
action.blockchain
|
||||
))
|
||||
}
|
||||
}
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline)
|
||||
|
|
@ -293,4 +296,43 @@ class WalletMiddleware {
|
|||
tokenRate = tokenRate
|
||||
)
|
||||
}
|
||||
|
||||
private fun checkForRentWarning(walletManager: WalletManager?) {
|
||||
val rentProvider = walletManager as? RentProvider ?: return
|
||||
|
||||
scope.launch {
|
||||
when (val result = rentProvider.minimalBalanceForRentExemption()) {
|
||||
is com.tangem.blockchain.extensions.Result.Success -> {
|
||||
fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean {
|
||||
return balance < rentExempt
|
||||
}
|
||||
|
||||
val balance = walletManager.wallet.fundsAvailable(AmountType.Coin)
|
||||
val outgoingTxs = walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing)
|
||||
val rentExempt = result.data
|
||||
val show = if (outgoingTxs.isEmpty()) {
|
||||
isNeedToShowWarning(balance, rentExempt)
|
||||
} else {
|
||||
val outgoingAmount = outgoingTxs.sumOf { it.amount ?: BigDecimal.ZERO }
|
||||
val rest = balance.minus(outgoingAmount)
|
||||
isNeedToShowWarning(rest, rentExempt)
|
||||
}
|
||||
|
||||
val currency = walletManager.wallet.blockchain.currency
|
||||
if (show) {
|
||||
dispatchOnMain(WalletAction.SetWalletRent(
|
||||
blockchain = BlockchainNetwork.fromWalletManager(walletManager),
|
||||
minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"),
|
||||
rentExempt = ("${rentExempt.stripZeroPlainString()} $currency")
|
||||
))
|
||||
} else {
|
||||
dispatchOnMain(WalletAction.RemoveWalletRent(
|
||||
blockchain = BlockchainNetwork.fromWalletManager(walletManager),
|
||||
))
|
||||
}
|
||||
}
|
||||
is com.tangem.blockchain.extensions.Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import com.tangem.common.card.Card
|
|||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.tap.common.analytics.AnalyticsEvent
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.isGreaterThan
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
|
|
@ -64,6 +66,9 @@ class WarningsMiddleware {
|
|||
)
|
||||
}
|
||||
}
|
||||
is WalletAction.Warnings.RestoreFundsWarningClosed -> {
|
||||
preferencesStorage.saveRestoreFundsWarningClosed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +93,9 @@ class WarningsMiddleware {
|
|||
addWarningMessage(WarningMessagesManager.testCardWarning(), autoUpdate = true)
|
||||
return@let
|
||||
}
|
||||
if (card.useOldStyleDerivation && !preferencesStorage.wasRestoreFundsWarningClosed()) {
|
||||
addWarningMessage(warning = WarningMessagesManager.restoreFundsWarning())
|
||||
}
|
||||
|
||||
showWarningLowRemainingSignaturesIfNeeded(card)
|
||||
if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) {
|
||||
|
|
@ -193,7 +201,7 @@ class WarningsMiddleware {
|
|||
}
|
||||
|
||||
private fun setWarningMessages() {
|
||||
store.dispatch(WalletAction.Warnings.Set(getWarnings()))
|
||||
store.dispatchOnMain(WalletAction.Warnings.Set(getWarnings()))
|
||||
}
|
||||
|
||||
private fun getWarnings(): List<WarningMessage> {
|
||||
|
|
|
|||
|
|
@ -314,6 +314,15 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
newState = newState.updateWalletsData(listOf(walletData))
|
||||
}
|
||||
}
|
||||
is WalletAction.RemoveWalletRent -> {
|
||||
var walletData = newState.getWalletData(action.blockchain)
|
||||
if (walletData == null) {
|
||||
newState
|
||||
} else {
|
||||
walletData = walletData.copy(warningRent = null)
|
||||
newState = newState.updateWalletsData(listOf(walletData))
|
||||
}
|
||||
}
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding
|
|||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
|
|
@ -24,6 +25,7 @@ import com.tangem.tap.features.wallet.models.PendingTransaction
|
|||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
|
||||
import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog
|
||||
import com.tangem.tap.features.wallet.ui.test.TestWalletDetails
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentWalletDetailsBinding
|
||||
|
|
@ -54,9 +56,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.walletState == newState.walletState
|
||||
}.select { it.walletState }
|
||||
state.select { it.walletState }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,12 +70,11 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
binding.toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
|
||||
|
||||
setupTransactionsRecyclerView()
|
||||
setupButtons()
|
||||
setupTestActionButton()
|
||||
}
|
||||
|
||||
|
||||
private fun setupTransactionsRecyclerView() = with(binding) {
|
||||
pendingTransactionAdapter = PendingTransactionsAdapter()
|
||||
rvPendingTransaction.layoutManager = LinearLayoutManager(requireContext())
|
||||
|
|
@ -93,6 +92,15 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
btnSell.setOnClickListener { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
|
||||
}
|
||||
|
||||
private fun setupTestActionButton() {
|
||||
view?.findViewById<View>(R.id.l_balance)?.let { view ->
|
||||
TestActions.initFor(
|
||||
view = view,
|
||||
actions = TestWalletDetails.solanaRentExemptWarning()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun newState(state: WalletState) {
|
||||
if (activity == null || view == null) return
|
||||
if (state.selectedCurrency == null) return
|
||||
|
|
@ -113,11 +121,13 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
|
||||
binding.srlWalletDetails.setOnRefreshListener {
|
||||
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
|
||||
store.dispatch(
|
||||
WalletAction.LoadWallet(
|
||||
blockchain = BlockchainNetwork(selectedWallet.currency.blockchain, selectedWallet.currency.derivationPath, emptyList())
|
||||
store.dispatch(WalletAction.LoadWallet(
|
||||
blockchain = BlockchainNetwork(
|
||||
selectedWallet.currency.blockchain,
|
||||
selectedWallet.currency.derivationPath,
|
||||
emptyList()
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,6 +175,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
}
|
||||
|
||||
private fun handleNotEnoughFundsOnMainCurrency(selectedWalletData: WalletData) = with(binding) {
|
||||
if (selectedWalletData.currency.isBlockchain()) return@with
|
||||
|
||||
if (selectedWalletData.shouldShowCoinAmountWarning()) {
|
||||
val blockchainName = selectedWalletData.currency.blockchain.fullName
|
||||
val warningMessage = requireContext().getString(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.tap.features.wallet.ui.adapters
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Rect
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
|
@ -13,6 +15,7 @@ import com.tangem.tap.common.analytics.AnalyticsEvent
|
|||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
|
||||
import com.tangem.tap.features.feedback.RateCanBeBetterEmail
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -56,7 +59,9 @@ class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHol
|
|||
|
||||
tvTitle.text = getString(warning.titleResId, warning.title)
|
||||
tvMessage.text = getString(
|
||||
resId = warning.messageResId, default = warning.message, formatArgs = warning.messageFormatArg
|
||||
resId = warning.messageResId,
|
||||
default = warning.message,
|
||||
formatArgs = warning.messageFormatArg
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -73,10 +78,13 @@ class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHol
|
|||
WarningMessage.Type.Permanent, WarningMessage.Type.TestCard -> {
|
||||
binding.groupControlsTemporary.hide()
|
||||
binding.groupControlsRating.hide()
|
||||
binding.btnClose.hide()
|
||||
}
|
||||
WarningMessage.Type.Temporary -> {
|
||||
binding.groupControlsRating.hide()
|
||||
binding.groupControlsTemporary.show()
|
||||
binding.btnClose.hide()
|
||||
|
||||
val buttonAction =
|
||||
when (warning.titleResId) {
|
||||
R.string.warning_important_security_info -> {
|
||||
|
|
@ -84,6 +92,20 @@ class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHol
|
|||
store.dispatch(WalletAction.ShowDialog.SignedHashesMultiWalletDialog)
|
||||
}
|
||||
}
|
||||
R.string.alert_funds_restoration_message -> {
|
||||
binding.btnClose.show()
|
||||
binding.btnClose.setOnClickListener {
|
||||
store.dispatch(GlobalAction.HideWarningMessage(warning))
|
||||
store.dispatch(WalletAction.Warnings.RestoreFundsWarningClosed)
|
||||
}
|
||||
val locale = ConfigurationCompat
|
||||
.getLocales(Resources.getSystem().configuration)
|
||||
.get(0)
|
||||
val url = WarningMessagesManager.getRestoreFundsGuideUrl(locale.language)
|
||||
View.OnClickListener {
|
||||
store.dispatchOpenUrl(url)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
View.OnClickListener {
|
||||
store.dispatch(GlobalAction.HideWarningMessage(warning))
|
||||
|
|
@ -99,6 +121,7 @@ class WarningMessageVH(val binding: LayoutWarningBinding) : RecyclerView.ViewHol
|
|||
WarningMessage.Type.AppRating -> {
|
||||
binding.groupControlsTemporary.hide()
|
||||
binding.groupControlsRating.show()
|
||||
binding.btnClose.show()
|
||||
val analyticsHandler = store.state.globalState.analyticsHandlers
|
||||
binding.btnClose.setOnClickListener {
|
||||
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
package com.tangem.tap.features.wallet.ui.test
|
||||
|
||||
import com.tangem.blockchain.blockchains.solana.SolanaWalletManager
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.common.TestAction
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TestWalletDetails {
|
||||
companion object {
|
||||
fun solanaRentExemptWarning(): List<TestAction> {
|
||||
val checker = SolanaRentWarningActionEmitter()
|
||||
return listOf(
|
||||
"BALANCE = 0.0" to { checker.setZeroBalance() },
|
||||
"BALANCE < 0.00089088" to { checker.setLessThanRentExempt() },
|
||||
"BALANCE > 0.00089088" to { checker.setMoreThanRentExempt() },
|
||||
"BALANCE = 0.00089087" to { checker.setLessThanRentExemptByOne() },
|
||||
"BALANCE = 0.00089088 (rent exempt)" to { checker.setForRentExemptBarrier() },
|
||||
"BALANCE = 0.00089089" to { checker.setMoreThanRentExemptByOne() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SolanaRentWarningActionEmitter {
|
||||
|
||||
private val zero = BigDecimal.ZERO
|
||||
private val one = BigDecimal(0.00000001)
|
||||
private val rentExemptBarrier = BigDecimal(0.00089088)
|
||||
private val lessThanRentExemptByOne = rentExemptBarrier.minus(one)
|
||||
private val moreThanRentExemptByOne = rentExemptBarrier.plus(one)
|
||||
private val moreThanRentExempt = rentExemptBarrier.plus(Random.nextDouble().toBigDecimal())
|
||||
private val lessThanRentExempt = rentExemptBarrier
|
||||
.minus(Random.nextDouble(0.0, rentExemptBarrier.minus(one).toDouble()).toBigDecimal())
|
||||
|
||||
fun setZeroBalance() {
|
||||
setBalance(zero)
|
||||
}
|
||||
|
||||
fun setForRentExemptBarrier() {
|
||||
setBalance(rentExemptBarrier)
|
||||
}
|
||||
|
||||
fun setLessThanRentExemptByOne() {
|
||||
setBalance(lessThanRentExemptByOne)
|
||||
}
|
||||
|
||||
fun setMoreThanRentExemptByOne() {
|
||||
setBalance(moreThanRentExemptByOne)
|
||||
}
|
||||
|
||||
fun setMoreThanRentExempt() {
|
||||
setBalance(moreThanRentExempt)
|
||||
}
|
||||
|
||||
fun setLessThanRentExempt() {
|
||||
setBalance(lessThanRentExempt)
|
||||
}
|
||||
|
||||
private fun setBalance(value: BigDecimal) {
|
||||
val amount = Amount(getBlockchain()).copy(value = value)
|
||||
getWalletManager().apply {
|
||||
TestActions.testAmountInjectionForWalletManagerEnabled = true
|
||||
wallet.setAmount(amount)
|
||||
}
|
||||
store.dispatch(WalletAction.LoadData)
|
||||
}
|
||||
|
||||
private fun getWalletManager(): SolanaWalletManager {
|
||||
return store.state.walletState.getWalletManager(BlockchainNetwork(
|
||||
blockchain = getBlockchain(),
|
||||
card = scanResponse().card
|
||||
)) as SolanaWalletManager
|
||||
}
|
||||
|
||||
private fun getBlockchain(): Blockchain = when {
|
||||
scanResponse().card.isTestCard -> Blockchain.SolanaTestnet
|
||||
else -> Blockchain.Solana
|
||||
}
|
||||
|
||||
private fun scanResponse(): ScanResponse {
|
||||
return store.state.globalState.scanResponse ?: throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
|
@ -64,12 +64,21 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
preferences.edit { putInt(APP_LAUNCH_COUNT_KEY, ++count) }
|
||||
}
|
||||
|
||||
fun wasRestoreFundsWarningClosed(): Boolean {
|
||||
return preferences.getBoolean(RESTORE_FUNDS_CLOSED_KEY, false)
|
||||
}
|
||||
|
||||
fun saveRestoreFundsWarningClosed() {
|
||||
preferences.edit { putBoolean(RESTORE_FUNDS_CLOSED_KEY, true) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFERENCES_NAME = "tapPrefs"
|
||||
private const val APP_CURRENCY_KEY = "appCurrency"
|
||||
private const val DISCLAIMER_ACCEPTED_KEY = "disclaimerAccepted"
|
||||
private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown"
|
||||
private const val APP_LAUNCH_COUNT_KEY = "launchCount"
|
||||
private const val RESTORE_FUNDS_CLOSED_KEY = "restoreFundsClosed"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="visible"
|
||||
app:constraint_referenced_ids="btn_close, btn_can_be_better, btn_really_cool" />
|
||||
app:constraint_referenced_ids="btn_can_be_better, btn_really_cool" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
<string name="currency_subtitle_expanded">Available networks</string>
|
||||
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
|
||||
<string name="contract_address_copied_message">Contract address copied!</string>
|
||||
<string name="alert_funds_restoration_message">Funds restoration instuction</string>
|
||||
|
||||
<string name="common_custom">Custom</string>
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
<string name="currency_subtitle_expanded">Available networks</string>
|
||||
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
|
||||
<string name="contract_address_copied_message">Contract address copied!</string>
|
||||
<string name="alert_funds_restoration_message">Funds restoration instuction</string>
|
||||
|
||||
<string name="common_custom">Custom</string>
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
<string name="currency_subtitle_expanded">Available networks</string>
|
||||
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
|
||||
<string name="contract_address_copied_message">Contract address copied!</string>
|
||||
<string name="alert_funds_restoration_message">Funds restoration instuction</string>
|
||||
|
||||
<string name="common_custom">Custom</string>
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
<string name="currency_subtitle_expanded">Доступные сети</string>
|
||||
<string name="alert_manage_tokens_addresses_message">Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства.</string>
|
||||
<string name="contract_address_copied_message">Адрес контракта скопирован!</string>
|
||||
<string name="alert_funds_restoration_message">Инструкция по восстановлению средств</string>
|
||||
|
||||
<string name="common_custom">Пользовательский</string>
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
<string name="currency_subtitle_expanded">Available networks</string>
|
||||
<string name="alert_manage_tokens_addresses_message">Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds.</string>
|
||||
<string name="contract_address_copied_message">Contract address copied!</string>
|
||||
<string name="alert_funds_restoration_message">Funds restoration instuction</string>
|
||||
|
||||
<string name="common_custom">Custom</string>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,30 +5,30 @@ import com.tangem.blockchain.common.Blockchain
|
|||
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
|
||||
return when (networkId) {
|
||||
"avalanche", "avalanche-2" -> Blockchain.Avalanche
|
||||
"avalanche-testnet", "avalanche-2-testnet" -> Blockchain.AvalancheTestnet
|
||||
"avalanche/test", "avalanche-2/test" -> Blockchain.AvalancheTestnet
|
||||
"binancecoin" -> Blockchain.Binance
|
||||
"binancecoin-testnet" -> Blockchain.BinanceTestnet
|
||||
"binancecoin/test" -> Blockchain.BinanceTestnet
|
||||
"binance-smart-chain" -> Blockchain.BSC
|
||||
"binance-smart-chain-testnet" -> Blockchain.BSCTestnet
|
||||
"binance-smart-chain/test" -> Blockchain.BSCTestnet
|
||||
"ethereum" -> Blockchain.Ethereum
|
||||
"ethereum-testnet" -> Blockchain.EthereumTestnet
|
||||
"ethereum/test" -> Blockchain.EthereumTestnet
|
||||
"polygon-pos" -> Blockchain.Polygon
|
||||
"polygon-pos-testnet" -> Blockchain.PolygonTestnet
|
||||
"polygon-pos/test" -> Blockchain.PolygonTestnet
|
||||
"solana" -> Blockchain.Solana
|
||||
"solana-testnet" -> Blockchain.SolanaTestnet
|
||||
"solana/test" -> Blockchain.SolanaTestnet
|
||||
"fantom" -> Blockchain.Fantom
|
||||
"fantom-testnet" -> Blockchain.FantomTestnet
|
||||
"fantom/test" -> Blockchain.FantomTestnet
|
||||
"bitcoin" -> Blockchain.Bitcoin
|
||||
"bitcoin-testnet" -> Blockchain.BitcoinTestnet
|
||||
"bitcoin/test" -> Blockchain.BitcoinTestnet
|
||||
"bitcoin-cash" -> Blockchain.BitcoinCash
|
||||
"bitcoin-cash-testnet" -> Blockchain.BitcoinCashTestnet
|
||||
"bitcoin-cash/test" -> Blockchain.BitcoinCashTestnet
|
||||
"cardano" -> Blockchain.CardanoShelley
|
||||
"dogecoin" -> Blockchain.Dogecoin
|
||||
"ducatus" -> Blockchain.Ducatus
|
||||
"litecoin" -> Blockchain.Litecoin
|
||||
"rsk" -> Blockchain.RSK
|
||||
"stellar" -> Blockchain.Stellar
|
||||
"stellar-testnet" -> Blockchain.StellarTestnet
|
||||
"stellar/test" -> Blockchain.StellarTestnet
|
||||
"tezos" -> Blockchain.Tezos
|
||||
"ripple" -> Blockchain.XRP
|
||||
else -> null
|
||||
|
|
@ -39,31 +39,31 @@ fun Blockchain.toNetworkId(): String {
|
|||
return when (this) {
|
||||
Blockchain.Unknown -> "unknown"
|
||||
Blockchain.Avalanche -> "avalanche"
|
||||
Blockchain.AvalancheTestnet -> "avalanche-testnet"
|
||||
Blockchain.AvalancheTestnet -> "avalanche/test"
|
||||
Blockchain.Binance -> "binancecoin"
|
||||
Blockchain.BinanceTestnet -> "binancecoin-testnet"
|
||||
Blockchain.BinanceTestnet -> "binancecoin/test"
|
||||
Blockchain.BSC -> "binance-smart-chain"
|
||||
Blockchain.BSCTestnet -> "binance-smart-chain-testnet"
|
||||
Blockchain.BSCTestnet -> "binance-smart-chain/test"
|
||||
Blockchain.Bitcoin -> "bitcoin"
|
||||
Blockchain.BitcoinTestnet -> "bitcoin-testnet"
|
||||
Blockchain.BitcoinTestnet -> "bitcoin/test"
|
||||
Blockchain.BitcoinCash -> "bitcoin-cash"
|
||||
Blockchain.BitcoinCashTestnet -> "bitcoin-cash-testnet"
|
||||
Blockchain.BitcoinCashTestnet -> "bitcoin-cash/test"
|
||||
Blockchain.Cardano -> "cardano"
|
||||
Blockchain.CardanoShelley -> "cardano"
|
||||
Blockchain.Dogecoin -> "dogecoin"
|
||||
Blockchain.Ducatus -> "ducatus"
|
||||
Blockchain.Ethereum -> "ethereum"
|
||||
Blockchain.EthereumTestnet -> "ethereum-testnet"
|
||||
Blockchain.EthereumTestnet -> "ethereum/test"
|
||||
Blockchain.Fantom -> "fantom"
|
||||
Blockchain.FantomTestnet -> "fantom-testnet"
|
||||
Blockchain.FantomTestnet -> "fantom/test"
|
||||
Blockchain.Litecoin -> "litecoin"
|
||||
Blockchain.Polygon -> "matic-network"
|
||||
Blockchain.PolygonTestnet -> "matic-networks-testnet"
|
||||
Blockchain.PolygonTestnet -> "matic-networks/test"
|
||||
Blockchain.RSK -> "rootstock"
|
||||
Blockchain.Stellar -> "stellar"
|
||||
Blockchain.StellarTestnet -> "stellar-testnet"
|
||||
Blockchain.StellarTestnet -> "stellar/test"
|
||||
Blockchain.Solana -> "solana"
|
||||
Blockchain.SolanaTestnet -> "solana-testnet"
|
||||
Blockchain.SolanaTestnet -> "solana/test"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "ripple"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.domain.common.form
|
||||
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumAddressService
|
||||
import com.tangem.blockchain.blockchains.solana.SolanaAddressService
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.address.AddressService
|
||||
import com.tangem.common.Validator
|
||||
import com.tangem.domain.AddCustomTokenError
|
||||
|
||||
|
|
@ -25,15 +27,36 @@ class StringIsNotEmptyValidator : CustomTokenValidator<String>() {
|
|||
}
|
||||
|
||||
class TokenContractAddressValidator : CustomTokenValidator<String>() {
|
||||
|
||||
private var blockchain: Blockchain = Blockchain.Unknown
|
||||
|
||||
fun nextValidationFor(blockchain: Blockchain) {
|
||||
this.blockchain = blockchain
|
||||
}
|
||||
|
||||
override fun validate(data: String?): AddCustomTokenError? {
|
||||
if (data == null || data.isEmpty()) return AddCustomTokenError.FieldIsEmpty
|
||||
|
||||
return if (EthereumAddressService().validate(data)) {
|
||||
return if (getAddressService().validate(data)) {
|
||||
null
|
||||
} else {
|
||||
AddCustomTokenError.InvalidContractAddress
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAddressService(): AddressService {
|
||||
return when (blockchain) {
|
||||
Blockchain.Solana, Blockchain.SolanaTestnet -> SolanaAddressService()
|
||||
Blockchain.Unknown -> EthereumAddressService()
|
||||
else -> {
|
||||
if (blockchain.isEvm()) {
|
||||
EthereumAddressService()
|
||||
} else {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TokenNetworkValidator : CustomTokenValidator<Blockchain>() {
|
||||
|
|
|
|||
|
|
@ -413,7 +413,10 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
|
|||
val decimalsValidator: TokenDecimalsValidator = state.getValidator(Decimals)
|
||||
val networkValidator: TokenNetworkValidator = state.getValidator(Network)
|
||||
return when (this) {
|
||||
ContractAddress -> contractAddressValidator.validate(value as String)
|
||||
ContractAddress -> {
|
||||
contractAddressValidator.nextValidationFor(Network.getFieldValue())
|
||||
contractAddressValidator.validate(value as String)
|
||||
}
|
||||
Network, DerivationPath -> networkValidator.validate(value as Blockchain)
|
||||
Name -> nameValidator.validate(value as String)
|
||||
Symbol -> symbolValidator.validate(value as String)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue