Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-20 13:09:57 +03:00
parent 2d98489526
commit a75226b865
9 changed files with 222 additions and 20 deletions

View file

@ -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 {

View file

@ -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(

View 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) }
}
}
}

View file

@ -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()

View file

@ -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()
}

View file

@ -26,6 +26,7 @@ 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),

View file

@ -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

View file

@ -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
@ -68,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())
@ -91,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
@ -111,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()
)
)
))
}
}
@ -163,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(

View file

@ -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()
}
}