diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml deleted file mode 100644 index 09d3bc259e..0000000000 --- a/.idea/navEditor.xml +++ /dev/null @@ -1,541 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Gemfile b/Gemfile index 7a118b49be..cdd3a6b349 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,6 @@ source "https://rubygems.org" gem "fastlane" + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/app/build.gradle b/app/build.gradle index a3a738a121..4e81272cfe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,8 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' -apply plugin: 'io.fabric' +apply plugin: 'com.google.firebase.crashlytics' +apply plugin: 'com.google.gms.google-services' static def generateVersionName() { def gitCommitsCount = "git rev-list HEAD --count".execute().text.trim() @@ -29,24 +30,17 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.debug - buildConfigField 'boolean', 'CRASHLYTICS', 'Boolean.parseBoolean("false")' - ext.enableCrashlytics = false } debug { debuggable true minifyEnabled false applicationIdSuffix ".dev" signingConfig signingConfigs.debug - buildConfigField 'boolean', 'CRASHLYTICS', 'Boolean.parseBoolean("false")' - ext.enableCrashlytics = false } debug_beta { initWith debug versionNameSuffix "-beta" applicationIdSuffix ".debug" - buildConfigField 'boolean', 'CRASHLYTICS', 'Boolean.parseBoolean("true")' - manifestPlaceholders = [fabric_api_key: properties.hasProperty('fabric_api_key') - ? project.property('fabric_api_key') : '000'] } } flavorDimensions "" @@ -75,7 +69,6 @@ android { repositories { maven { url "https://jitpack.io" } - maven { url 'https://maven.fabric.io/public' } } configurations { all*.exclude group: 'com.google.guava', module: 'listenablefuture' @@ -133,8 +126,8 @@ dependencies { // implementation 'com.github.stellar:java-stellar-sdk:0.4.1' implementation 'net.i2p.crypto:eddsa:0.3.0' - implementation('com.crashlytics.sdk.android:crashlytics:2.9.9') { transitive = true } - implementation('io.fabric.sdk.android:fabric:1.4.0@aar') { transitive = true } + implementation 'com.google.firebase:firebase-analytics:17.2.3' + implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta02' implementation files('libs/ripple-core-0.0.1.jar') //4 dependencies for ripple-core TODO: move to module? diff --git a/app/src/debug_beta/AndroidManifest.xml b/app/src/debug_beta/AndroidManifest.xml index 9f07ca1097..8cf74b318f 100644 --- a/app/src/debug_beta/AndroidManifest.xml +++ b/app/src/debug_beta/AndroidManifest.xml @@ -7,10 +7,6 @@ - - lastTag?.let { val engineCoin = CoinEngineFactory.create(ctx) @@ -315,6 +321,7 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader } } else { + FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) // remove last UIDs because of error and no card read rlProgressBar.post { context?.let { Toast.makeText(it, R.string.general_notification_scan_again, Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/tangem/ui/fragment/PurgeFragment.kt b/app/src/main/java/com/tangem/ui/fragment/PurgeFragment.kt index dc12ed4860..76d8d97dc6 100644 --- a/app/src/main/java/com/tangem/ui/fragment/PurgeFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/PurgeFragment.kt @@ -12,6 +12,7 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.core.os.bundleOf +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.App import com.tangem.Constant import com.tangem.tangem_card.reader.CardProtocol @@ -151,6 +152,7 @@ class PurgeFragment : BaseFragment(), NfcAdapter.ReaderCallback, CardProtocol.No navigateToDestination(R.id.action_purgeFragment_to_mainFragment) } } else { + FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) if (cardProtocol.error is CardProtocol.TangemException_InvalidPIN) { progressBar?.post { progressBar?.progress = 100 diff --git a/app/src/main/java/com/tangem/ui/fragment/SendTransactionFragment.kt b/app/src/main/java/com/tangem/ui/fragment/SendTransactionFragment.kt index 7d709ee662..4e9de2f02e 100644 --- a/app/src/main/java/com/tangem/ui/fragment/SendTransactionFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/SendTransactionFragment.kt @@ -6,11 +6,15 @@ import android.nfc.Tag import android.os.Bundle import android.widget.Toast import androidx.activity.OnBackPressedCallback +import androidx.core.os.bundleOf +import com.google.firebase.analytics.FirebaseAnalytics import com.tangem.App import com.tangem.Constant import com.tangem.tangem_card.util.Util import com.tangem.ui.activity.MainActivity import com.tangem.ui.event.TransactionFinishWithSuccess +import com.tangem.util.AnalyticsEvent +import com.tangem.util.AnalyticsParam import com.tangem.util.UtilHelper import com.tangem.wallet.CoinEngine import com.tangem.wallet.CoinEngineFactory @@ -40,7 +44,7 @@ class SendTransactionFragment : BaseFragment(), NfcAdapter.ReaderCallback { if (success) { App.pendingTransactionsStorage.putTransaction(ctx.card, Util.bytesToHex(tx), engine.pendingTransactionTimeoutInSeconds()) finishWithSuccess() - }else + } else finishWithError(ctx.error) } @@ -72,6 +76,11 @@ class SendTransactionFragment : BaseFragment(), NfcAdapter.ReaderCallback { } private fun finishWithSuccess() { + activity?.let { + val params = bundleOf(AnalyticsParam.BLOCKCHAIN.param to ctx.blockchainName) + FirebaseAnalytics.getInstance(it).logEvent(AnalyticsEvent.TRANSACTION_IS_SENT.event, params) + } + val transactionFinishWithSuccess = TransactionFinishWithSuccess() EventBus.getDefault().post(transactionFinishWithSuccess) diff --git a/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt b/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt index 52fa224f03..98b57d7910 100644 --- a/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt @@ -1,18 +1,24 @@ package com.tangem.ui.fragment +import android.content.SharedPreferences import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity +import androidx.core.os.bundleOf import androidx.navigation.fragment.NavHostFragment import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.tangem.util.Analytics +import com.tangem.util.AnalyticsEvent import com.tangem.wallet.BuildConfig import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_settings.* -class SettingsFragment : PreferenceFragmentCompat() { +class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_main) @@ -40,6 +46,32 @@ class SettingsFragment : PreferenceFragmentCompat() { } } + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + if (key == getString(R.string.pref_analytics)) { + activity?.let { + val analytics = FirebaseAnalytics.getInstance(it) + if (!Analytics.isEnabled()) { + analytics.logEvent(AnalyticsEvent.ANALYTICS_TURNED_OFF.event, bundleOf()) + } + analytics.setAnalyticsCollectionEnabled(Analytics.isEnabled()) + } + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(Analytics.isEnabled()) + + } + } + + override fun onResume() { + super.onResume() + preferenceScreen.sharedPreferences + .registerOnSharedPreferenceChangeListener(this); + } + + override fun onPause() { + super.onPause() + preferenceScreen.sharedPreferences + .unregisterOnSharedPreferenceChangeListener(this); + } + override fun onDestroyView() { (activity as AppCompatActivity).setSupportActionBar(toolbar) super.onDestroyView() diff --git a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt b/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt index 421f780cc1..1272905501 100644 --- a/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/VerifyCardFragment.kt @@ -69,6 +69,11 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter. super.onViewCreated(view, savedInstanceState) updateViews() + val card = ctx.card + if (!card.allowSwapPIN() && !card.allowSwapPIN2() && card.forbidPurgeWallet()) { + fabMenu.hide() + } + // set listeners fabMenu.setOnClickListener { showMenu(fabMenu) } @@ -278,11 +283,6 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter. ivBlockchain.setImageResource(Blockchain.getLogoImageResource(ctx.card!!.blockchainID, ctx.card!!.tokenSymbol)) - if (ctx.card!!.isReusable!!) - tvReusable.setText(R.string.details_reusable) - else - tvReusable.setText(R.string.details_one_off_card) - var s = "" for (signingM in ctx.card!!.allowedSigningMethod) { s += if (signingM == ctx.card!!.signingMethod) { @@ -328,44 +328,6 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter. tvFirmware.text = ctx.card!!.firmwareVersion - var features = "" - - features += if (ctx.card!!.allowSwapPIN()!! && ctx.card!!.allowSwapPIN2()!!) { - getString(R.string.details_both_pins_can_be_changed) - } else if (ctx.card!!.allowSwapPIN()!!) { - getString(R.string.details_pin1_can_be_changed) - } else if (ctx.card!!.allowSwapPIN2()!!) { - getString(R.string.details_pin2_can_be_changed) - } else { - getString(R.string.details_both_pins_fixed) - } - - if (ctx.card!!.needCVC()!!) - features += getString(R.string.details_required_cvc) - - - if (ctx.card!!.supportDynamicNDEF()!!) { - features += getString(R.string.details_dynamic_ndef) - } else if (ctx.card!!.supportNDEF()!!) - features += getString(R.string.details_ndef) - - if (ctx.card!!.supportBlock()!!) - features += getString(R.string.details_blockable) - - if (ctx.card!!.supportLinkingTerminal()) { - features += getString(R.string.details_linking_card_supported) - llLinkedCard.visibility = View.VISIBLE - } - - - if (ctx.card!!.supportOnlyOneCommandAtTime()) - features += getString(R.string.details_atomic_commmands) - - if (features.endsWith("\n")) - features = features.substring(0, features.length - 1) - - tvFeatures.text = features - val textIsLinked = if (ctx.card.terminalIsLinked) { getString(R.string.details_linked_card_to_phone) } else { @@ -373,25 +335,6 @@ class VerifyCardFragment : BaseFragment(), NavigationResultListener, NfcAdapter. } tvIsLinked.text = textIsLinked - if (ctx.card!!.useDefaultPIN1()) { - imgPIN.setImageResource(R.drawable.unlock_pin1) - imgPIN.setOnClickListener { Toast.makeText(context, R.string.details_protected_by_default_pin_1, Toast.LENGTH_LONG).show() } - } else { - imgPIN.setImageResource(R.drawable.lock_pin1) - imgPIN.setOnClickListener { Toast.makeText(context, R.string.details_protected_by_user_pin_1, Toast.LENGTH_LONG).show() } - } - - if (ctx.card!!.pauseBeforePIN2 > 0 && (ctx.card!!.useDefaultPIN2()!! || !ctx.card!!.useSmartSecurityDelay())) { - imgPIN2orSecurityDelay.setImageResource(R.drawable.timer) - imgPIN2orSecurityDelay.setOnClickListener { Toast.makeText(context, String.format(getString(R.string.details_security_delay), ctx.card!!.pauseBeforePIN2 / 1000.0), Toast.LENGTH_LONG).show() } - } else if (ctx.card!!.useDefaultPIN2()!!) { - imgPIN2orSecurityDelay.setImageResource(R.drawable.unlock_pin2) - imgPIN2orSecurityDelay.setOnClickListener { Toast.makeText(context, R.string.details_protected_by_default_pin_2, Toast.LENGTH_LONG).show() } - } else { - imgPIN2orSecurityDelay.setImageResource(R.drawable.lock_pin2) - imgPIN2orSecurityDelay.setOnClickListener { Toast.makeText(context, R.string.details_protected_by_user_pin_2, Toast.LENGTH_LONG).show() } - } - if (ctx.card!!.useDevelopersFirmware()!!) { imgDeveloperVersion.setImageResource(R.drawable.ic_developer_version) imgDeveloperVersion.visibility = View.VISIBLE diff --git a/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt index 7c8ec384e4..8013e66fed 100644 --- a/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt @@ -12,6 +12,7 @@ import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.App import com.tangem.Constant import com.tangem.data.Blockchain @@ -26,7 +27,6 @@ import com.tangem.ui.activity.MainActivity import com.tangem.ui.dialog.NoExtendedLengthSupportDialog import com.tangem.ui.dialog.WaitSecurityDelayDialog import com.tangem.ui.fragment.BaseFragment -import com.tangem.ui.fragment.wallet.LoadedWalletViewModel import com.tangem.util.LOG import com.tangem.util.UtilHelper import com.tangem.wallet.* @@ -232,6 +232,7 @@ class IdFragment : BaseFragment(), NfcAdapter.ReaderCallback, } } else { // remove last UIDs because of error and no card read + FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) rlProgressBar?.post { lastReadSuccess = false if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) diff --git a/app/src/main/java/com/tangem/ui/fragment/id/ValidateIdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/ValidateIdFragment.kt index 04812351c0..2eed027cf3 100644 --- a/app/src/main/java/com/tangem/ui/fragment/id/ValidateIdFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/id/ValidateIdFragment.kt @@ -11,12 +11,12 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.activity.OnBackPressedCallback +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.App import com.tangem.Constant import com.tangem.tangem_card.data.TangemCard import com.tangem.tangem_card.reader.CardProtocol import com.tangem.tangem_card.tasks.OneTouchSignTask -import com.tangem.tangem_card.tasks.SignTask import com.tangem.tangem_card.util.Util import com.tangem.tangem_sdk.android.data.PINStorage import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation @@ -24,7 +24,6 @@ import com.tangem.tangem_sdk.android.reader.NfcReader import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID import com.tangem.tangem_sdk.data.asBundle -import com.tangem.ui.SignTransactionFragment import com.tangem.ui.activity.MainActivity import com.tangem.ui.dialog.NoExtendedLengthSupportDialog import com.tangem.ui.dialog.WaitSecurityDelayDialog @@ -223,6 +222,7 @@ class ValidateIdFragment : BaseFragment(), NavigationResultListener, mpFinishSignSound.start() } else { + FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) lastReadSuccess = false if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) { progressBar?.post { diff --git a/app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt index 5b0235f356..3df0850220 100644 --- a/app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/id/WriteIdFragment.kt @@ -11,6 +11,7 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.activity.OnBackPressedCallback +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.App import com.tangem.Constant import com.tangem.tangem_card.reader.CardProtocol @@ -190,6 +191,7 @@ class WriteIdFragment : BaseFragment(), NavigationResultListener, } else { lastReadSuccess = false + FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) { progressBar?.post { progressBar?.progress = 100 diff --git a/app/src/main/java/com/tangem/ui/fragment/pin/PinSwapFragment.kt b/app/src/main/java/com/tangem/ui/fragment/pin/PinSwapFragment.kt index 04b12813ba..ee846c13ca 100644 --- a/app/src/main/java/com/tangem/ui/fragment/pin/PinSwapFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/pin/PinSwapFragment.kt @@ -10,6 +10,7 @@ import android.os.Bundle import android.view.View import android.widget.ProgressBar import android.widget.Toast +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.App import com.tangem.Constant import com.tangem.tangem_card.data.TangemCard @@ -112,6 +113,7 @@ class PinSwapFragment : BaseFragment(), NfcAdapter.ReaderCallback, CardProtocol. swapPinTask = null if (cardProtocol != null) { + cardProtocol.error?.let { FirebaseCrashlytics.getInstance().recordException(it) } when { cardProtocol.error == null -> progressBar!!.post { progressBar!!.progress = 100 diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/CreateNewWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/CreateNewWalletFragment.kt index 5496044e70..51e9ef4544 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/CreateNewWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/CreateNewWalletFragment.kt @@ -9,6 +9,7 @@ import android.nfc.tech.IsoDep import android.os.Bundle import android.view.View import android.widget.Toast +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.App import com.tangem.Constant import com.tangem.tangem_card.reader.CardProtocol @@ -101,6 +102,7 @@ class CreateNewWalletFragment : BaseFragment(), NfcAdapter.ReaderCallback, CardP return@post } } else { + FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) lastReadSuccess = false if (cardProtocol.error is CardProtocol.TangemException_InvalidPIN) { progressBar?.post { diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/EmptyWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/EmptyWalletFragment.kt index 65461743c6..e8a4cc7542 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/EmptyWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/EmptyWalletFragment.kt @@ -13,6 +13,7 @@ import android.os.Bundle import android.text.Html import android.view.View import android.widget.Toast +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.App import com.tangem.Constant import com.tangem.tangem_card.data.TangemCard @@ -181,6 +182,7 @@ class EmptyWalletFragment : BaseFragment(), NavigationResultListener, this.cardProtocol = cardProtocol } } else { + FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) // remove last UIDs because of error and no card read progressBar?.post { lastReadSuccess = false diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index 4de8ee31c8..e1055fbfce 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -337,6 +337,7 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte @Subscribe fun onTransactionFinishWithSuccess(transactionFinishWithSuccess: TransactionFinishWithSuccess) { refreshAction = Runnable { refresh() } + ctx.card.remainingSignatures -= 1 srl?.postDelayed(refreshAction, 10000) } @@ -478,6 +479,7 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte Constant.REQUEST_CODE_SEND_TRANSACTION, Constant.REQUEST_CODE_RECEIVE_TRANSACTION -> { if (resultCode == Activity.RESULT_OK) { +// ctx.card.remainingSignatures = ctx.card.remainingSignatures - 1 ctx.coinData?.clearInfo() srl?.postDelayed({ this.refresh() }, 5000) srl?.isRefreshing = true diff --git a/app/src/main/java/com/tangem/util/Analytics.kt b/app/src/main/java/com/tangem/util/Analytics.kt new file mode 100644 index 0000000000..c01d40e340 --- /dev/null +++ b/app/src/main/java/com/tangem/util/Analytics.kt @@ -0,0 +1,39 @@ +package com.tangem.util + +import android.os.Bundle +import androidx.core.os.bundleOf +import com.tangem.data.dp.PrefsManager +import com.tangem.wallet.R +import com.tangem.wallet.TangemContext + +enum class AnalyticsEvent(val event: String) { + CARD_IS_SCANNED("card_is_scanned"), + TRANSACTION_IS_SENT("transaction_is_sent"), + READY_TO_SCAN("ready_to_scan"), + READY_TO_SIGN("ready_to_sign"), + SIGNED("signed"), + ANALYTICS_TURNED_OFF("analytics_turned_off"), + ; +} + +enum class AnalyticsParam(val param: String) { + BLOCKCHAIN("blockchain"), + BATCH_ID("batch_id"), + FIRMWARE("firmware"), + ; +} +class Analytics { + companion object { + fun setCardData(ctx: TangemContext): Bundle { + return bundleOf( + AnalyticsParam.BLOCKCHAIN.param to ctx.blockchainName, + AnalyticsParam.BATCH_ID.param to ctx.card.batch, + AnalyticsParam.FIRMWARE.param to ctx.card.firmwareVersion + ) + } + + fun isEnabled(): Boolean { + return PrefsManager.getInstance().getSettingsBoolean(R.string.pref_analytics, true) + } + } +} diff --git a/app/src/main/java/com/tangem/wallet/TangemContext.java b/app/src/main/java/com/tangem/wallet/TangemContext.java index df164a4616..a027d33f3b 100644 --- a/app/src/main/java/com/tangem/wallet/TangemContext.java +++ b/app/src/main/java/com/tangem/wallet/TangemContext.java @@ -4,10 +4,12 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; +import com.google.firebase.crashlytics.FirebaseCrashlytics; import com.tangem.Constant; import com.tangem.data.Blockchain; import com.tangem.tangem_card.data.TangemCard; import com.tangem.tangem_sdk.data.TangemCardExtensionsKt; +import com.tangem.util.AnalyticsParam; public class TangemContext { // public static final String EXTRA_BLOCKCHAIN_DATA = "BLOCKCHAIN_DATA"; @@ -58,7 +60,7 @@ public class TangemContext { public String getBlockchainName() { Blockchain blockchain = getBlockchain(); if (blockchain == Blockchain.Token || blockchain == Blockchain.RootstockToken) { - return card.getTokenSymbol()+ "
" + getBlockchain().getOfficialName() + " smart contract token"; + return card.getTokenSymbol() + "
" + getBlockchain().getOfficialName() + " smart contract token"; } if (blockchain == Blockchain.NftToken) { return card.getTokenSymbol().substring(4) + "
" + getBlockchain().getOfficialName() + " non-fungible token"; @@ -67,7 +69,7 @@ public class TangemContext { return card.getTokenSymbol() + "
" + getBlockchain().getOfficialName() + " asset"; } if (blockchain == Blockchain.StellarTag) { - return "TANGEM TAG
"+ getBlockchain().getOfficialName() + " non-fungible token "; + return "TANGEM TAG
" + getBlockchain().getOfficialName() + " non-fungible token "; } return blockchain.getOfficialName(); } @@ -97,6 +99,11 @@ public class TangemContext { } public void setError(String error) { + if (error != null) { + FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance(); + crashlytics.setCustomKey(AnalyticsParam.BLOCKCHAIN.getParam(), getBlockchainName()); + crashlytics.recordException(new Exception(error)); + } this.error = error; } diff --git a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java index 8c24e30480..4c31f74772 100644 --- a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java @@ -481,7 +481,6 @@ public class EthEngine extends CoinEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceReceived(true); coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - // Log.i("$TAG eth_get_balance", balanceCap) } break; @@ -491,8 +490,9 @@ public class EthEngine extends CoinEngine { nonce = nonce.substring(2); BigInteger count = new BigInteger(nonce, 16); coinData.setConfirmedTXCount(count); - - + if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getTransCount", nonce) } break; @@ -502,7 +502,9 @@ public class EthEngine extends CoinEngine { pending = pending.substring(2); BigInteger count = new BigInteger(pending, 16); coinData.setUnconfirmedTXCount(count); - + if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getPendingTxCount", pending) } break; @@ -582,7 +584,6 @@ public class EthEngine extends CoinEngine { }; serverApiBlockcypher.setResponseListener(blockcypherListener); - serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); diff --git a/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java b/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java index 6568157b56..713f4b503c 100644 --- a/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java @@ -109,7 +109,6 @@ public class MaticTokenEngine extends TokenEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceReceived(true); coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - // Log.i("$TAG eth_get_balance", balanceCap) } break; @@ -119,8 +118,10 @@ public class MaticTokenEngine extends TokenEngine { nonce = nonce.substring(2); BigInteger count = new BigInteger(nonce, 16); coinData.setConfirmedTXCount(count); - - + if (serverApiMatic.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getTransCount", nonce) } break; @@ -130,7 +131,10 @@ public class MaticTokenEngine extends TokenEngine { pending = pending.substring(2); BigInteger count = new BigInteger(pending, 16); coinData.setUnconfirmedTXCount(count); - + if (serverApiMatic.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getPendingTxCount", pending) } break; @@ -143,16 +147,6 @@ public class MaticTokenEngine extends TokenEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol)); // Log.i("$TAG eth_call", balanceCap) - - - if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); - serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); - serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); - } else { - ctx.setError("Terminated by user"); - } - } catch (Exception e) { e.printStackTrace(); } @@ -181,7 +175,8 @@ public class MaticTokenEngine extends TokenEngine { serverApiMatic.setResponseListener(responseListener); if (validateAddress(getContractAddress(ctx.getCard()))) { - serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } else { ctx.setError("Smart contract address not defined"); blockchainRequestsCallbacks.onComplete(false); diff --git a/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java b/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java index d968f05e93..27614e94ea 100644 --- a/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java +++ b/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java @@ -85,8 +85,9 @@ public class RskEngine extends EthEngine { nonce = nonce.substring(2); BigInteger count = new BigInteger(nonce, 16); coinData.setConfirmedTXCount(count); - - + if (serverApiRootstock.isRequestsSequenceCompleted()) { + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getTransCount", nonce) } break; @@ -96,7 +97,9 @@ public class RskEngine extends EthEngine { pending = pending.substring(2); BigInteger count = new BigInteger(pending, 16); coinData.setUnconfirmedTXCount(count); - + if (serverApiRootstock.isRequestsSequenceCompleted()) { + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getPendingTxCount", pending) } break; @@ -122,7 +125,6 @@ public class RskEngine extends EthEngine { }; serverApiRootstock.setResponseListener(responseListener); - serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } diff --git a/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java b/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java index 0f54555f16..e7ed9eed23 100644 --- a/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java @@ -82,7 +82,6 @@ public class RskTokenEngine extends TokenEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceReceived(true); coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - // Log.i("$TAG eth_get_balance", balanceCap) } break; @@ -92,8 +91,10 @@ public class RskTokenEngine extends TokenEngine { nonce = nonce.substring(2); BigInteger count = new BigInteger(nonce, 16); coinData.setConfirmedTXCount(count); - - + if (serverApiRootstock.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getTransCount", nonce) } break; @@ -103,7 +104,10 @@ public class RskTokenEngine extends TokenEngine { pending = pending.substring(2); BigInteger count = new BigInteger(pending, 16); coinData.setUnconfirmedTXCount(count); - + if (serverApiRootstock.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getPendingTxCount", pending) } break; @@ -115,15 +119,6 @@ public class RskTokenEngine extends TokenEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol)); // Log.i("$TAG eth_call", balanceCap) - - if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); - serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); - serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); - } else { - ctx.setError("Terminated by user"); - } - } catch (Exception e) { e.printStackTrace(); } @@ -152,7 +147,8 @@ public class RskTokenEngine extends TokenEngine { serverApiRootstock.setResponseListener(responseListener); if (validateAddress(getContractAddress(ctx.getCard()))) { - serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } else { ctx.setError("Smart contract address not defined"); blockchainRequestsCallbacks.onComplete(false); diff --git a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java index d36abf0480..7668f0fcae 100644 --- a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java @@ -655,7 +655,6 @@ public class TokenEngine extends CoinEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceReceived(true); coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - // Log.i("$TAG eth_get_balance", balanceCap) } break; @@ -665,8 +664,10 @@ public class TokenEngine extends CoinEngine { nonce = nonce.substring(2); BigInteger count = new BigInteger(nonce, 16); coinData.setConfirmedTXCount(count); - - + if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getTransCount", nonce) } break; @@ -676,29 +677,21 @@ public class TokenEngine extends CoinEngine { pending = pending.substring(2); BigInteger count = new BigInteger(pending, 16); coinData.setUnconfirmedTXCount(count); - + if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getPendingTxCount", pending) } break; // case ServerApiInfura.INFURA_ETH_CALL: { try { - String balanceCap = infuraResponse.getResult(); balanceCap = balanceCap.substring(2); BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol)); // Log.i("$TAG eth_call", balanceCap) - - - if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); - serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); - serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); - } else { - ctx.setError("Terminated by user"); - } - } catch (Exception e) { e.printStackTrace(); } @@ -706,7 +699,7 @@ public class TokenEngine extends CoinEngine { break; } - if (serverApiInfura.isRequestsSequenceCompleted()&& serverApiBlockcypher.isRequestsSequenceCompleted()) { + if (serverApiInfura.isRequestsSequenceCompleted() && serverApiBlockcypher.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); } else { blockchainRequestsCallbacks.onProgress(); @@ -771,13 +764,13 @@ public class TokenEngine extends CoinEngine { serverApiBlockcypher.setResponseListener(blockcypherListener); if (validateAddress(getContractAddress(ctx.getCard()))) { - serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), ""); } else { ctx.setError("Smart contract address not defined"); blockchainRequestsCallbacks.onComplete(false); } - - serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), ""); } @Override diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java index 1b5a8213cc..eee75cd22c 100644 --- a/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java @@ -109,14 +109,14 @@ public class XlmAssetEngine extends CoinEngine { @Override public boolean isBalanceNotZero() { if (coinData == null) return false; - if (coinData.getXlmBalance() == null) return false; - return coinData.getXlmBalance().notZero(); + return (coinData.getXlmBalance() != null && coinData.getXlmBalance().notZero() && + coinData.getAssetBalance() != null && coinData.getAssetBalance().notZero()); } @Override public boolean hasBalanceInfo() { if (coinData == null) return false; - return coinData.getXlmBalance() != null; + return (coinData.getXlmBalance() != null && coinData.getAssetBalance() != null) || (coinData.isError404()); } diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java index 1b10a221a3..ca79936381 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java @@ -122,6 +122,6 @@ public class XrpData extends CoinData { } public boolean hasUnconfirmed() { - return !balanceConfirmed.equals(balanceUnconfirmed); + return hasBalanceInfo() && !balanceConfirmed.equals(balanceUnconfirmed); } } diff --git a/app/src/main/res/layout/fr_verify_card.xml b/app/src/main/res/layout/fr_verify_card.xml index 189186e5f5..cef381db19 100644 --- a/app/src/main/res/layout/fr_verify_card.xml +++ b/app/src/main/res/layout/fr_verify_card.xml @@ -159,32 +159,6 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 39cb7cd948..67cf1e495e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -28,6 +28,7 @@ Settings Common Manual editing Fee + Share anonymous data with developers Encryption modes None @@ -187,6 +188,9 @@ Incorrect destination wallet address Destination wallet address equal source address Amount is empty + Only one signature left + You need to send all available funds in this transaction + target wallet address diff --git a/app/src/main/res/values/strings_untranslatable.xml b/app/src/main/res/values/strings_untranslatable.xml index 0b6a2bcf98..419b1c6812 100644 --- a/app/src/main/res/values/strings_untranslatable.xml +++ b/app/src/main/res/values/strings_untranslatable.xml @@ -26,6 +26,7 @@ category_common manual_editing_fee + analytics select_nodes encryption_modes diff --git a/app/src/main/res/xml/pref_main.xml b/app/src/main/res/xml/pref_main.xml index 47be8c9663..e00d77c325 100644 --- a/app/src/main/res/xml/pref_main.xml +++ b/app/src/main/res/xml/pref_main.xml @@ -4,11 +4,16 @@ android:key="@string/pref_category_common" android:title="@string/settings_category_сommon"> - + + diff --git a/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt index cdea28160d..48a385a3d8 100644 --- a/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt +++ b/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt @@ -54,8 +54,18 @@ class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, Nfc rgIncFee.visibility = View.VISIBLE } - if (ctx.card!!.remainingSignatures < 2) + if (ctx.card!!.remainingSignatures < 2) { etAmount.isEnabled = false + } + + if (ctx.card.remainingSignatures == 1) { + androidx.appcompat.app.AlertDialog.Builder(requireContext()) + .setTitle(R.string.prepare_transaction_warning_last_signature) + .setMessage(R.string.prepare_transaction_warning_send_full_amount) + .setPositiveButton(R.string.general_ok) { _, _ -> } + .create() + .show() + } tvCurrency.text = engine.balance.currency etAmount.setText(engine.balance.toValueString()) @@ -144,7 +154,7 @@ class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, Nfc val schemeSplit = code!!.split(":") when (schemeSplit.size) { 2 -> { - if (ctx.blockchain.officialName.toLowerCase(Locale.ROOT).replace("\\s","") == schemeSplit[0]) { + if (ctx.blockchain.officialName.toLowerCase(Locale.ROOT).replace("\\s", "") == schemeSplit[0]) { val uri = Uri.parse(schemeSplit[1]) etWallet?.setText(uri.path) // val amount = uri.getQueryParameter("amount") //TODO: enable after redesign diff --git a/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt index 8429ec1e2e..bb5858b8e3 100644 --- a/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt +++ b/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt @@ -11,6 +11,8 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.activity.OnBackPressedCallback +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.App import com.tangem.Constant import com.tangem.tangem_card.reader.CardProtocol @@ -26,6 +28,8 @@ import com.tangem.ui.dialog.NoExtendedLengthSupportDialog import com.tangem.ui.dialog.WaitSecurityDelayDialog import com.tangem.ui.fragment.BaseFragment import com.tangem.ui.navigation.NavigationResultListener +import com.tangem.util.Analytics +import com.tangem.util.AnalyticsEvent import com.tangem.util.LOG import com.tangem.wallet.CoinEngine import com.tangem.wallet.CoinEngineFactory @@ -34,7 +38,6 @@ import com.tangem.wallet.TangemContext import kotlinx.android.synthetic.main.layout_progress_horizontal.* import kotlinx.android.synthetic.main.layout_touch_card.* import kotlinx.android.synthetic.tangemAccess.fragment_sign_transaction.* -import java.lang.IllegalArgumentException class SignTransactionFragment : BaseFragment(), NavigationResultListener, @@ -88,6 +91,9 @@ class SignTransactionFragment : BaseFragment(), NavigationResultListener, tvCardID.text = ctx.card!!.cidDescription progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY) progressBar.visibility = View.INVISIBLE + + FirebaseAnalytics.getInstance(requireActivity()) + .logEvent(AnalyticsEvent.READY_TO_SIGN.event, Analytics.setCardData(ctx)) } override fun onPause() { @@ -175,6 +181,10 @@ class SignTransactionFragment : BaseFragment(), NavigationResultListener, signTransactionTask = null if (cardProtocol != null) { if (cardProtocol.error == null) { + + FirebaseAnalytics.getInstance(requireActivity()) + .logEvent(AnalyticsEvent.SIGNED.event, Analytics.setCardData(ctx)) + rlProgressBar?.post { rlProgressBar?.visibility = View.GONE } progressBar?.post { @@ -185,6 +195,7 @@ class SignTransactionFragment : BaseFragment(), NavigationResultListener, mpFinishSignSound.start() } else { lastReadSuccess = false + FirebaseCrashlytics.getInstance().recordException(cardProtocol.error) if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) { progressBar?.post { progressBar?.progress = 100 diff --git a/build.gradle b/build.gradle index c523a7b20a..e3220b3ea2 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,8 @@ buildscript { classpath "com.android.tools.build:gradle:$versions.build_gradle" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" classpath "com.github.dcendents:android-maven-gradle-plugin:2.1" - classpath "io.fabric.tools:gradle:1.31.0" + classpath 'com.google.gms:google-services:4.3.3' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta03' } } diff --git a/dependencies.gradle b/dependencies.gradle index d7d33bc69f..f1121c113d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ - kotlin : '1.3.70', + kotlin : '1.3.71', build_gradle: '3.6.1', ] diff --git a/fastlane/Fastfile b/fastlane/Fastfile index b30c471eb1..ab218c5ea6 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -61,12 +61,12 @@ platform :android do }) end - desc "Submit a new Beta Build to Crashlytics Beta" + desc "Submit a new Beta Build to Firebase App Distribution" lane :beta do |options| - crashlytics( - api_token: options[:api_token], - build_secret: options[:build_secret], - apk_path: options[:apk_path]) + firebase_app_distribution( + app: options[:app_id], + apk_path: options[:apk_path], + groups: options[:groups]) end end diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile new file mode 100644 index 0000000000..b18539bc9b --- /dev/null +++ b/fastlane/Pluginfile @@ -0,0 +1,5 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-firebase_app_distribution' diff --git a/fastlane/README.md b/fastlane/README.md index 7c3f972135..7ef939cccf 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -35,7 +35,7 @@ Build debug and release APKs ``` fastlane android beta ``` -Submit a new Beta Build to Crashlytics Beta +Submit a new Beta Build to Firebase App Distribution ---- diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7a3265ee94..5c2d1cf016 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3e4d7dcd2a..3cbbb9a496 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Feb 27 13:33:42 AST 2020 +#Mon Mar 30 09:15:33 MSK 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip diff --git a/gradlew b/gradlew index cccdd3d517..83f2acfdc3 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -109,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` diff --git a/gradlew.bat b/gradlew.bat index e95643d6a2..24467a141f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/server-android/src/main/res/drawable/card_tg061.png b/server-android/src/main/res/drawable/card_tg061.png index d836185971..e49f9df65b 100644 Binary files a/server-android/src/main/res/drawable/card_tg061.png and b/server-android/src/main/res/drawable/card_tg061.png differ diff --git a/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java b/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java index 37da532954..7d924574a6 100644 --- a/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java +++ b/tangem-card-old/src/main/java/com/tangem/tangem_card/data/TangemCard.java @@ -8,7 +8,6 @@ import com.tangem.tangem_card.reader.TLVList; import com.tangem.tangem_card.util.Util; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Date; import java.util.EnumSet; @@ -359,6 +358,11 @@ public class TangemCard { return (settingsMask & SettingsMask.AllowSwapPIN2) != 0; } + public Boolean forbidPurgeWallet() { + if (settingsMask == null) return null; + return (settingsMask & SettingsMask.ForbidPurgeWallet) != 0; + } + public Boolean needCVC() { if (settingsMask == null) return null; return (settingsMask & SettingsMask.UseCVC) != 0; diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index f5d064d4e9..c5a1f99e77 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -1,10 +1,13 @@ package com.tangem import com.tangem.commands.* -import com.tangem.commands.personalization.CardConfig import com.tangem.commands.personalization.DepersonalizeCommand import com.tangem.commands.personalization.DepersonalizeResponse import com.tangem.commands.personalization.PersonalizeCommand +import com.tangem.commands.personalization.entities.Acquirer +import com.tangem.commands.personalization.entities.CardConfig +import com.tangem.commands.personalization.entities.Issuer +import com.tangem.commands.personalization.entities.Manufacturer import com.tangem.common.CardEnvironment import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils @@ -260,16 +263,15 @@ class CardManager( * During this procedure all data exchange is encrypted. * @param config is a configuration file with all the card settings that are written on the card * during personalization. - * @param cardId this parameter will set up CID, Unique Tangem card ID. + * @param issuer Issuer is a third-party team or company wishing to use Tangem cards. + * @param manufacturer Tangem Card Manufacturer. + * @param acquirer Acquirer is a trusted third-party company that operates proprietary + * (non-EMV) POS terminal infrastructure and transaction processing back-end. */ fun personalize(config: CardConfig, - cardId: String, + issuer: Issuer, manufacturer: Manufacturer, acquirer: Acquirer? = null, callback: (result: TaskEvent) -> Unit) { - if (this.config.issuer == null) { - callback(TaskEvent.Completion(TaskError.IssuerIsRequired())) - return - } - val personalizationCommand = PersonalizeCommand(config, cardId) + val personalizationCommand = PersonalizeCommand(config, issuer, manufacturer, acquirer) val task = SingleCommandTask(personalizationCommand) task.performPreflightRead = false runTask(task, callback = callback) @@ -320,10 +322,7 @@ class CardManager( val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null return CardEnvironment( cardId = cardId, - terminalKeys = terminalKeys, - manufacturerKeyPair = config.manufacturerKeyPair, - acquirerKeyPair = config.acquirerKeyPair, - issuer = config.issuer + terminalKeys = terminalKeys ) } diff --git a/tangem-core/src/main/java/com/tangem/Config.kt b/tangem-core/src/main/java/com/tangem/Config.kt index 2067f61920..2d1193e176 100644 --- a/tangem-core/src/main/java/com/tangem/Config.kt +++ b/tangem-core/src/main/java/com/tangem/Config.kt @@ -1,12 +1,6 @@ package com.tangem -import com.tangem.commands.personalization.entities.Issuer -import com.tangem.common.KeyPair - class Config( val linkedTerminal: Boolean = true, - val issuerPublicKey: ByteArray? = null, - val manufacturerKeyPair: KeyPair? = null, - val acquirerKeyPair: KeyPair? = null, - val issuer: Issuer? = null + val issuerPublicKey: ByteArray? = null ) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index ba5954fe01..656c1750f0 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -9,9 +9,6 @@ import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag import com.tangem.tasks.TaskError -/** -[REDACTED_AUTHOR] - */ class ReadUserDataResponse( /** * CID, Unique Tangem card ID number. diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index 804abf6601..ac79cba45c 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -9,10 +9,6 @@ import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag import com.tangem.tasks.TaskError -/** -[REDACTED_AUTHOR] - */ - class WriteUserDataResponse( /** * CID, Unique Tangem card ID number. diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt deleted file mode 100644 index 15548f9871..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/CardConfig.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.tangem.commands.personalization - -import com.tangem.commands.* - -data class NdefRecord( - val type: Type, - val value: String -) { - enum class Type { - URI, AAR, TEXT - } - - val valueInBytes: ByteArray by lazy { value.toByteArray() } -} - -/** - * It is a configuration file with all the card settings that are written on the card - * during [PersonalizeCommand]. - */ -data class CardConfig( - val issuerName: String? = null, - val acquirerName: String? = null, - val series: String? = null, - val startNumber: Long = 0, - val count: Int = 0, - val pin: String, - val pin2: String, - val pin3: String, - val hexCrExKey: String?, - val cvc: String, - val pauseBeforePin2: Int, - val smartSecurityDelay: Boolean, - val curveID: EllipticCurve, - val signingMethod: SigningMethod, - val maxSignatures: Int, - val isReusable: Boolean, - val allowSwapPin: Boolean, - val allowSwapPin2: Boolean, - val useActivation: Boolean, - val useCvc: Boolean, - val useNdef: Boolean, - val useDynamicNdef: Boolean, - val useOneCommandAtTime: Boolean, - val useBlock: Boolean, - val allowSelectBlockchain: Boolean, - val forbidPurgeWallet: Boolean, - val protocolAllowUnencrypted: Boolean, - val protocolAllowStaticEncryption: Boolean, - val protectIssuerDataAgainstReplay: Boolean, - val forbidDefaultPin: Boolean, - val disablePrecomputedNdef: Boolean, - val skipSecurityDelayIfValidatedByIssuer: Boolean, - val skipCheckPIN2andCVCIfValidatedByIssuer: Boolean, - val skipSecurityDelayIfValidatedByLinkedTerminal: Boolean, - - val restrictOverwriteIssuerDataEx: Boolean, - - val requireTerminalTxSignature: Boolean, - val requireTerminalCertSignature: Boolean, - val checkPin3onCard: Boolean, - - val createWallet: Boolean, - - val cardData: CardData, - val ndefRecords: List -) { - - fun getSettingsMask(): SettingsMask { - val builder = SettingsMaskBuilder() - - if (allowSwapPin) builder.add(Settings.AllowSwapPIN) - if (allowSwapPin2) builder.add(Settings.AllowSwapPIN2) - if (useCvc) builder.add(Settings.UseCVC) - if (isReusable) builder.add(Settings.IsReusable) - - if (useOneCommandAtTime) builder.add(Settings.UseOneCommandAtTime) - if (useNdef) builder.add(Settings.UseNdef) - if (useDynamicNdef) builder.add(Settings.UseDynamicNdef) - if (disablePrecomputedNdef) builder.add(Settings.DisablePrecomputedNdef) - - if (protocolAllowUnencrypted) builder.add(Settings.ProtocolAllowUnencrypted) - if (protocolAllowStaticEncryption) builder.add(Settings.ProtocolAllowStaticEncryption) - - if (forbidDefaultPin) builder.add(Settings.ForbidDefaultPIN) - - if (useActivation) builder.add(Settings.UseActivation) - - if (useBlock) builder.add(Settings.UseBlock) - if (smartSecurityDelay) builder.add(Settings.SmartSecurityDelay) - - if (protectIssuerDataAgainstReplay) builder.add(Settings.ProtectIssuerDataAgainstReplay) - - if (forbidPurgeWallet) builder.add(Settings.ForbidPurgeWallet) - if (allowSelectBlockchain) builder.add(Settings.AllowSelectBlockchain) - - if (skipCheckPIN2andCVCIfValidatedByIssuer) builder.add(Settings.SkipCheckPin2andCvcIfValidatedByIssuer) - if (skipSecurityDelayIfValidatedByIssuer) builder.add(Settings.SkipSecurityDelayIfValidatedByIssuer) - - if (skipSecurityDelayIfValidatedByLinkedTerminal) builder.add(Settings.SkipSecurityDelayIfValidatedByLinkedTerminal) - if (restrictOverwriteIssuerDataEx) builder.add(Settings.RestrictOverwriteIssuerDataEx) - - if (requireTerminalTxSignature) builder.add(Settings.RequireTermTxSignature) - - if (requireTerminalCertSignature) builder.add(Settings.RequireTermCertSignature) - - if (checkPin3onCard) builder.add(Settings.CheckPIN3onCard) - - return builder.build() - } - - companion object -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt index 46239ea4ea..034b28dce5 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt @@ -20,8 +20,7 @@ class DepersonalizeCommand : CommandSerializer() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { return CommandApdu( - Instruction.Depersonalize, byteArrayOf(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + Instruction.Depersonalize, byteArrayOf() ) } diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt index a034d171cb..6c6103556e 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt @@ -1,5 +1,6 @@ package com.tangem.commands.personalization +import com.tangem.commands.personalization.entities.NdefRecord import java.io.ByteArrayOutputStream import java.nio.charset.StandardCharsets diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt index 6bcdd815a5..035963c21a 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt @@ -3,7 +3,7 @@ package com.tangem.commands.personalization import com.tangem.commands.Card import com.tangem.commands.CardData import com.tangem.commands.CommandSerializer -import com.tangem.commands.personalization.entities.Issuer +import com.tangem.commands.personalization.entities.* import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction @@ -23,22 +23,23 @@ import com.tangem.tasks.TaskError * Personalization is an initialization procedure, required before starting using a card. * During this procedure a card setting is set up. * During this procedure all data exchange is encrypted. - * @param config is a configuration file with all the card settings that are written on the card + * @property config is a configuration file with all the card settings that are written on the card * during personalization. - * @param cardId this parameter will set up CID, Unique Tangem card ID. + * @property issuer Issuer is a third-party team or company wishing to use Tangem cards. + * @property manufacturer Tangem Card Manufacturer. + * @property acquirer Acquirer is a trusted third-party company that operates proprietary + * (non-EMV) POS terminal infrastructure and transaction processing back-end. */ -class PersonalizeCommand(private val config: CardConfig, private val cardId: String) : CommandSerializer() { +class PersonalizeCommand( + private val config: CardConfig, + private val issuer: Issuer, private val manufacturer: Manufacturer, + private val acquirer: Acquirer? = null +) : CommandSerializer() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - if (cardEnvironment.issuer == null || cardEnvironment.manufacturerKeyPair == null) { - throw TaskError.SerializeCommandError() - } return CommandApdu( Instruction.Personalize, - serializePersonalizationData( - cardId, config, - cardEnvironment.issuer, cardEnvironment.manufacturerKeyPair.privateKey, - cardEnvironment.acquirerKeyPair?.publicKey), + serializePersonalizationData(config), encryptionKey = devPersonalizationKey ) } @@ -99,20 +100,19 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str ) } - private fun serializePersonalizationData(cardId: String, config: CardConfig, - issuer: Issuer, manufacturerPrivateKey: ByteArray, - acquirePublicKey: ByteArray? - ): ByteArray { + private fun serializePersonalizationData(config: CardConfig): ByteArray { + val cardId = config.createCardId() ?: throw TaskError.SerializeCommandError() + val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.CardId, cardId) - tlvBuilder.append(TlvTag.CurveId, config.curveID) tlvBuilder.append(TlvTag.MaxSignatures, config.maxSignatures) tlvBuilder.append(TlvTag.SigningMethod, config.signingMethod) - tlvBuilder.append(TlvTag.SettingsMask, config.getSettingsMask()) + tlvBuilder.append(TlvTag.SettingsMask, config.createSettingsMask()) tlvBuilder.append(TlvTag.PauseBeforePin2, config.pauseBeforePin2 / 10) tlvBuilder.append(TlvTag.Cvc, config.cvc.toByteArray()) - if (!config.ndefRecords.isNullOrEmpty()) tlvBuilder.append(TlvTag.NdefData, serializeNdef(config.ndefRecords)) + if (!config.ndefRecords.isNullOrEmpty()) + tlvBuilder.append(TlvTag.NdefData, serializeNdef(config)) tlvBuilder.append(TlvTag.CreateWalletAtPersonalize, config.createWallet) @@ -123,25 +123,22 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str tlvBuilder.append(TlvTag.IssuerDataPublicKey, issuer.dataKeyPair.publicKey) tlvBuilder.append(TlvTag.IssuerTransactionPublicKey, issuer.transactionKeyPair.publicKey) - tlvBuilder.append(TlvTag.AcquirerPublicKey, acquirePublicKey) + tlvBuilder.append(TlvTag.AcquirerPublicKey, acquirer?.keyPair?.publicKey) - tlvBuilder.append( - TlvTag.CardData, serializeCardData(cardId, config.cardData, issuer, manufacturerPrivateKey) - ) + tlvBuilder.append(TlvTag.CardData, serializeCardData(cardId, config.cardData)) return tlvBuilder.serialize() } - private fun serializeCardData( - cardId: String, cardData: CardData, - issuer: Issuer, manufacturerPrivateKey: ByteArray): ByteArray { + private fun serializeNdef(config: CardConfig): ByteArray { + return NdefEncoder(config.ndefRecords, config.useDynamicNdef).encode() + } + + private fun serializeCardData(cardId: String, cardData: CardData): ByteArray { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.Batch, cardData.batchId) tlvBuilder.append(TlvTag.ProductMask, cardData.productMask) - tlvBuilder.append(TlvTag.ManufactureDateTime, cardData.manufactureDateTime) - tlvBuilder.append(TlvTag.IssuerId, issuer.id) - tlvBuilder.append(TlvTag.BlockchainId, cardData.blockchainName) if (cardData.tokenSymbol != null) { @@ -150,15 +147,12 @@ class PersonalizeCommand(private val config: CardConfig, private val cardId: Str tlvBuilder.append(TlvTag.TokenDecimal, cardData.tokenDecimal) } tlvBuilder.append( - TlvTag.CardIdManufacturerSignature, cardId.hexToBytes().sign(manufacturerPrivateKey) + TlvTag.CardIdManufacturerSignature, + cardId.hexToBytes().sign(manufacturer.keyPair.privateKey) ) return tlvBuilder.serialize() } - private fun serializeNdef(ndefRecords: List): ByteArray { - return NdefEncoder(ndefRecords, config.useDynamicNdef).encode() - } - companion object { val devPersonalizationKey = "1234".calculateSha256().copyOf(32) } diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt new file mode 100644 index 0000000000..cda239b145 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt @@ -0,0 +1,9 @@ +package com.tangem.commands.personalization.entities + +import com.tangem.common.KeyPair + +data class Acquirer( + val keyPair: KeyPair, + val name: String? = null, + val id: String? = null +) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt new file mode 100644 index 0000000000..bc1a082237 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt @@ -0,0 +1,70 @@ +package com.tangem.commands.personalization.entities + +import com.tangem.commands.CardData +import com.tangem.commands.EllipticCurve +import com.tangem.commands.SigningMethod + +data class NdefRecord( + val type: Type, + val value: String +) { + enum class Type { + URI, AAR, TEXT + } + + val valueInBytes: ByteArray by lazy { value.toByteArray() } +} + +/** + * It is a configuration file with all the card settings that are written on the card + * during [PersonalizeCommand]. + */ +data class CardConfig( + val issuerName: String? = null, + val acquirerName: String? = null, + val series: String? = null, + val startNumber: Long = 0, + val count: Int = 0, + val pin: String, + val pin2: String, + val pin3: String, + val hexCrExKey: String?, + val cvc: String, + val pauseBeforePin2: Int, + val smartSecurityDelay: Boolean, + val curveID: EllipticCurve, + val signingMethod: SigningMethod, + val maxSignatures: Int, + val isReusable: Boolean, + val allowSwapPin: Boolean, + val allowSwapPin2: Boolean, + val useActivation: Boolean, + val useCvc: Boolean, + val useNdef: Boolean, + val useDynamicNdef: Boolean, + val useOneCommandAtTime: Boolean, + val useBlock: Boolean, + val allowSelectBlockchain: Boolean, + val forbidPurgeWallet: Boolean, + val protocolAllowUnencrypted: Boolean, + val protocolAllowStaticEncryption: Boolean, + val protectIssuerDataAgainstReplay: Boolean, + val forbidDefaultPin: Boolean, + val disablePrecomputedNdef: Boolean, + val skipSecurityDelayIfValidatedByIssuer: Boolean, + val skipCheckPIN2andCVCIfValidatedByIssuer: Boolean, + val skipSecurityDelayIfValidatedByLinkedTerminal: Boolean, + + val restrictOverwriteIssuerDataEx: Boolean, + + val requireTerminalTxSignature: Boolean, + val requireTerminalCertSignature: Boolean, + val checkPin3onCard: Boolean, + + val createWallet: Boolean, + + val cardData: CardData, + val ndefRecords: List +) { + companion object +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfigExtensions.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfigExtensions.kt new file mode 100644 index 0000000000..4d7895f3c4 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfigExtensions.kt @@ -0,0 +1,81 @@ +package com.tangem.commands.personalization.entities + +import com.tangem.commands.Settings +import com.tangem.commands.SettingsMask +import com.tangem.commands.SettingsMaskBuilder + +internal fun CardConfig.createSettingsMask(): SettingsMask { + val builder = SettingsMaskBuilder() + + if (allowSwapPin) builder.add(Settings.AllowSwapPIN) + if (allowSwapPin2) builder.add(Settings.AllowSwapPIN2) + if (useCvc) builder.add(Settings.UseCVC) + if (isReusable) builder.add(Settings.IsReusable) + + if (useOneCommandAtTime) builder.add(Settings.UseOneCommandAtTime) + if (useNdef) builder.add(Settings.UseNdef) + if (useDynamicNdef) builder.add(Settings.UseDynamicNdef) + if (disablePrecomputedNdef) builder.add(Settings.DisablePrecomputedNdef) + + if (protocolAllowUnencrypted) builder.add(Settings.ProtocolAllowUnencrypted) + if (protocolAllowStaticEncryption) builder.add(Settings.ProtocolAllowStaticEncryption) + + if (forbidDefaultPin) builder.add(Settings.ForbidDefaultPIN) + + if (useActivation) builder.add(Settings.UseActivation) + + if (useBlock) builder.add(Settings.UseBlock) + if (smartSecurityDelay) builder.add(Settings.SmartSecurityDelay) + + if (protectIssuerDataAgainstReplay) builder.add(Settings.ProtectIssuerDataAgainstReplay) + + if (forbidPurgeWallet) builder.add(Settings.ForbidPurgeWallet) + if (allowSelectBlockchain) builder.add(Settings.AllowSelectBlockchain) + + if (skipCheckPIN2andCVCIfValidatedByIssuer) builder.add(Settings.SkipCheckPin2andCvcIfValidatedByIssuer) + if (skipSecurityDelayIfValidatedByIssuer) builder.add(Settings.SkipSecurityDelayIfValidatedByIssuer) + + if (skipSecurityDelayIfValidatedByLinkedTerminal) builder.add(Settings.SkipSecurityDelayIfValidatedByLinkedTerminal) + if (restrictOverwriteIssuerDataEx) builder.add(Settings.RestrictOverwriteIssuerDataEx) + + if (requireTerminalTxSignature) builder.add(Settings.RequireTermTxSignature) + + if (requireTerminalCertSignature) builder.add(Settings.RequireTermCertSignature) + + if (checkPin3onCard) builder.add(Settings.CheckPIN3onCard) + + return builder.build() +} + +internal fun CardConfig.createCardId(): String? { + if (series == null) return null + if (startNumber <= 0 || (series.length != 2 && series.length != 4)) return null + + val Alf = "ABCDEF0123456789" + fun checkSeries(series: String): Boolean { + val containsList = series.filter { Alf.contains(it) } + return containsList.length == series.length + } + if (!checkSeries(series)) return null + + val tail = if (series.length == 2) String.format("%013d", startNumber) else String.format("%011d", startNumber) + var cardId = (series + tail).replace(" ", "") + if (cardId.length != 15 || Alf.indexOf(cardId[0]) == -1 || Alf.indexOf(cardId[1]) == -1) + return null + + cardId += "0" + val length = cardId.length + var sum = 0 + for (i in 0 until length) { + // get digits in reverse order + var digit: Int + val cDigit = cardId[length - i - 1] + digit = if (cDigit in '0'..'9') cDigit - '0' else cDigit - 'A' + + // every 2nd number multiply with 2 + if (i % 2 == 1) digit *= 2 + sum += if (digit > 9) digit - 9 else digit + } + val lunh = (10 - sum % 10) % 10 + return cardId.substring(0, 15) + String.format("%d", lunh) +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt new file mode 100644 index 0000000000..bffdde9384 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt @@ -0,0 +1,8 @@ +package com.tangem.commands.personalization.entities + +import com.tangem.common.KeyPair + +data class Manufacturer( + val keyPair: KeyPair, + val name: String? = null +) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt index bc507bf9a2..09cfd736b4 100644 --- a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt @@ -1,6 +1,7 @@ package com.tangem.common -import com.tangem.commands.personalization.entities.Issuer +import com.tangem.commands.EllipticCurve +import com.tangem.crypto.CryptoUtils.generatePublicKey /** @@ -14,10 +15,7 @@ data class CardEnvironment( val terminalKeys: KeyPair? = null, var encryptionMode: EncryptionMode = EncryptionMode.NONE, var encryptionKey: ByteArray? = null, - val cvc: ByteArray? = null, - val manufacturerKeyPair: KeyPair? = null, - val acquirerKeyPair: KeyPair? = null, - val issuer: Issuer? = null + val cvc: ByteArray? = null ) { companion object { @@ -32,4 +30,8 @@ enum class EncryptionMode(val code: Byte) { STRONG(0x2) } -class KeyPair(val publicKey: ByteArray, val privateKey: ByteArray) \ No newline at end of file +class KeyPair(val publicKey: ByteArray, val privateKey: ByteArray) { + + constructor(privateKey: ByteArray, curve: EllipticCurve = EllipticCurve.Secp256k1) : + this(generatePublicKey(privateKey, curve), privateKey) +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/String.kt b/tangem-core/src/main/java/com/tangem/common/extensions/String.kt index 2f9d73f416..fbddb5829c 100644 --- a/tangem-core/src/main/java/com/tangem/common/extensions/String.kt +++ b/tangem-core/src/main/java/com/tangem/common/extensions/String.kt @@ -12,6 +12,12 @@ fun String.calculateSha256(): ByteArray { return sha256.digest(data) } +fun String.calculateSha512(): ByteArray { + val sha = MessageDigest.getInstance("SHA-512") + val data = this.toByteArray(Charset.forName("UTF-8")) + return sha.digest(data) +} + fun String.hexToBytes(): ByteArray { return ByteArray(this.length / 2) { i -> diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt index b585cd3023..224b163a9a 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -141,12 +141,6 @@ sealed class TaskError(val code: Int) : Exception() { * (when the card's requires it), but the counter is missing. */ class MissingCounter : TaskError(7001) - - /** - * This error is returned when [com.tangem.commands.personalization.PersonalizeCommand] is attempted - * without [com.tangem.commands.personalization.entities.Issuer] being set in the [com.tangem.Config]. - */ - class IssuerIsRequired : TaskError(7002) } /** diff --git a/tangem-demo/build.gradle b/tangem-demo/build.gradle index 614e6b9552..fa9e64d58f 100644 --- a/tangem-demo/build.gradle +++ b/tangem-demo/build.gradle @@ -3,8 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 29 - buildToolsVersion "29.0.2" - + buildToolsVersion "29.0.3" defaultConfig { applicationId "com.tangem.tangemtest" @@ -34,13 +33,23 @@ android { dependencies { implementation project(':tangem-core') implementation project(':tangem-sdk') - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.2.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" + + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.2.0' + implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4" + implementation "androidx.navigation:navigation-fragment-ktx:2.2.1" + implementation "androidx.navigation:navigation-ui-ktx:2.2.1" + implementation "androidx.recyclerview:recyclerview:1.2.0-alpha01" + implementation "com.google.android.material:material:1.2.0-alpha05" + implementation "androidx.viewpager2:viewpager2:1.0.0" + + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.github.gbIxaHue:eu4d:0.3.7' } diff --git a/tangem-demo/src/main/AndroidManifest.xml b/tangem-demo/src/main/AndroidManifest.xml index f90fa6c597..cf184dcfe2 100644 --- a/tangem-demo/src/main/AndroidManifest.xml +++ b/tangem-demo/src/main/AndroidManifest.xml @@ -8,8 +8,8 @@ - - + - + diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt new file mode 100644 index 0000000000..c9fc162b6a --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt @@ -0,0 +1,29 @@ +package com.tangem.tangemtest + +import android.app.Application +import com.tangem.tangemtest._arch.structure.ILog +import com.tangem.tangemtest._arch.structure.ItemLogger +import com.tangem.tangemtest.commons.TangemLogger +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +class AppTangemDemo : Application() { + + override fun onCreate() { + super.onCreate() + + AppTangemDemo.appInstance = this + setupLoggers() + } + + private fun setupLoggers() { + Log.setLogger(TangemLogger()) + ILog.setLogger(ItemLogger()) + } + + companion object { + lateinit var appInstance: AppTangemDemo + } +} diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt similarity index 83% rename from tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt rename to tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt index 1a294a8897..b39081b6b0 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt @@ -4,15 +4,14 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.tangem.CardManager -import com.tangem.commands.personalization.CardConfig import com.tangem.tangem_sdk_new.extensions.init -import com.tangem.tangemtest.extensions.init +import com.tangem.tangemtest.extensions.create import com.tangem.tasks.ScanEvent import com.tangem.tasks.TaskError import com.tangem.tasks.TaskEvent -import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.old_activity_main.* -class MainActivity : AppCompatActivity() { +class Old_MainActivity : AppCompatActivity() { private lateinit var cardManager: CardManager private lateinit var cardId: String @@ -22,7 +21,7 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + setContentView(R.layout.old_activity_main) cardManager = CardManager.init(this) @@ -153,30 +152,6 @@ class MainActivity : AppCompatActivity() { } } btn_read_write_user_data?.setOnClickListener { startActivity(Intent(this, TestUserDataActivity::class.java)) } - btn_personalize?.setOnClickListener { _ -> - cardManager.personalize( - CardConfig.init(application), "BB00000000000395" - ) { - when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } - } - is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = it.data.cardId } - } - } - } - btn_depersonalize?.setOnClickListener { _ -> - cardManager.depersonalize(cardId) { - when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } - } - is TaskEvent.Event -> runOnUiThread { - tv_card_cid?.text = "Depersonalized: ${it.data.success.toString()}" - } - } - } - } } private fun createSampleHashes(): Array { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt index 5b187df946..9ffcba3103 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt @@ -19,162 +19,162 @@ import java.nio.charset.StandardCharsets /** [REDACTED_AUTHOR] */ -class TestUserDataActivity: AppCompatActivity() { +class TestUserDataActivity : AppCompatActivity() { - private lateinit var cardManager: CardManager - private lateinit var writeOptions: WriteOptions + private lateinit var cardManager: CardManager + private lateinit var writeOptions: WriteOptions - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_test_user_data) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_test_user_data) - init() - initWriteOptions() - } - - private fun init() { - cardManager = CardManager.init(this) - - btn_scan?.setOnClickListener { _ -> - cardManager.scanCard { taskEvent -> - when (taskEvent) { - is TaskEvent.Event -> { - when (taskEvent.data) { - is ScanEvent.OnReadEvent -> { - // Handle returned card data - writeOptions.cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId - runOnUiThread { showReadWriteSection(true) } - } - is ScanEvent.OnVerifyEvent -> { - //Handle card verification - } - } - } - is TaskEvent.Completion -> { - if (taskEvent.error != null) { - if (taskEvent.error is TaskError.UserCancelled) { - // Handle case when user cancelled manually - } - // Handle other errors - } - // Handle completion - } - } - } + init() + initWriteOptions() } - btn_write.setOnClickListener { - if (writeOptions.cardId == null) return@setOnClickListener + private fun init() { + cardManager = CardManager.init(this) - cardManager.writeUserData( - writeOptions.cardId !!, - writeOptions.userData, - writeOptions.userProtectedData, - writeOptions.userCounter, - writeOptions.userProtectedCounter - ) { - when (it) { - is TaskEvent.Completion -> handleError(tv_write_result, it.error) - is TaskEvent.Event -> { - runOnUiThread { - val data = it.data as? WriteUserDataResponse - if (data == null) { - tv_write_result.text = "Response doesn't match" - return@runOnUiThread - } - tv_write_result?.text = "Success" + btn_scan?.setOnClickListener { _ -> + cardManager.scanCard { taskEvent -> + when (taskEvent) { + is TaskEvent.Event -> { + when (taskEvent.data) { + is ScanEvent.OnReadEvent -> { + // Handle returned card data + writeOptions.cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId + runOnUiThread { showReadWriteSection(true) } + } + is ScanEvent.OnVerifyEvent -> { + //Handle card verification + } + } + } + is TaskEvent.Completion -> { + if (taskEvent.error != null) { + if (taskEvent.error is TaskError.UserCancelled) { + // Handle case when user cancelled manually + } + // Handle other errors + } + // Handle completion + } + } + } + } + + btn_write.setOnClickListener { + if (writeOptions.cardId == null) return@setOnClickListener + + cardManager.writeUserData( + writeOptions.cardId!!, + writeOptions.userData, + writeOptions.userProtectedData, + writeOptions.userCounter, + writeOptions.userProtectedCounter + ) { + when (it) { + is TaskEvent.Completion -> handleError(tv_write_result, it.error) + is TaskEvent.Event -> { + runOnUiThread { + val data = it.data as? WriteUserDataResponse + if (data == null) { + tv_write_result.text = "Response doesn't match" + return@runOnUiThread + } + tv_write_result?.text = "Success" + } + } + } + } + } + + btn_read.setOnClickListener { + if (writeOptions.cardId == null) return@setOnClickListener + + cardManager.readUserData(writeOptions.cardId!!) { + when (it) { + is TaskEvent.Completion -> handleError(tv_read_result, it.error) + is TaskEvent.Event -> { + runOnUiThread { + val data = it.data as? ReadUserDataResponse + if (data == null) { + tv_read_result.text = "Response doesn't match" + return@runOnUiThread + } + tv_read_result?.text = "Success" + + writeOptions.userData = data.userData + writeOptions.userProtectedData = data.userProtectedData + writeOptions.userCounter = data.userCounter + writeOptions.userProtectedCounter = data.userProtectedCounter + + tv_card_cid.text = data.cardId + tv_data.text = String(data.userData, StandardCharsets.US_ASCII) + tv_protected_data.text = String(data.userProtectedData, StandardCharsets.US_ASCII) + tv_counter.text = data.userCounter.toString() + tv_protected_counter.text = data.userProtectedCounter.toString() + } + + } + } } - } } - } } - btn_read.setOnClickListener { - if (writeOptions.cardId == null) return@setOnClickListener + private fun handleError(tv: TextView, error: TaskError?) { + val er = error ?: return + if (er is TaskError.UserCancelled) return - cardManager.readUserData(writeOptions.cardId !!) { - when (it) { - is TaskEvent.Completion -> handleError(tv_read_result, it.error) - is TaskEvent.Event -> { - runOnUiThread { - val data = it.data as? ReadUserDataResponse - if (data == null) { - tv_read_result.text = "Response doesn't match" - return@runOnUiThread - } - tv_read_result?.text = "Success" - - writeOptions.userData = data.userData - writeOptions.userProtectedData = data.userProtectedData - writeOptions.userCounter = data.userCounter - writeOptions.userProtectedCounter = data.userProtectedCounter - - tv_card_cid.text = data.cardId - tv_data.text = String(data.userData, StandardCharsets.US_ASCII) - tv_protected_data.text = String(data.userProtectedData, StandardCharsets.US_ASCII) - tv_counter.text = data.userCounter.toString() - tv_protected_counter.text = data.userProtectedCounter.toString() - } - - } - } - } + runOnUiThread { tv.text = er::class.simpleName } } - } - private fun handleError(tv: TextView, error: TaskError?) { - val er = error ?: return - if (er is TaskError.UserCancelled) return + private fun initWriteOptions() { + writeOptions = WriteOptions() - runOnUiThread { tv.text = er::class.simpleName } - } + chb_with_ud.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateData(buttonView) } + chb_with_ud_protected.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedData(buttonView) } + chb_with_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateCounter(buttonView) } + chb_with_protected_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedCounter(buttonView) } + chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) } + } - private fun initWriteOptions() { - writeOptions = WriteOptions() - - chb_with_ud.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateData(buttonView) } - chb_with_ud_protected.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedData(buttonView) } - chb_with_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateCounter(buttonView) } - chb_with_protected_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedCounter(buttonView) } - chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) } - } - - private fun showReadWriteSection(show: Boolean) { - val state = if (show) View.VISIBLE else View.GONE - cl_read_write.visibility = state - } + private fun showReadWriteSection(show: Boolean) { + val state = if (show) View.VISIBLE else View.GONE + cl_read_write.visibility = state + } } class WriteOptions { - var cardId: String? = null - var userData: ByteArray? = null - var userProtectedData: ByteArray? = null - var userCounter: Int? = null - var userProtectedCounter: Int? = null - var pin2: String? = null + var cardId: String? = null + var userData: ByteArray? = null + var userProtectedData: ByteArray? = null + var userCounter: Int? = null + var userProtectedCounter: Int? = null + var pin2: String? = null - fun updateData(chbx: CompoundButton) { - val value = "simple user data".toByteArray() - userData = if (chbx.isChecked) value else null - } + fun updateData(chbx: CompoundButton) { + val value = "simple user data".toByteArray() + userData = if (chbx.isChecked) value else null + } - fun updateProtectedData(chbx: CompoundButton) { - val value = "protected user data".toByteArray() - userProtectedData = if (chbx.isChecked) value else null - } + fun updateProtectedData(chbx: CompoundButton) { + val value = "protected user data".toByteArray() + userProtectedData = if (chbx.isChecked) value else null + } - fun updateCounter(chbx: CompoundButton) { - val value = if (userCounter == null) 0 else userCounter !! + 1 - userCounter = if (chbx.isChecked) value else null - } + fun updateCounter(chbx: CompoundButton) { + val value = if (userCounter == null) 0 else userCounter!! + 1 + userCounter = if (chbx.isChecked) value else null + } - fun updateProtectedCounter(chbx: CompoundButton) { - val value = if (userProtectedCounter == null) 0 else userProtectedCounter !! + 1 - userProtectedCounter = if (chbx.isChecked) value else null - } + fun updateProtectedCounter(chbx: CompoundButton) { + val value = if (userProtectedCounter == null) 0 else userProtectedCounter!! + 1 + userProtectedCounter = if (chbx.isChecked) value else null + } - fun updatePin2(chbx: CompoundButton) { - val value = CardEnvironment.DEFAULT_PIN2 - pin2 = if (chbx.isChecked) value else null - } + fun updatePin2(chbx: CompoundButton) { + val value = CardEnvironment.DEFAULT_PIN2 + pin2 = if (chbx.isChecked) value else null + } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/SingleLiveEvent.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/SingleLiveEvent.kt new file mode 100644 index 0000000000..943023a1c1 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/SingleLiveEvent.kt @@ -0,0 +1,44 @@ +package com.tangem.tangemtest._arch + +import android.util.Log +import androidx.annotation.MainThread +import androidx.annotation.Nullable +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import java.util.concurrent.atomic.AtomicBoolean + +/** +[REDACTED_AUTHOR] + */ +class SingleLiveEvent : MutableLiveData() { + private val mPending: AtomicBoolean = AtomicBoolean(false) + + override fun observe(owner: LifecycleOwner, observer: Observer) { + if (hasActiveObservers()) { + Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") + } + + // Observe the internal MutableLiveData + super.observe(owner, Observer { + if (mPending.compareAndSet(true, false)) { + observer.onChanged(it) + } + }) + } + + @MainThread + override fun setValue(@Nullable t: T?) { + mPending.set(true) + super.setValue(t) + } + + @MainThread + fun call() { + setValue(null) + } + + companion object { + private const val TAG = "SingleLiveEvent" + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt new file mode 100644 index 0000000000..98704005f5 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt @@ -0,0 +1,28 @@ +package com.tangem.tangemtest._arch.structure + +/** +[REDACTED_AUTHOR] + */ +interface DataHolder { + var viewModel: D +} + +typealias Payload = MutableMap + +interface PayloadHolder { + val payload: Payload + + fun get(key: String): Any? = payload[key] + fun remove(key: String): Any? = payload.remove(key) + fun set(key: String, value: Any?) { + payload[key] = value + } +} + +interface ItemListHolder { + fun setItems(list: MutableList) + fun getItems(): MutableList + fun addItem(item: I) + fun removeItem(item: I) + fun clear() +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt new file mode 100644 index 0000000000..3b40edcc77 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt @@ -0,0 +1,13 @@ +package com.tangem.tangemtest._arch.structure + +/** +[REDACTED_AUTHOR] + */ +interface Id + +enum class Additional : Id { + UNDEFINED, + JSON_INCOMING, + JSON_OUTGOING, + JSON_TAILS, +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Logger.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Logger.kt new file mode 100644 index 0000000000..e71fc33cb6 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Logger.kt @@ -0,0 +1,36 @@ +package com.tangem.tangemtest._arch.structure + +import ru.dev.gbixahue.eu4d.lib.android.global.log.Logger +import ru.dev.gbixahue.eu4d.lib.android.global.log.TagLogger +import ru.dev.gbixahue.eu4d.lib.android.global.log.profiling.SimpleLogProfiler +import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf + +/** +[REDACTED_AUTHOR] + */ +object ILog { + + private var logger: Logger? = null + + fun setLogger(logger: Logger) { + ILog.logger = logger + } + + fun d(from: Any, msg: Any?, value: Any? = null) { + logger?.d(from, stringOf(msg), value) + } + + fun w(from: Any, msg: Any?, value: Any? = null) { + logger?.w(from, stringOf(msg), value) + } + + fun e(from: Any, msg: Any?, value: Any? = null) { + logger?.e(from, stringOf(msg), value) + } +} + +class ItemLogger : TagLogger("ITEM") { + init { + msProfiler = SimpleLogProfiler() + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt new file mode 100644 index 0000000000..11086e6f93 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt @@ -0,0 +1,50 @@ +package com.tangem.tangemtest._arch.structure.abstraction + +import com.tangem.tangemtest._arch.structure.ILog +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.ItemListHolder + +/** +[REDACTED_AUTHOR] + */ +interface Block : Item { + val itemList: MutableList +} + +abstract class BaseBlock : Block { + override var parent: Item? = null + override val itemList: MutableList = mutableListOf() + override val payload: MutableMap = mutableMapOf() +} + +open class ListItemBlock( + override val id: Id +) : BaseBlock(), ItemListHolder { + + override fun setItems(list: MutableList) { + ILog.d(this, "setItems into: $id, count: ${list.size}") + itemList.clear() + itemList.addAll(list) + itemList.forEach { it.parent = this } + } + + override fun getItems(): MutableList { + return itemList + } + + override fun addItem(item: Item) { + ILog.d(this, "addItem into: $id, who: ${item.id}") + item.parent = this + itemList.add(item) + } + + override fun removeItem(item: Item) { + ILog.d(this, "removeItem from: $id, which: ${item.id}") + itemList.remove(item) + } + + override fun clear() { + ILog.d(this, "clear $id") + itemList.clear() + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt new file mode 100644 index 0000000000..2cfbc69173 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt @@ -0,0 +1,31 @@ +package com.tangem.tangemtest._arch.structure.abstraction + +import com.tangem.tangemtest._arch.structure.DataHolder +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.PayloadHolder + +/** +[REDACTED_AUTHOR] + */ +interface Item : PayloadHolder { + val id: Id + var parent: Item? +} + +abstract class BaseItem( + override var viewModel: ItemViewModel +) : Item, DataHolder> { + override var parent: Item? = null + override val payload: Payload = mutableMapOf() + + fun getData(): D? = viewModel.data + + fun setData(value: D?) { + viewModel.data = value + } + + fun restoreDefaultData(){ + setData(viewModel.defaultData) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Plugin.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Plugin.kt new file mode 100644 index 0000000000..71bdc779c7 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Plugin.kt @@ -0,0 +1,10 @@ +package com.tangem.tangemtest._arch.structure.abstraction + +/** +[REDACTED_AUTHOR] + */ +typealias PluginCallBackResult = (Any?) -> Item + +interface Plugin : Item { + fun invoke(data: Any?, asyncCallback: PluginCallBackResult? = null): Any? +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt new file mode 100644 index 0000000000..63a54281d5 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt @@ -0,0 +1,71 @@ +package com.tangem.tangemtest._arch.structure.abstraction + +import com.tangem.tangemtest._arch.structure.ILog +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.PayloadHolder + + +/** +[REDACTED_AUTHOR] + */ +typealias ValueChange = (V?) -> Unit +typealias SafeValueChange = (V) -> Unit + +interface ItemViewModel : PayloadHolder { + val viewState: ViewState + var data: D? + var defaultData: D? + var onDataUpdated: ValueChange? + + fun updateDataByView(data: D?) +} + +open class BaseItemViewModel : ItemViewModel { + + override val viewState: ViewState = ViewState() + override val payload: Payload = mutableMapOf() + + // Don't update it directly from a View. Use for it updateDataByView() + override var data: D? = null + set(value) { + if (handleDataUpdates(value)) field = value + } + + // Data for restoring initial value + override var defaultData: D? = null + set(value) { + field = value + data = value + } + + // Use it for handling data updates in View + override var onDataUpdated: ValueChange? = null + + // When data updates directly it invokes onDataUpdated + // return true = data will update + // return false = data won't update + protected open fun handleDataUpdates(value: D?): Boolean { + ILog.d(this, "handleDateUpdates: $value") + onDataUpdated?.invoke(value) + return true + } + + // Use it to update the data from a View. It disables onDataUpdated to prevent a callback loop + override fun updateDataByView(data: D?) { + ILog.d(this, "data changed: $data") + val callback = onDataUpdated + onDataUpdated = null + this.data = data + onDataUpdated = callback + } +} + +class ViewState { + var descriptionVisibility: Int = 0x00000008 + set(value) { + field = value + onDescriptionVisibilityChanged?.invoke(value) + } + + var onDescriptionVisibilityChanged: SafeValueChange? = null +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt new file mode 100644 index 0000000000..11ea5510c0 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt @@ -0,0 +1,19 @@ +package com.tangem.tangemtest._arch.structure.impl + +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem + +/** +[REDACTED_AUTHOR] + */ +class AnyItem(override val id: Id, value: Any? = null) : BaseItem(AnyViewModel(value)) +class TextItem(override val id: Id, value: String? = null) : BaseItem(StringViewModel(value)) +class EditTextItem(override val id: Id, value: String? = null) : BaseItem(StringViewModel(value)) +class NumberItem(override val id: Id, value: Number? = null) : BaseItem(NumberViewModel(value)) +class BoolItem(override val id: Id, value: Boolean? = null) : BaseItem(BoolViewModel(value)) + +class ListItem(override val id: Id, value: List, selectedValue: Any?) + : BaseItem(ListViewModel(ListValueWrapper(selectedValue, value)) +) + +inline fun BaseItem.getData(): T? = viewModel.data \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt new file mode 100644 index 0000000000..f082577e70 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt @@ -0,0 +1,29 @@ +package com.tangem.tangemtest._arch.structure.impl + +import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel + +/** +[REDACTED_AUTHOR] + */ +open class TransitiveViewModel(value: D?) : BaseItemViewModel() { + init { + defaultData = value + } +} + +class AnyViewModel(value: Any? = null) : TransitiveViewModel(value) +class StringViewModel(value: String? = null) : TransitiveViewModel(value) +class NumberViewModel(value: Number? = null) : TransitiveViewModel(value) +class BoolViewModel(value: Boolean? = null) : TransitiveViewModel(value) +class ListViewModel(value: ListValueWrapper? = null) : TransitiveViewModel(value) { + + override fun handleDataUpdates(value: ListValueWrapper?): Boolean { + val newValue = value ?: return false + if (newValue.selectedItem == data?.selectedItem) return false + + return super.handleDataUpdates(value) + } +} + +class KeyValue(val key: String, val value: Any) +class ListValueWrapper(var selectedItem: Any?, val itemList: List) \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt new file mode 100644 index 0000000000..4e774f0be7 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt @@ -0,0 +1,62 @@ +package com.tangem.tangemtest._main + +import android.content.res.Resources +import android.os.Bundle +import android.util.Log +import android.view.Menu +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SwitchCompat +import androidx.appcompat.widget.Toolbar +import androidx.navigation.findNavController +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.navigateUp +import androidx.navigation.ui.setupActionBarWithNavController +import com.tangem.tangemtest.R + +/** +[REDACTED_AUTHOR] + */ +class MainActivity : AppCompatActivity() { + + private val vm: MainViewModel by viewModels() + private lateinit var appBarConfiguration: AppBarConfiguration + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.a_main) + + val toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + + val host: NavHostFragment = supportFragmentManager + .findFragmentById(R.id.nav_host_fragment) as NavHostFragment? ?: return + + val navController = host.navController + appBarConfiguration = AppBarConfiguration(setOf(R.id.nav_entry_point)) + setupActionBarWithNavController(navController, appBarConfiguration) + + navController.addOnDestinationChangedListener { _, destination, _ -> + val dest: String = try { + resources.getResourceName(destination.id) + } catch (e: Resources.NotFoundException) { + destination.id.toString() + } + Log.d("NavigationActivity", "Navigated to $dest") + } + } + + override fun onSupportNavigateUp(): Boolean { + return findNavController(R.id.nav_host_fragment).navigateUp(appBarConfiguration) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.main_menu, menu) + val switchMenu = menu.findItem(R.id.action_favorite) + (switchMenu.actionView as? SwitchCompat)?.let { + it.setOnCheckedChangeListener { buttonView, isChecked -> vm.switchToggled(isChecked) } + } + return super.onCreateOptionsMenu(menu) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt new file mode 100644 index 0000000000..17ce97d8f9 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt @@ -0,0 +1,15 @@ +package com.tangem.tangemtest._main + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +/** +[REDACTED_AUTHOR] + */ +class MainViewModel : ViewModel() { + val ldDescriptionSwitch = MutableLiveData(false) + + fun switchToggled(state: Boolean) { + ldDescriptionSwitch.postValue(state) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt new file mode 100644 index 0000000000..313c4aaffe --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt @@ -0,0 +1,87 @@ +package com.tangem.tangemtest._main.entryPoint + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.navigation.NavController +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.transition.AutoTransition +import androidx.transition.TransitionManager +import com.tangem.tangemtest.R +import com.tangem.tangemtest._main.MainViewModel +import com.tangem.tangemtest.ucase.getDefaultNavigationOptions +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.resources.MainResourceHolder +import kotlinx.android.synthetic.main.fg_entry_point.* + +/** +[REDACTED_AUTHOR] + */ +class ActionListFragment : Fragment() { + + private val navController: NavController by lazy { findNavController() } + private lateinit var rvActions: RecyclerView + + private val mainActivityVM: MainViewModel by activityViewModels() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fg_entry_point, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initRecyclerView() + } + + private fun initRecyclerView() { + val layoutManager = LinearLayoutManager(activity) + rvActions = rv_actions + rvActions.layoutManager = layoutManager + rvActions.addItemDecoration(DividerItemDecoration(activity, layoutManager.orientation)) + + val vhDataWrapper = VhExDataWrapper(MainResourceHolder, false) + + rvActions.adapter = RvActionsAdapter(vhDataWrapper) { type, position, data -> navigate(data) } + mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer { + vhDataWrapper.descriptionIsVisible = it + TransitionManager.beginDelayedTransition(rvActions as ViewGroup, AutoTransition()) + rvActions.adapter?.notifyDataSetChanged() + }) + } + + override fun onResume() { + super.onResume() + val adapter: RvActionsAdapter = rvActions.adapter as? RvActionsAdapter ?: return + + adapter.setItemList(getNavigateOptions()) + adapter.notifyDataSetChanged() + } + + private fun navigate(destinationId: Int) { + navController.navigate(destinationId, null, getDefaultNavigationOptions()) + } + + private fun getNavigateOptions(): MutableList { + return mutableListOf( + ActionType.Scan, + ActionType.Sign, + ActionType.Personalize, + ActionType.Depersonalize +// ActionType.CreateWallet, +// ActionType.PurgeWallet, +// ActionType.ReadIssuerData, +// ActionType.WriteIssuerData, +// ActionType.ReadIssuerExData, +// ActionType.WriteIssuerExData, +// ActionType.ReadUserData, +// ActionType.WriteUserData + ) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/RvActions.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/RvActions.kt new file mode 100644 index 0000000000..df045a572d --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/RvActions.kt @@ -0,0 +1,69 @@ +package com.tangem.tangemtest._main.entryPoint + +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.recyclerview.widget.RecyclerView +import com.tangem.tangemtest.R +import com.tangem.tangemtest.ucase.resources.ActionRes +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.resources.MainResourceHolder +import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate +import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseAdapter +import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseVH +import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvCallback + +/** +[REDACTED_AUTHOR] + */ +class RvActionsAdapter( + private val wrapper: VhExDataWrapper, + private val callback: RvCallback +) : RvBaseAdapter() { + + override fun onBindViewHolder(holder: RvActionsVH, position: Int) { + holder.bindData(itemList[position]) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RvActionsVH { + val view = parent.inflate(R.layout.vh_actions, false) + return RvActionsVH(view, wrapper, callback) + } + +} + +class VhExDataWrapper( + val resHolder: MainResourceHolder, + var descriptionIsVisible: Boolean +) + +class RvActionsVH( + itemView: View, + private val wrapper: VhExDataWrapper, + private val callback: RvCallback? +) : RvBaseVH(itemView) { + + private val tvAction = itemView.findViewById(R.id.tv_title) + private val containerDescription = itemView.findViewById(R.id.container_description) + private val tvDescription = itemView.findViewById(R.id.tv_description) + + override fun bindData(data: ActionType) { + val res: ActionRes = wrapper.resHolder.safeGet(data) + tvAction.text = getString(res.resName) + tvDescription.text = getString(res.resDescription) + + itemView.setOnClickListener { + res.resNavigation?.let { callback?.invoke(itemViewType, adapterPosition, it) } + } + + containerDescription.visibility = if (wrapper.descriptionIsVisible) View.VISIBLE else View.GONE + } + + override fun onDataBound(data: ActionType) {} +} + +fun RecyclerView.ViewHolder.getString(@StringRes id: Int?, ifNull: String = ""): String { + val reqId = id ?: return ifNull + return itemView.context.getString(reqId) +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt new file mode 100644 index 0000000000..7a19cf981f --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt @@ -0,0 +1,8 @@ +package com.tangem.tangemtest.commons + +/** +[REDACTED_AUTHOR] + */ +fun performAction(a: A?, b: B?, action: (A, B) -> Unit) { + if (a != null && b != null) action(a, b) +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Logger.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Logger.kt new file mode 100644 index 0000000000..795c23c726 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Logger.kt @@ -0,0 +1,8 @@ +package com.tangem.tangemtest.commons + +import ru.dev.gbixahue.eu4d.lib.android.global.log.TagLogger + +/** +[REDACTED_AUTHOR] + */ +class TangemLogger : TagLogger("TangemDemo") \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt index 5412ab7c15..c8f68bec9c 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt @@ -3,11 +3,11 @@ package com.tangem.tangemtest.extensions import android.app.Application import android.content.Context import com.tangem.commands.* -import com.tangem.commands.personalization.CardConfig -import com.tangem.commands.personalization.NdefRecord +import com.tangem.commands.personalization.entities.CardConfig +import com.tangem.commands.personalization.entities.NdefRecord import java.util.* -fun CardConfig.Companion.init(application: Application): CardConfig { +fun CardConfig.Companion.create(application: Application): CardConfig { val preferences = application.getSharedPreferences("prefs", Context.MODE_PRIVATE) diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/List.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/List.kt new file mode 100644 index 0000000000..d637d6f89a --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/List.kt @@ -0,0 +1,16 @@ +package com.tangem.tangemtest.extensions + +/** +[REDACTED_AUTHOR] + */ +fun List.print(delimiter: String = ", ", wrap: Boolean = true): String { + val builder = StringBuilder() + forEach { builder.append(it).append(delimiter) } + val length = builder.length + if (length > delimiter.length) { + builder.delete(length - delimiter.length, length) + } + val result = builder.toString() + + return if (wrap) "[$result]" else result +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/NavigationOp.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/NavigationOp.kt new file mode 100644 index 0000000000..fa588f2b68 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/NavigationOp.kt @@ -0,0 +1,19 @@ +package com.tangem.tangemtest.ucase + +import androidx.navigation.NavOptions +import androidx.navigation.navOptions +import com.tangem.tangemtest.R + +/** +[REDACTED_AUTHOR] + */ +fun getDefaultNavigationOptions(): NavOptions { + return navOptions { + anim { + enter = R.anim.slide_in_right + exit = R.anim.slide_out_left + popEnter = R.anim.slide_in_left + popExit = R.anim.slide_out_right + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt new file mode 100644 index 0000000000..285c8de4b6 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt @@ -0,0 +1,46 @@ +package com.tangem.tangemtest.ucase.domain.actions + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.AfterActionModification +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence +import com.tangem.tasks.TaskEvent + +/** +[REDACTED_AUTHOR] + * + * The Card Action class family is designed for calling Card Manager functions + * and then processing the response. It also allows you to extract the main action as a lambda expression + */ +data class AttrForAction( + val cardManager: CardManager, + val itemList: List, + val payload: Payload, + val consequence: ItemsChangeConsequence? +) + +interface Action { + fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) + fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? = null +} + +abstract class BaseAction : Action { + protected open fun handleResult( + payload: PayloadHolder, + taskEvent: TaskEvent<*>, + modifier: AfterActionModification?, + attrs: AttrForAction, + callback: ActionCallback + ) { + val modifiedItems = mutableListOf() + modifier?.modify(payload, taskEvent, attrs.itemList)?.forEach { item -> + modifiedItems.add(item) + attrs.consequence?.affectChanges(payload, item, attrs.itemList)?.let { modifiedItems.addAll(it) } + } + callback(taskEvent, modifiedItems) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt new file mode 100644 index 0000000000..6a08e2e816 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt @@ -0,0 +1,26 @@ +package com.tangem.tangemtest.ucase.domain.actions + +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.domain.paramsManager.findDataItem +import com.tangem.tangemtest.ucase.variants.TlvId + +/** +[REDACTED_AUTHOR] + */ +class DepersonalizeAction : BaseAction() { + override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { + val item = attrs.itemList.findDataItem(TlvId.CardId) ?: return + val cardId = item.viewModel.data as? String ?: return + + attrs.cardManager.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) } + } + + override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { + return when (id) { + TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } + else -> null + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt new file mode 100644 index 0000000000..d82d8c7ddf --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt @@ -0,0 +1,25 @@ +package com.tangem.tangemtest.ucase.domain.actions + +import com.tangem.commands.personalization.entities.CardConfig +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey +import com.tangem.tangemtest.ucase.variants.personalize.dto.DefaultPersonalizeParams + +/** +[REDACTED_AUTHOR] + */ +class PersonalizeAction : BaseAction() { + override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { + val cardConfig = attrs.payload.remove(PayloadKey.cardConfig) as? CardConfig + ?: throw IllegalArgumentException("CardConfig must be in the payloads of the ParamsManager") + + val issuer = DefaultPersonalizeParams.issuer() + val acquirer = DefaultPersonalizeParams.acquirer() + val manufacturer = DefaultPersonalizeParams.manufacturer() + + attrs.cardManager.personalize(cardConfig, issuer, manufacturer, acquirer) { + handleResult(payload, it, null, attrs, callback) + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt new file mode 100644 index 0000000000..5264be9aed --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt @@ -0,0 +1,14 @@ +package com.tangem.tangemtest.ucase.domain.actions + +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.AfterScanModifier + +/** +[REDACTED_AUTHOR] + */ +class ScanAction : BaseAction() { + override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { + attrs.cardManager.scanCard { handleResult(payload, it, AfterScanModifier(), attrs, callback) } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt new file mode 100644 index 0000000000..f191a4aa4a --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt @@ -0,0 +1,28 @@ +package com.tangem.tangemtest.ucase.domain.actions + +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.domain.paramsManager.findDataItem +import com.tangem.tangemtest.ucase.variants.TlvId +import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf + +/** +[REDACTED_AUTHOR] + */ +class SignAction : BaseAction() { + override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { + val dataForHashing = attrs.itemList.findDataItem(TlvId.TransactionOutHash) ?: return + val hash = dataForHashing.getData() as? ByteArray ?: return + val cardId = attrs.itemList.findDataItem(TlvId.CardId)?.viewModel?.data ?: return + + attrs.cardManager.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) } + } + + override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { + return when (id) { + TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } + else -> null + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/Factory.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/Factory.kt new file mode 100644 index 0000000000..000e9a53c5 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/Factory.kt @@ -0,0 +1,25 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager + +import com.tangem.tangemtest.ucase.domain.paramsManager.managers.DepersonalizeItemsManager +import com.tangem.tangemtest.ucase.domain.paramsManager.managers.PersonalizeItemsManager +import com.tangem.tangemtest.ucase.domain.paramsManager.managers.ScanItemsManager +import com.tangem.tangemtest.ucase.domain.paramsManager.managers.SignItemsManager +import com.tangem.tangemtest.ucase.resources.ActionType +import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder + +/** +[REDACTED_AUTHOR] + */ +class ParamsManagerFactory : BaseTypedHolder() { + + companion object { + fun createFactory(): ParamsManagerFactory { + return ParamsManagerFactory().apply { + register(ActionType.Scan, ScanItemsManager()) + register(ActionType.Sign, SignItemsManager()) + register(ActionType.Personalize, PersonalizeItemsManager()) + register(ActionType.Depersonalize, DepersonalizeItemsManager()) + } + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt new file mode 100644 index 0000000000..6a37f00b34 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt @@ -0,0 +1,48 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence +import com.tangem.tasks.TaskEvent + +typealias ActionResponse = TaskEvent<*> +typealias AffectedList = List +typealias AffectedItemsCallback = (AffectedList) -> Unit +typealias ActionCallback = (ActionResponse, AffectedList) -> Unit + +/** +[REDACTED_AUTHOR] + */ +interface ItemsManager : PayloadHolder { + + fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback? = null) + fun getItems(): List + fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) + fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? + fun attachPayload(payload: Payload) + + val consequence: ItemsChangeConsequence? +} + +interface PayloadKey { + companion object { + val cardConfig: String = "cardConfig" + val incomingJson: String = "incomingJson" + val card = "card" + val actionView = "actionView" + } +} + +fun List.findItem(id: Id): Item? { + return firstOrNull { it.id == id } +} + +fun List.findDataItem(id: Id): BaseItem? { + val item = firstOrNull { it.id == id } + return if (item is BaseItem<*>) item as BaseItem + else null +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt new file mode 100644 index 0000000000..aaf280dedb --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt @@ -0,0 +1,51 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.ucase.domain.actions.Action +import com.tangem.tangemtest.ucase.domain.actions.AttrForAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.domain.paramsManager.AffectedItemsCallback +import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager +import com.tangem.tangemtest.ucase.domain.paramsManager.findDataItem +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence + +/** +[REDACTED_AUTHOR] + */ +abstract class BaseItemsManager(protected val action: Action) : ItemsManager { + + protected val itemList: List by lazy { createItemsList() } + + override val consequence: ItemsChangeConsequence? = null + + override val payload: MutableMap = mutableMapOf() + + override fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback?) { + if (itemList.isEmpty()) return + val foundItem = itemList.findDataItem(id) ?: return + + foundItem.setData(value) + applyChangesByAffectedItems(foundItem, callback) + } + + override fun getItems(): MutableList = itemList.toMutableList() + + override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? = null + + override fun attachPayload(payload: Payload) { + payload.forEach { this.payload[it.key] = it.value } + } + + // Use it if current item needs to be affect any other items + protected open fun applyChangesByAffectedItems(param: Item, callback: AffectedItemsCallback?) { + consequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) } + } + + protected fun getAttrsForAction(cardManager: CardManager) + : AttrForAction = AttrForAction(cardManager, itemList, payload, consequence) + + abstract fun createItemsList(): List +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/DepersonalizeItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/DepersonalizeItemsManager.kt new file mode 100644 index 0000000000..8267c86bb6 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/DepersonalizeItemsManager.kt @@ -0,0 +1,26 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.impl.EditTextItem +import com.tangem.tangemtest.ucase.domain.actions.DepersonalizeAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.variants.TlvId + +/** +[REDACTED_AUTHOR] + */ +class DepersonalizeItemsManager : BaseItemsManager(DepersonalizeAction()) { + override fun createItemsList(): List { + return listOf(EditTextItem(TlvId.CardId, null)) + } + + override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(cardManager), callback) + } + + override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? { + return action.getActionByTag(this, id, getAttrsForAction(cardManager)) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizeItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizeItemsManager.kt new file mode 100644 index 0000000000..82b6724052 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizeItemsManager.kt @@ -0,0 +1,21 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.impl.EditTextItem +import com.tangem.tangemtest.ucase.domain.actions.PersonalizeAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.variants.TlvId + +/** +[REDACTED_AUTHOR] + */ +class PersonalizeItemsManager : BaseItemsManager(PersonalizeAction()) { + override fun createItemsList(): List { + return listOf(EditTextItem(TlvId.CardId)) + } + + override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(cardManager), callback) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/ScanItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/ScanItemsManager.kt new file mode 100644 index 0000000000..1d9c973786 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/ScanItemsManager.kt @@ -0,0 +1,17 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.ucase.domain.actions.ScanAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback + +/** +[REDACTED_AUTHOR] + */ +class ScanItemsManager : BaseItemsManager(ScanAction()) { + override fun createItemsList(): List = listOf() + + override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(cardManager), callback) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SignItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SignItemsManager.kt new file mode 100644 index 0000000000..7d5d77a912 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SignItemsManager.kt @@ -0,0 +1,34 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.impl.EditTextItem +import com.tangem.tangemtest.ucase.domain.actions.SignAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.SignScanConsequence +import com.tangem.tangemtest.ucase.variants.TlvId + +/** +[REDACTED_AUTHOR] + */ +class SignItemsManager : BaseItemsManager(SignAction()) { + + override val consequence: ItemsChangeConsequence? by lazy { SignScanConsequence() } + + override fun createItemsList(): List { + return listOf( + EditTextItem(TlvId.CardId, null), + EditTextItem(TlvId.TransactionOutHash, "Data used for hashing") + ) + } + + override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(cardManager), callback) + } + + override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? { + return action.getActionByTag(this, id, getAttrsForAction(cardManager)) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt new file mode 100644 index 0000000000..0b6cecbf64 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt @@ -0,0 +1,16 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction + +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tasks.TaskEvent + +/** +[REDACTED_AUTHOR] + * + * The After Action Modification class family is intended for modifying items (if necessary) + * after calling CardManager.anyAction. + * Returns a list of items that have been modified + */ +interface AfterActionModification { + fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List): List +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt new file mode 100644 index 0000000000..cef30ec7f8 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt @@ -0,0 +1,42 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction + +import com.tangem.commands.Card +import com.tangem.commands.CardStatus +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey +import com.tangem.tangemtest.ucase.domain.paramsManager.findDataItem +import com.tangem.tangemtest.ucase.tunnel.ActionView +import com.tangem.tangemtest.ucase.tunnel.CardError +import com.tangem.tangemtest.ucase.variants.TlvId +import com.tangem.tasks.ScanEvent +import com.tangem.tasks.TaskEvent +import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI + +/** +[REDACTED_AUTHOR] + */ +class AfterScanModifier : AfterActionModification { + override fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List): List { + val foundItem = itemList.findDataItem(TlvId.CardId) ?: return listOf() + val card = smartCast(taskEvent)?.card ?: return listOf() + val actionView = payload.get(PayloadKey.actionView) as? ActionView ?: return listOf() + + return if (isNotPersonalized(card)) { + actionView.showSnackbar(CardError.NotPersonalized) + postUI { actionView.showActionFab(false) } + listOf() + } else { + payload.set(PayloadKey.card, card) + foundItem.setData(card.cardId) + postUI { actionView.showActionFab(true) } + listOf(foundItem) + } + } + + private fun smartCast(taskEvent: TaskEvent<*>): ScanEvent.OnReadEvent? { + return (taskEvent as? TaskEvent.Event)?.data as? ScanEvent.OnReadEvent + } + + private fun isNotPersonalized(card: Card): Boolean = card.status == CardStatus.NotPersonalized +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt new file mode 100644 index 0000000000..d7da0d3d1b --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt @@ -0,0 +1,47 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence + +import com.tangem.commands.Card +import com.tangem.commands.EllipticCurve +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.calculateSha512 +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey +import com.tangem.tangemtest.ucase.domain.paramsManager.findDataItem +import com.tangem.tangemtest.ucase.tunnel.ActionView +import com.tangem.tangemtest.ucase.variants.TlvId +import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI + +/** +[REDACTED_AUTHOR] + * + * The ParamsChangeConsequence class family modifies parameters depending on the state + * of the incoming parameter + */ +interface ItemsChangeConsequence { + fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List): List? +} + +class SignScanConsequence: ItemsChangeConsequence { + + override fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List): List? { + if (changedItem.id != TlvId.CardId) return null + + val hashItem = itemList.findDataItem(TlvId.TransactionOutHash) ?: return null + val affectedItems = mutableListOf(hashItem) + val card = payload.remove(PayloadKey.card) as Card? + if (card == null) { + hashItem.restoreDefaultData() + postUI { (payload.get(PayloadKey.actionView) as? ActionView)?.showActionFab(false) } + } else { + val dataForHashing = hashItem.getData() as? String ?: "Any data mother...s" + val hashedData = when(card.curve) { + EllipticCurve.Secp256k1 -> dataForHashing.calculateSha256() + EllipticCurve.Ed25519 -> dataForHashing.calculateSha512() + else -> return null + } + hashItem.setData(hashedData) + } + return affectedItems.toList() + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/GsonInitializer.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/GsonInitializer.kt new file mode 100644 index 0000000000..826eeae2eb --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/GsonInitializer.kt @@ -0,0 +1,55 @@ +package com.tangem.tangemtest.ucase.domain.responses + +import com.google.gson.* +import com.tangem.commands.ProductMask +import com.tangem.commands.Settings +import com.tangem.commands.SettingsMask +import com.tangem.commands.SigningMethod +import com.tangem.common.extensions.toHexString +import java.lang.reflect.Type + +/** +[REDACTED_AUTHOR] + */ +class GsonInitializer { + val gson: Gson by lazy { init() } + + private fun init(): Gson { + val builder = GsonBuilder().apply { + registerTypeAdapter(ByteArray::class.java, ByteTypeAdapter()) + registerTypeAdapter(SigningMethod::class.java, SigningMethodTypeAdapter()) + registerTypeAdapter(SettingsMask::class.java, SettingsMaskTypeAdapter()) + registerTypeAdapter(ProductMask::class.java, ProductMaskTypeAdapter()) + } + builder.setPrettyPrinting() + return builder.create() + } +} + +class ByteTypeAdapter : JsonSerializer { + override fun serialize(src: ByteArray, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(src.toHexString()) + } +} + +class SettingsMaskTypeAdapter : JsonSerializer { + override fun serialize(src: SettingsMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + val arrayElement = JsonArray() + Settings.values() + .filter { src.contains(it) } + .forEach { arrayElement.add(it.name) } + return arrayElement + } +} + +class ProductMaskTypeAdapter : JsonSerializer { + override fun serialize(src: ProductMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(src.rawValue.toString()) + } +} + +class SigningMethodTypeAdapter : JsonSerializer { + override fun serialize(src: SigningMethod, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(src.rawValue.toString()) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/Ids.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/Ids.kt new file mode 100644 index 0000000000..e686f0f8c3 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/Ids.kt @@ -0,0 +1,22 @@ +package com.tangem.tangemtest.ucase.resources + +import com.tangem.tangemtest._arch.structure.Id + +/** +[REDACTED_AUTHOR] + */ +enum class ActionType : Id { + Scan, + Sign, + CreateWallet, + PurgeWallet, + ReadIssuerData, + WriteIssuerData, + ReadIssuerExData, + WriteIssuerExData, + ReadUserData, + WriteUserData, + Personalize, + Depersonalize, + Unknown, +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/MainResourceHolder.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/MainResourceHolder.kt new file mode 100644 index 0000000000..90b908b742 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/MainResourceHolder.kt @@ -0,0 +1,26 @@ +package com.tangem.tangemtest.ucase.resources + +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest.ucase.resources.initializers.ActionResources +import com.tangem.tangemtest.ucase.resources.initializers.PersonalizeResources +import com.tangem.tangemtest.ucase.resources.initializers.TlvResources +import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder + +/** +[REDACTED_AUTHOR] + */ +open class ResourceHolder : BaseTypedHolder() + +object MainResourceHolder : ResourceHolder() { + init { + PersonalizeResources().init(this) + ActionResources().init(this) + TlvResources().init(this) + } + + inline fun safeGet(id: Id): Res { + val result = super.get(id) ?: Resources(R.string.unknown, R.string.unknown) + return result as Res + } +} diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/ResourseTypes.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/ResourseTypes.kt new file mode 100644 index 0000000000..72fad2464a --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/ResourseTypes.kt @@ -0,0 +1,15 @@ +package com.tangem.tangemtest.ucase.resources + +/** +[REDACTED_AUTHOR] + */ +open class Resources( + val resName: Int, + val resDescription: Int? = null +) + +class ActionRes( + resName: Int, + resDescription: Int? = null, + val resNavigation: Int? = null +) : Resources(resName, resDescription) \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/ActionResources.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/ActionResources.kt new file mode 100644 index 0000000000..053710adf7 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/ActionResources.kt @@ -0,0 +1,29 @@ +package com.tangem.tangemtest.ucase.resources.initializers + +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest.ucase.resources.ActionRes +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.resources.ResourceHolder + +/** +[REDACTED_AUTHOR] + */ +class ActionResources { + + fun init(holder: ResourceHolder) { + holder.register(ActionType.Scan, ActionRes(R.string.action_card_scan, R.string.info_action_card_scan, R.id.action_nav_entry_point_to_nav_scan)) + holder.register(ActionType.Sign, ActionRes(R.string.action_card_sign, R.string.info_action_card_sign, R.id.action_nav_entry_point_to_nav_sign)) + holder.register(ActionType.Personalize, ActionRes(R.string.action_personalize, R.string.info_action_personalize, R.id.action_nav_entry_point_to_nav_personalize)) + holder.register(ActionType.Depersonalize, ActionRes(R.string.action_depersonalize, R.string.info_action_depersonalize, R.id.action_nav_entry_point_to_nav_depersonalize)) + holder.register(ActionType.CreateWallet, ActionRes(R.string.action_wallet_create, R.string.info_action_wallet_create, R.id.action_nav_entry_point_to_nav_wallet_create)) + holder.register(ActionType.PurgeWallet, ActionRes(R.string.action_wallet_purge, R.string.info_action_wallet_purge, R.id.action_nav_entry_point_to_nav_wallet_purge)) + holder.register(ActionType.ReadIssuerData, ActionRes(R.string.action_issuer_read_data, R.string.info_action_issuer_read_data, R.id.action_nav_entry_point_to_nav_issuer_read_data)) + holder.register(ActionType.WriteIssuerData, ActionRes(R.string.action_issuer_write_data, R.string.info_action_issuer_write_data, R.id.action_nav_entry_point_to_nav_issuer_write_data)) + holder.register(ActionType.ReadIssuerExData, ActionRes(R.string.action_issuer_read_ex_data, R.string.info_action_issuer_read_ex_data, R.id.action_nav_entry_point_to_nav_issuer_read_ex_data)) + holder.register(ActionType.WriteIssuerExData, ActionRes(R.string.action_issuer_write_ex_data, R.string.info_action_issuer_write_ex_data, R.id.action_nav_entry_point_to_nav_issuer_write_ex_data)) + holder.register(ActionType.ReadUserData, ActionRes(R.string.action_user_read_data, R.string.info_action_user_read_data, R.id.action_nav_entry_point_to_nav_user_read_data)) + holder.register(ActionType.WriteUserData, ActionRes(R.string.action_user_write_data, R.string.info_action_user_write_data, R.id.action_nav_entry_point_to_nav_user_write_data)) +// holder.register(ActionType.Unknown, getIfNotContains()) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizeResources.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizeResources.kt new file mode 100644 index 0000000000..d1d91d121f --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizeResources.kt @@ -0,0 +1,130 @@ +package com.tangem.tangemtest.ucase.resources.initializers + +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest.ucase.resources.ResourceHolder +import com.tangem.tangemtest.ucase.resources.Resources +import com.tangem.tangemtest.ucase.variants.personalize.* +import ru.dev.gbixahue.eu4d.lib.kotlin.common.TypedHolder + +/** +[REDACTED_AUTHOR] + */ +class PersonalizeResources() { + fun init(holder: ResourceHolder) { + initBlock(holder) + initCardNumber(holder) + initCommon(holder) + initSigningMethod(holder) + initSignHashExProp(holder) + initDenomination(holder) + initToken(holder) + initProductMask(holder) + initSettingsMask(holder) + initSettingsMaskProtocolEnc(holder) + initSettingsMaskNde(holder) + initPins(holder) + } + + private fun initBlock(holder: TypedHolder) { + holder.register(BlockId.CardNumber, Resources(R.string.pers_block_card_number, R.string.info_pers_block_card_number)) + holder.register(BlockId.Common, Resources(R.string.pers_block_common, R.string.info_pers_block_common)) + holder.register(BlockId.SigningMethod, Resources(R.string.pers_block_signing_method, R.string.info_pers_block_signing_method)) + holder.register(BlockId.SignHashExProp, Resources(R.string.pers_block_sign_hash_ex_prop, R.string.info_pers_block_sign_hash_ex_prop)) + holder.register(BlockId.Denomination, Resources(R.string.pers_block_denomination, R.string.info_pers_block_denomination)) + holder.register(BlockId.Token, Resources(R.string.pers_block_token, R.string.info_pers_block_token)) + holder.register(BlockId.ProdMask, Resources(R.string.pers_block_product_mask, R.string.info_pers_block_product_mask)) + holder.register(BlockId.SettingsMask, Resources(R.string.pers_block_settings_mask, R.string.info_pers_block_settings_mask)) + holder.register(BlockId.SettingsMaskProtocolEnc, Resources(R.string.pers_block_settings_mask_protocol_enc, R.string.info_pers_block_settings_mask_protocol_enc)) + holder.register(BlockId.SettingsMaskNdef, Resources(R.string.pers_block_settings_mask_ndef, R.string.info_pers_block_settings_mask_ndef)) + holder.register(BlockId.Pins, Resources(R.string.pers_block_pins, R.string.info_pers_block_pins)) + } + + private fun initCardNumber(holder: TypedHolder) { + holder.register(CardNumber.Series, Resources(R.string.pers_item_series, R.string.info_pers_item_series)) + holder.register(CardNumber.Number, Resources(R.string.pers_item_number, R.string.info_pers_item_number)) + } + + private fun initCommon(holder: TypedHolder) { + holder.register(Common.Curve, Resources(R.string.pers_item_curve, R.string.info_pers_item_curve)) + holder.register(Common.Blockchain, Resources(R.string.pers_item_blockchain, R.string.info_pers_item_blockchain)) + holder.register(Common.BlockchainCustom, Resources(R.string.pers_item_custom_blockchain, R.string.info_pers_item_custom_blockchain)) + holder.register(Common.MaxSignatures, Resources(R.string.pers_item_max_signatures, R.string.info_pers_item_max_signatures)) + holder.register(Common.CreateWallet, Resources(R.string.pers_item_create_wallet, R.string.info_pers_item_create_wallet)) + } + + private fun initSigningMethod(holder: TypedHolder) { + holder.register(SigningMethod.SignTx, Resources(R.string.pers_item_sign_tx_hashes, R.string.info_pers_item_sign_tx_hashes)) + holder.register(SigningMethod.SignTxRaw, Resources(R.string.pers_item_sign_raw_tx, R.string.info_pers_item_sign_raw_tx)) + holder.register(SigningMethod.SignValidatedTx, Resources(R.string.pers_item_sign_validated_tx_hashes, R.string.info_pers_item_sign_validated_tx_hashes)) + holder.register(SigningMethod.SignValidatedTxRaw, Resources(R.string.pers_item_sign_validated_raw_tx, R.string.info_pers_item_sign_validated_raw_tx)) + holder.register(SigningMethod.SignValidatedTxIssuer, Resources(R.string.pers_item_sign_validated_tx_hashes_with_iss_data, R.string.info_pers_item_sign_validated_tx_hashes_with_iss_data)) + holder.register(SigningMethod.SignValidatedTxRawIssuer, Resources(R.string.pers_item_sign_validated_raw_tx_with_iss_data, R.string.info_pers_item_sign_validated_raw_tx_with_iss_data)) + holder.register(SigningMethod.SignExternal, Resources(R.string.pers_item_sign_hash_ex, R.string.info_pers_item_sign_hash_ex)) + } + + private fun initSignHashExProp(holder: TypedHolder) { + holder.register(SignHashExProp.PinLessFloorLimit, Resources(R.string.pers_item_pin_less_floor_limit, R.string.info_pers_item_pin_less_floor_limit)) + holder.register(SignHashExProp.CryptoExKey, Resources(R.string.pers_item_cr_ex_key, R.string.info_pers_item_cr_ex_key)) + holder.register(SignHashExProp.RequireTerminalCertSig, Resources(R.string.pers_item_require_terminal_cert_sig, R.string.info_pers_item_require_terminal_cert_sig)) + holder.register(SignHashExProp.RequireTerminalTxSig, Resources(R.string.pers_item_require_terminal_tx_sig, R.string.info_pers_item_require_terminal_tx_sig)) + holder.register(SignHashExProp.CheckPin3, Resources(R.string.pers_item_check_pin3_on_card, R.string.info_pers_item_check_pin3_on_card)) + } + + private fun initDenomination(holder: TypedHolder) { + holder.register(Denomination.WriteOnPersonalize, Resources(R.string.pers_item_write_on_personalize, R.string.info_pers_item_write_on_personalize)) + holder.register(Denomination.Denomination, Resources(R.string.pers_item_denomination, R.string.info_pers_item_denomination)) + } + + private fun initToken(holder: TypedHolder) { + holder.register(Token.ItsToken, Resources(R.string.pers_item_its_token, R.string.info_pers_item_its_token)) + holder.register(Token.Symbol, Resources(R.string.pers_item_symbol, R.string.info_pers_item_symbol)) + holder.register(Token.ContractAddress, Resources(R.string.pers_item_contract_address, R.string.info_pers_item_contract_address)) + holder.register(Token.Decimal, Resources(R.string.pers_item_decimal, R.string.info_pers_item_decimal)) + } + + private fun initProductMask(holder: TypedHolder) { + holder.register(ProductMask.Note, Resources(R.string.pers_item_note, R.string.info_pers_item_note)) + holder.register(ProductMask.Tag, Resources(R.string.pers_item_tag, R.string.info_pers_item_tag)) + holder.register(ProductMask.CardId, Resources(R.string.pers_item_id_card, R.string.info_pers_item_id_card)) + } + + private fun initSettingsMask(holder: TypedHolder) { + holder.register(SettingsMask.IsReusable, Resources(R.string.pers_item_is_reusable, R.string.info_pers_item_is_reusable)) + holder.register(SettingsMask.NeedActivation, Resources(R.string.pers_item_need_activation, R.string.info_pers_item_need_activation)) + holder.register(SettingsMask.ForbidPurge, Resources(R.string.pers_item_forbid_purge, R.string.info_pers_item_forbid_purge)) + holder.register(SettingsMask.AllowSelectBlockchain, Resources(R.string.pers_item_allow_select_blockchain, R.string.info_pers_item_allow_select_blockchain)) + holder.register(SettingsMask.UseBlock, Resources(R.string.pers_item_use_block, R.string.info_pers_item_use_block)) + holder.register(SettingsMask.OneApdu, Resources(R.string.pers_item_one_apdu_at_once, R.string.info_pers_item_one_apdu_at_once)) + holder.register(SettingsMask.UseCvc, Resources(R.string.pers_item_use_cvc, R.string.info_pers_item_use_cvc)) + holder.register(SettingsMask.AllowSwapPin, Resources(R.string.pers_item_allow_swap_pin, R.string.info_pers_item_allow_swap_pin)) + holder.register(SettingsMask.AllowSwapPin2, Resources(R.string.pers_item_allow_swap_pin2, R.string.info_pers_item_allow_swap_pin2)) + holder.register(SettingsMask.ForbidDefaultPin, Resources(R.string.pers_item_forbid_default_pin, R.string.info_pers_item_forbid_default_pin)) + holder.register(SettingsMask.SmartSecurityDelay, Resources(R.string.pers_item_smart_security_delay, R.string.info_pers_item_smart_security_delay)) + holder.register(SettingsMask.ProtectIssuerDataAgainstReplay, Resources(R.string.pers_item_protect_issuer_data_against_replay, R.string.info_pers_item_protect_issuer_data_against_replay)) + holder.register(SettingsMask.SkipSecurityDelayIfValidated, Resources(R.string.pers_item_skip_security_delay_if_validated, R.string.info_pers_item_skip_security_delay_if_validated)) + holder.register(SettingsMask.SkipPin2CvcIfValidated, Resources(R.string.pers_item_skip_pin2_and_cvc_if_validated, R.string.info_pers_item_skip_pin2_and_cvc_if_validated)) + holder.register(SettingsMask.SkipSecurityDelayOnLinkedTerminal, Resources(R.string.pers_item_skip_security_delay_on_linked_terminal, R.string.info_pers_item_skip_security_delay_on_linked_terminal)) + holder.register(SettingsMask.RestrictOverwriteExtraIssuerData, Resources(R.string.pers_item_restrict_overwrite_ex_issuer_data, R.string.info_pers_item_restrict_overwrite_ex_issuer_data)) + } + + private fun initSettingsMaskProtocolEnc(holder: TypedHolder) { + holder.register(SettingsMaskProtocolEnc.AllowUnencrypted, Resources(R.string.pers_item_allow_unencrypted, R.string.info_pers_item_allow_unencrypted)) + holder.register(SettingsMaskProtocolEnc.AlloFastEncryption, Resources(R.string.pers_item_allow_fast_encryption, R.string.info_pers_item_allow_fast_encryption)) + } + + private fun initSettingsMaskNde(holder: TypedHolder) { + holder.register(SettingsMaskNdef.UseNdef, Resources(R.string.pers_item_use_ndef, R.string.info_pers_item_use_ndef)) + holder.register(SettingsMaskNdef.DynamicNdef, Resources(R.string.pers_item_dynamic_ndef, R.string.info_pers_item_dynamic_ndef)) + holder.register(SettingsMaskNdef.DisablePrecomputedNdef, Resources(R.string.pers_item_disable_precomputed_ndef, R.string.info_pers_item_disable_precomputed_ndef)) + holder.register(SettingsMaskNdef.Aar, Resources(R.string.pers_item_aar, R.string.info_pers_item_aar)) + } + + private fun initPins(holder: TypedHolder) { + holder.register(Pins.Pin, Resources(R.string.pers_item_pin, R.string.info_pers_item_pin)) + holder.register(Pins.Pin2, Resources(R.string.pers_item_pin2, R.string.info_pers_item_pin2)) + holder.register(Pins.Pin3, Resources(R.string.pers_item_pin3, R.string.info_pers_item_pin3)) + holder.register(Pins.Cvc, Resources(R.string.pers_item_cvc, R.string.info_pers_item_cvc)) + holder.register(Pins.PauseBeforePin2, Resources(R.string.pers_item_pause_before_pin2, R.string.info_pers_item_pause_before_pin2)) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/TlvResources.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/TlvResources.kt new file mode 100644 index 0000000000..4de4ab008f --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/TlvResources.kt @@ -0,0 +1,20 @@ +package com.tangem.tangemtest.ucase.resources.initializers + +import com.tangem.tangemtest.R +import com.tangem.tangemtest.ucase.resources.MainResourceHolder +import com.tangem.tangemtest.ucase.resources.Resources +import com.tangem.tangemtest.ucase.variants.TlvId + +/** +[REDACTED_AUTHOR] + */ +class TlvResources { + fun init(holder: MainResourceHolder) { + initScan(holder) + } + + private fun initScan(holder: MainResourceHolder) { + holder.register(TlvId.CardId, Resources(R.string.tlv_card_id, R.string.info_tlv_card_id)) + holder.register(TlvId.TransactionOutHash, Resources(R.string.tlv_transaction_out_hash, R.string.info_tlv_transaction_out_hash)) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/Errors.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/Errors.kt new file mode 100644 index 0000000000..e10f81c669 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/Errors.kt @@ -0,0 +1,10 @@ +package com.tangem.tangemtest.ucase.tunnel + +import com.tangem.tangemtest._arch.structure.Id + +/** +[REDACTED_AUTHOR] + */ +enum class CardError : Id { + NotPersonalized +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/ViewScreen.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/ViewScreen.kt new file mode 100644 index 0000000000..d7c1df09f2 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/ViewScreen.kt @@ -0,0 +1,14 @@ +package com.tangem.tangemtest.ucase.tunnel + +import com.tangem.tangemtest._arch.structure.Id + +interface ViewScreen + +interface SnackbarHolder { + fun showSnackbar(message: String) + fun showSnackbar(id: Id) +} + +interface ActionView : ViewScreen, SnackbarHolder { + fun showActionFab(show: Boolean) +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt new file mode 100644 index 0000000000..c4202bfa01 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt @@ -0,0 +1,146 @@ +package com.tangem.tangemtest.ucase.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.Snackbar +import com.tangem.CardManager +import com.tangem.tangem_sdk_new.extensions.init +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._main.MainViewModel +import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager +import com.tangem.tangemtest.ucase.domain.paramsManager.ParamsManagerFactory +import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.tunnel.ActionView +import com.tangem.tangemtest.ucase.tunnel.CardError +import com.tangem.tangemtest.ucase.ui.widgets.ParameterWidget +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log +import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder + +/** +[REDACTED_AUTHOR] + */ +abstract class BaseCardActionFragment : Fragment(), LayoutHolder, ActionView { + + protected val incomingParamsContainer: ViewGroup by lazy { + mainView.findViewById(R.id.ll_incoming_params_container) + } + protected lateinit var mainView: View + protected val actionFab: FloatingActionButton by lazy { mainView.findViewById(R.id.fab_action) } + + protected val paramsVM: ParamsViewModel by viewModels { ActionViewModelFactory(itemsManager) } + protected val widgetList = mutableListOf() + + private val itemsManager: ItemsManager by lazy { ParamsManagerFactory.createFactory().get(getAction())!! } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + Log.d(this, "onCreateView") + mainView = inflater.inflate(getLayoutId(), container, false) + return mainView + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + Log.d(this, "onViewCreated") + + viewLifecycleOwner.lifecycle.addObserver(paramsVM) + paramsVM.setCardManager(CardManager.init(requireActivity())) + paramsVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView)) + initFab() + showActionFab(false) + createWidgets { subscribeToViewModelChanges() } + } + + private fun initFab() { + Log.d(this, "initFab") + actionFab.setOnClickListener { paramsVM.invokeMainAction() } + } + + private fun createWidgets(widgetCreatedCallback: () -> Unit) { + Log.d(this, "createWidgets") + paramsVM.ldParams.observe(viewLifecycleOwner, Observer { itemList -> + itemList.forEach { param -> + val widget = ParameterWidget(inflateParamView(incomingParamsContainer), param) + widget.onValueChanged = { id, value -> paramsVM.userChangedItem(id, value) } + widget.onActionBtnClickListener = paramsVM.getItemAction(param.id) + widgetList.add(widget) + } + widgetCreatedCallback() + }) + } + + private fun subscribeToViewModelChanges() { + Log.d(this, "subscribeToViewModelChanges") + listenResponse() + listenError() + listenChangedItems() + listenDescriptionSwitchChanges() + } + + protected open fun listenResponse() { + val tvResponse by lazy { mainView.findViewById(R.id.tv_action_response_json) } + paramsVM.ldResponse.observe(viewLifecycleOwner, Observer { + Log.d(this, "action response: ${if (it.length > 50) it.substring(0..50) else it}") + tvResponse.text = it + }) + } + + protected open fun listenError() { + paramsVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) }) + } + + protected open fun listenChangedItems() { + paramsVM.seChangedItems.observe(viewLifecycleOwner, Observer { itemList -> + itemList.forEach { item -> + Log.d(this, "item changed from VM - name: ${item.id}") + val dataItem = item as? BaseItem ?: return@Observer + Log.d(this, "item changed from VM - name: ${dataItem.id}, value:${dataItem.viewModel.data}") + widgetList.firstOrNull { it.id == item.id }?.changeParamValue(item.viewModel.data) + } + }) + } + + protected open fun listenDescriptionSwitchChanges() { + val mainActViewModel by activityViewModels() + mainActViewModel.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer { isEnabled -> + widgetList.forEach { it.toggleDescriptionVisibility(isEnabled) } + }) + } + + private fun inflateParamView(where: ViewGroup): ViewGroup { + val inflater = LayoutInflater.from(where.context) + val view = inflater.inflate(R.layout.w_card_incoming_param, where, false) + where.addView(view) + return view as ViewGroup + } + + override fun showActionFab(show: Boolean) { + if (show) actionFab.show() else actionFab.hide() + } + + override fun showSnackbar(id: Id) { +// MainResourceHolder.safeGet<>() + when (id) { + CardError.NotPersonalized -> showSnackbar(requireContext().getString(R.string.card_error_not_personalized)) + else -> showSnackbar(requireContext().getString(R.string.unknown)) + } + } + + override fun showSnackbar(message: String) { + Snackbar.make(mainView, message, BaseTransientBottomBar.LENGTH_SHORT).show() + } + + abstract fun getAction(): ActionType +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ParamsViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ParamsViewModel.kt new file mode 100644 index 0000000000..2df743786e --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ParamsViewModel.kt @@ -0,0 +1,144 @@ +package com.tangem.tangemtest.ucase.ui + +import androidx.annotation.UiThread +import androidx.lifecycle.* +import com.google.gson.Gson +import com.tangem.CardManager +import com.tangem.commands.Card +import com.tangem.tangemtest._arch.SingleLiveEvent +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.commons.performAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager +import com.tangem.tangemtest.ucase.domain.responses.GsonInitializer +import com.tangem.tangemtest.ucase.tunnel.ViewScreen +import com.tangem.tasks.ScanEvent +import com.tangem.tasks.TaskError +import com.tangem.tasks.TaskEvent +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +class ActionViewModelFactory(private val manager: ItemsManager) : ViewModelProvider.NewInstanceFactory() { + override fun create(modelClass: Class): T = ParamsViewModel(manager) as T +} + +class ParamsViewModel(private val itemsManager: ItemsManager) : ViewModel(), LifecycleObserver { + + val ldCard = MutableLiveData() + val ldIsVerified = MutableLiveData() + val ldResponse = MutableLiveData() + val ldReadResponse = MutableLiveData() + val ldParams = MutableLiveData(itemsManager.getItems()) + + val seError: MutableLiveData = SingleLiveEvent() + val seChangedItems: MutableLiveData> = SingleLiveEvent() + + private val notifier: Notifier = Notifier(this) + private lateinit var cardManager: CardManager + + fun setCardManager(cardManager: CardManager) { + this.cardManager = cardManager + } + + fun userChangedItem(id: Id, value: Any?) { + itemChanged(id, value) + } + + private fun itemChanged(id: Id, value: Any?) { + itemsManager.itemChanged(id, value) { notifier.notifyItemsChanged(it) } + } + + //invokes Scan, Sign etc... + fun invokeMainAction() { + performAction(itemsManager, cardManager) { paramsManager, cardManager -> + paramsManager.invokeMainAction(cardManager) { response, listOfChangedParams -> + notifier.handleActionResult(response, listOfChangedParams) + } + } + } + + fun getItemAction(id: Id): (() -> Unit)? { + val itemFunction = itemsManager.getActionByTag(id, cardManager) ?: return null + + return { + itemFunction { response, listOfChangedParams -> + notifier.handleActionResult(response, listOfChangedParams) + } + } + } + + fun attachToPayload(payload: Payload) { + itemsManager.attachPayload(payload) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun viewOnDestroy() { + val keyList = mutableListOf() + itemsManager.payload.filterValues { it is ViewScreen }.forEach { keyList.add(it.key) } + keyList.forEach { itemsManager.payload.remove(it) } + } +} + +internal class Notifier(private val vm: ParamsViewModel) { + + private var notShowedError: TaskError? = null + private val gson: Gson = GsonInitializer().gson + + fun handleActionResult(response: TaskEvent<*>, list: List) { + if (list.isNotEmpty()) notifyItemsChanged(list) + handleResponse(response) + } + + @UiThread + fun notifyItemsChanged(list: List) { + vm.seChangedItems.postValue(list) + } + + fun handleResponse(response: TaskEvent<*>) { + val taskEvent = response as? TaskEvent<*> ?: return + + when (taskEvent) { + is TaskEvent.Completion -> handleCompletionEvent(taskEvent) + is TaskEvent.Event -> handleDataEvent(taskEvent.data) + } + } + + private fun handleDataEvent(event: Any?) { + when (event) { + is ScanEvent.OnReadEvent -> { + vm.ldCard.postValue(event.card) + vm.ldReadResponse.postValue(gson.toJson(event)) + } + is ScanEvent.OnVerifyEvent -> { + vm.ldIsVerified.postValue(true) + } + else -> vm.ldResponse.postValue(gson.toJson(event)) + } + } + + private fun handleCompletionEvent(taskEvent: TaskEvent.Completion<*>) { + if (taskEvent.error == null) { + Log.d(this, "error = null") + if (notShowedError != null) { + vm.seError.postValue("${notShowedError!!::class.simpleName}") + notShowedError = null + } + } else { + Log.d(this, "error = ${taskEvent.error}") + when (taskEvent.error) { + is TaskError.UserCancelled -> { + if (notShowedError == null) { + vm.seError.postValue("User canceled the action") + } else { + vm.seError.postValue("${notShowedError!!::class.simpleName}") + notShowedError = null + } + } + else -> notShowedError = taskEvent.error + } + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/widgets/ParameterWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/widgets/ParameterWidget.kt new file mode 100644 index 0000000000..15ba1cdcba --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/widgets/ParameterWidget.kt @@ -0,0 +1,119 @@ +package com.tangem.tangemtest.ucase.ui.widgets + +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import androidx.transition.AutoTransition +import androidx.transition.TransitionManager +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.resources.MainResourceHolder +import com.tangem.tangemtest.ucase.resources.Resources +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log +import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf + +/** +[REDACTED_AUTHOR] + */ +class ParameterWidget( + private val parent: ViewGroup, + item: Item +) { + + val id: Id = item.id + val dataItem: BaseItem = item as BaseItem + + var onValueChanged: ((Id, Any?) -> Unit)? = null + var onActionBtnClickListener: (() -> Unit)? = null + set(value) { + field = value + toggleActionBtnVisibility() + } + + private val tilValue: TextInputLayout = parent.findViewById(R.id.til_param) + private val etValue: TextInputEditText = parent.findViewById(R.id.et_param) + private val btnAction: Button = parent.findViewById(R.id.btn_action) + private val valueWatcher: TextWatcher by lazy { getWatcher() } + + private val descriptionContainer: ViewGroup by lazy { parent.findViewById(R.id.container_description) } + private val tvDescription: TextView? by lazy { descriptionContainer.findViewById(R.id.tv_description) } + + private var actionBtnVisibilityState: Int = btnAction.visibility + private var value: Any? = dataItem.viewModel.data + + init { + tilValue.hint = tilValue.context.getString(getResNameId()) + etValue.setText(stringOf(dataItem.viewModel.data)) + etValue.addTextChangedListener(valueWatcher) + btnAction.setOnClickListener { onActionBtnClickListener?.invoke() } + btnAction.setText(getResNameId(ActionType.Scan)) + toggleActionBtnVisibility() + initDescriptionWidget() + } + + fun changeParamValue(data: Any?, silent: Boolean = true) { + Log.d(this, "changeParamValue: tag: $id, value: $data") + value = data + toggleActionBtnVisibility() + if (silent) { + etValue.removeTextChangedListener(valueWatcher) + etValue.setText(stringOf(value)) + etValue.addTextChangedListener(valueWatcher) + } else { + etValue.setText(stringOf(value)) + } + } + + fun toggleDescriptionVisibility(state: Boolean) { + TransitionManager.beginDelayedTransition(parent.parent as ViewGroup, AutoTransition()) + descriptionContainer.visibility = if (state) View.VISIBLE else View.GONE + } + + private fun getWatcher(): TextWatcher { + return object : TextWatcher { + override fun afterTextChanged(editable: Editable?) { + Log.d(this, "afterTextChanged $editable") + value = if (editable.isNullOrEmpty()) null else editable.toString() + toggleActionBtnVisibility() + onValueChanged?.invoke(id, value) + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + } + } + + private fun toggleActionBtnVisibility() { + fun switchVisibilityState(newState: Int) { + actionBtnVisibilityState = newState + TransitionManager.beginDelayedTransition(parent, AutoTransition()) + btnAction.visibility = actionBtnVisibilityState + } + when { + onActionBtnClickListener == null -> { + if (actionBtnVisibilityState == View.GONE) return + switchVisibilityState(View.GONE) + } + value == null && actionBtnVisibilityState != View.VISIBLE -> switchVisibilityState(View.VISIBLE) + value != null && actionBtnVisibilityState != View.GONE -> switchVisibilityState(View.GONE) + } + } + + private fun initDescriptionWidget() { + getResDescription()?.let { tvDescription?.setText(it) } + } +} + +fun ParameterWidget.getResNameId(id: Id? = null): Int = MainResourceHolder.safeGet(id + ?: this.id).resName + +fun ParameterWidget.getResDescription(): Int? = MainResourceHolder.safeGet(id).resDescription \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/Ids.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/Ids.kt new file mode 100644 index 0000000000..13f4ddcf27 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/Ids.kt @@ -0,0 +1,11 @@ +package com.tangem.tangemtest.ucase.variants + +import com.tangem.tangemtest._arch.structure.Id + +/** +[REDACTED_AUTHOR] + */ +enum class TlvId : Id { + CardId, + TransactionOutHash, +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt new file mode 100644 index 0000000000..6567e465b1 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt @@ -0,0 +1,16 @@ +package com.tangem.tangemtest.ucase.variants.depersonalize.ui + +import com.tangem.tangemtest.R +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment + +/** +[REDACTED_AUTHOR] + */ +class DepersonalizeActionFragment : BaseCardActionFragment() { + + override fun getLayoutId(): Int = R.layout.fg_depersonalize + + override fun getAction(): ActionType = ActionType.Depersonalize + +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/Ids.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/Ids.kt new file mode 100644 index 0000000000..44860aff33 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/Ids.kt @@ -0,0 +1,114 @@ +package com.tangem.tangemtest.ucase.variants.personalize + +import com.tangem.tangemtest._arch.structure.Id + +/** +[REDACTED_AUTHOR] + */ + +interface PersonalizeId : Id + +interface BlockItem : PersonalizeId + +enum class BlockId : PersonalizeId { + CardNumber, + Common, + SigningMethod, + SignHashExProp, + Denomination, + Token, + ProdMask, + SettingsMask, + SettingsMaskProtocolEnc, + SettingsMaskNdef, + Pins, +} + +enum class CardNumber : BlockItem { + Series, + Number, +} + +enum class Common : BlockItem { + Curve, + Blockchain, + BlockchainCustom, + MaxSignatures, + CreateWallet, +} + +enum class SigningMethod : BlockItem { + SignTx, + SignTxRaw, + SignValidatedTx, + SignValidatedTxRaw, + SignValidatedTxIssuer, + SignValidatedTxRawIssuer, + SignExternal, +} + +enum class SignHashExProp : BlockItem { + PinLessFloorLimit, + CryptoExKey, + RequireTerminalCertSig, + RequireTerminalTxSig, + CheckPin3, +} + +enum class Denomination : BlockItem { + WriteOnPersonalize, + Denomination, +} + +enum class Token : BlockItem { + ItsToken, + Symbol, + ContractAddress, + Decimal +} + +enum class ProductMask : BlockItem { + Note, + Tag, + CardId +} + +enum class SettingsMask : BlockItem { + IsReusable, + NeedActivation, + ForbidPurge, + AllowSelectBlockchain, + UseBlock, + OneApdu, + UseCvc, + AllowSwapPin, + AllowSwapPin2, + ForbidDefaultPin, + SmartSecurityDelay, + ProtectIssuerDataAgainstReplay, + SkipSecurityDelayIfValidated, + SkipPin2CvcIfValidated, + SkipSecurityDelayOnLinkedTerminal, + RestrictOverwriteExtraIssuerData, +} + +enum class SettingsMaskProtocolEnc : BlockItem { + AllowUnencrypted, + AlloFastEncryption +} + +enum class SettingsMaskNdef : BlockItem { + UseNdef, + DynamicNdef, + DisablePrecomputedNdef, + Aar, + AarCustom +} + +enum class Pins : BlockItem { + Pin, + Pin2, + Pin3, + Cvc, + PauseBeforePin2 +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/IdToValueAssociations.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/IdToValueAssociations.kt new file mode 100644 index 0000000000..537f8a2b6f --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/IdToValueAssociations.kt @@ -0,0 +1,131 @@ +package com.tangem.tangemtest.ucase.variants.personalize.converter + +import com.tangem.commands.EllipticCurve +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.impl.KeyValue +import com.tangem.tangemtest.ucase.variants.personalize.* +import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig +import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder + +/** +[REDACTED_AUTHOR] + */ +class Value( + private var value: Any? = null, + val list: List? = mutableListOf() +) { + fun get(): Any? = value + fun set(newValue: Any?) { + value = newValue + } +} + +class IdToValueAssociations : BaseTypedHolder() { + + // All registered association values can't be objects + fun init(from: PersonalizeConfig) { + register(CardNumber.Series, Value(from.series)) + register(CardNumber.Number, Value(from.startNumber)) + register(Common.Curve, Value(from.curveID, Helper.listOfCurves())) + register(Common.Blockchain, Value(from.blockchain, Helper.listOfBlockchain())) + register(Common.BlockchainCustom, Value("")) + register(Common.MaxSignatures, Value(from.MaxSignatures)) + register(Common.CreateWallet, Value(from.createWallet)) + register(SigningMethod.SignTx, Value(from.SigningMethod0)) + register(SigningMethod.SignTxRaw, Value(from.SigningMethod1)) + register(SigningMethod.SignValidatedTx, Value(from.SigningMethod2)) + register(SigningMethod.SignValidatedTxRaw, Value(from.SigningMethod3)) + register(SigningMethod.SignValidatedTxIssuer, Value(from.SigningMethod4)) + register(SigningMethod.SignValidatedTxRawIssuer, Value(from.SigningMethod5)) + register(SigningMethod.SignExternal, Value(from.SigningMethod6)) + register(SignHashExProp.PinLessFloorLimit, Value(from.pinLessFloorLimit)) + register(SignHashExProp.CryptoExKey, Value(from.hexCrExKey)) + register(SignHashExProp.RequireTerminalCertSig, Value(from.requireTerminalCertSignature)) + register(SignHashExProp.RequireTerminalTxSig, Value(from.requireTerminalTxSignature)) + register(SignHashExProp.CheckPin3, Value(from.checkPIN3onCard)) + register(Denomination.WriteOnPersonalize, Value(from.writeOnPersonalization)) + register(Denomination.Denomination, Value(from.denomination)) + register(Token.ItsToken, Value(from.itsToken)) + register(Token.Symbol, Value(from.symbol)) + register(Token.ContractAddress, Value(from.contractAddress)) + register(Token.Decimal, Value(from.decimal)) + register(ProductMask.Note, Value(from.cardData.product_note)) + register(ProductMask.Tag, Value(from.cardData.product_tag)) + register(ProductMask.CardId, Value(from.cardData.product_id_card)) + register(SettingsMask.IsReusable, Value(from.isReusable)) + register(SettingsMask.NeedActivation, Value(from.useActivation)) + register(SettingsMask.ForbidPurge, Value(from.forbidPurgeWallet)) + register(SettingsMask.AllowSelectBlockchain, Value(from.allowSelectBlockchain)) + register(SettingsMask.UseBlock, Value(from.useBlock)) + register(SettingsMask.OneApdu, Value(from.oneApdu)) + register(SettingsMask.UseCvc, Value(from.useCVC)) + register(SettingsMask.AllowSwapPin, Value(from.allowSwapPIN)) + register(SettingsMask.AllowSwapPin2, Value(from.allowSwapPIN2)) + register(SettingsMask.ForbidDefaultPin, Value(from.forbidDefaultPIN)) + register(SettingsMask.SmartSecurityDelay, Value(from.smartSecurityDelay)) + register(SettingsMask.ProtectIssuerDataAgainstReplay, Value(from.protectIssuerDataAgainstReplay)) + register(SettingsMask.SkipSecurityDelayIfValidated, Value(from.skipSecurityDelayIfValidatedByIssuer)) + register(SettingsMask.SkipPin2CvcIfValidated, Value(from.skipCheckPIN2andCVCIfValidatedByIssuer)) + register(SettingsMask.SkipSecurityDelayOnLinkedTerminal, Value(from.skipSecurityDelayIfValidatedByLinkedTerminal)) + register(SettingsMask.RestrictOverwriteExtraIssuerData, Value(from.restrictOverwriteIssuerDataEx)) + register(SettingsMaskProtocolEnc.AllowUnencrypted, Value(from.protocolAllowUnencrypted)) + register(SettingsMaskProtocolEnc.AlloFastEncryption, Value(false)) + register(SettingsMaskNdef.UseNdef, Value(from.useNDEF)) + register(SettingsMaskNdef.DynamicNdef, Value(from.useDynamicNDEF)) + register(SettingsMaskNdef.DisablePrecomputedNdef, Value(from.disablePrecomputedNDEF)) + register(SettingsMaskNdef.Aar, Value(from.aar, Helper.aarList())) + register(SettingsMaskNdef.AarCustom, Value(from.aar)) + register(Pins.Pin, Value(from.PIN)) + register(Pins.Pin2, Value(from.PIN2)) + register(Pins.Pin3, Value(from.PIN3)) + register(Pins.Cvc, Value(from.CVC)) + register(Pins.PauseBeforePin2, Value(from.pauseBeforePIN2, Helper.pauseBeforePin())) + } +} + +internal class Helper { + companion object { + fun listOfCurves(): List { + return EllipticCurve.values().map { KeyValue(it.name, it.curve) } + } + + fun listOfBlockchain(): List { + return mutableListOf( + KeyValue("--- CUSTOM ---", "BTC/test"), + KeyValue("BTC", "BTC"), + KeyValue("BTC/test", "BTC/test"), + KeyValue("ETH", "ETH"), + KeyValue("ETH/test", "ETH/test"), + KeyValue("BCH", "BCH"), + KeyValue("BCH/test", "BCH/test"), + KeyValue("LTC", "LTC"), + KeyValue("XLM", "XLM"), + KeyValue("XLM/test", "XLM/test"), + KeyValue("RSK", "RSK"), + KeyValue("XPR", "XPR"), + KeyValue("CARDANO", "CARDANO") + ) + } + + fun aarList(): List { + return mutableListOf( + KeyValue("--- CUSTOM ---", ""), + KeyValue("Release APP", "com.tangem.wallet"), + KeyValue("Debug APP", "com.tangem.wallet.debug"), + KeyValue("None", "") + ) + } + + fun pauseBeforePin(): List { + return mutableListOf( + KeyValue("immediately", 0L), + KeyValue("2 seconds", 2000L), + KeyValue("5 seconds", 5000L), + KeyValue("15 seconds", 15000L), + KeyValue("30 seconds", 30000L), + KeyValue("1 minute", 60000L), + KeyValue("2 minute", 120000L) + ) + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizeConfigConverter.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizeConfigConverter.kt new file mode 100644 index 0000000000..e4bee5910d --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizeConfigConverter.kt @@ -0,0 +1,335 @@ +package com.tangem.tangemtest.ucase.variants.personalize.converter + +import com.tangem.commands.CardData +import com.tangem.commands.EllipticCurve +import com.tangem.commands.ProductMaskBuilder +import com.tangem.commands.personalization.entities.CardConfig +import com.tangem.commands.personalization.entities.NdefRecord +import com.tangem.tangemtest._arch.structure.Additional +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.Block +import com.tangem.tangemtest._arch.structure.abstraction.ListItemBlock +import com.tangem.tangemtest._arch.structure.impl.* +import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey +import com.tangem.tangemtest.ucase.variants.personalize.* +import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig +import java.util.* + +/** +[REDACTED_AUTHOR] + */ +class PersonalizeConfigConverter { + + private val associations: IdToValueAssociations = IdToValueAssociations() + private val valueMapper = ValueMapper() + + fun toBlock(from: PersonalizeConfig): List { + associations.init(from) + val blocList = mutableListOf() + blocList.add(cardNumber()) + blocList.add(common()) + blocList.add(signingMethod()) + blocList.add(signHashExProperties()) + blocList.add(denomination()) + blocList.add(token()) + blocList.add(productMask()) + blocList.add(settingsMask()) + blocList.add(settingsMaskProtocolEnc()) + blocList.add(settingsMaskNdef()) + blocList.add(pins()) + val payloadBlock = ListItemBlock(Additional.JSON_TAILS) + addPayload(payloadBlock, from) + blocList.add(payloadBlock) + return blocList + } + + private fun cardNumber(): Block { + val block = createBlock(BlockId.CardNumber) + mutableListOf( + CardNumber.Series, + CardNumber.Number + ).forEach { createItem(block, it as Id) } + return block + } + + private fun common(): Block { + val block = createBlock(BlockId.Common) + mutableListOf( + Common.Curve, + Common.Blockchain, + Common.BlockchainCustom, + Common.MaxSignatures, + Common.CreateWallet + ).forEach { createItem(block, it) } + return block + } + + private fun signingMethod(): Block { + val block = createBlock(BlockId.SigningMethod) + mutableListOf( + SigningMethod.SignTx, + SigningMethod.SignTxRaw, + SigningMethod.SignValidatedTx, + SigningMethod.SignValidatedTxRaw, + SigningMethod.SignValidatedTxIssuer, + SigningMethod.SignValidatedTxRawIssuer, + SigningMethod.SignExternal + ).forEach { createItem(block, it) } + return block + } + + private fun signHashExProperties(): Block { + val block = createBlock(BlockId.SignHashExProp) + mutableListOf( + SignHashExProp.PinLessFloorLimit, + SignHashExProp.CryptoExKey, + SignHashExProp.RequireTerminalCertSig, + SignHashExProp.RequireTerminalTxSig, + SignHashExProp.CheckPin3 + ).forEach { createItem(block, it) } + return block + } + + private fun denomination(): Block { + val block = createBlock(BlockId.Denomination) + mutableListOf( + Denomination.WriteOnPersonalize, + Denomination.Denomination + ).forEach { createItem(block, it) } + return block + } + + private fun token(): Block { + val block = createBlock(BlockId.Token) + mutableListOf( + Token.ItsToken, + Token.Symbol, + Token.ContractAddress, + Token.Decimal + ).forEach { createItem(block, it) } + return block + } + + private fun productMask(): Block { + val block = createBlock(BlockId.ProdMask) + mutableListOf( + ProductMask.Note, + ProductMask.Tag, + ProductMask.CardId + ).forEach { createItem(block, it) } + return block + } + + private fun settingsMask(): Block { + val block = createBlock(BlockId.SettingsMask) + mutableListOf( + SettingsMask.IsReusable, + SettingsMask.NeedActivation, + SettingsMask.ForbidPurge, + SettingsMask.AllowSelectBlockchain, + SettingsMask.UseBlock, + SettingsMask.OneApdu, + SettingsMask.UseCvc, + SettingsMask.AllowSwapPin, + SettingsMask.AllowSwapPin2, + SettingsMask.ForbidDefaultPin, + SettingsMask.SmartSecurityDelay, + SettingsMask.ProtectIssuerDataAgainstReplay, + SettingsMask.SkipSecurityDelayIfValidated, + SettingsMask.SkipPin2CvcIfValidated, + SettingsMask.SkipSecurityDelayOnLinkedTerminal, + SettingsMask.RestrictOverwriteExtraIssuerData + ).forEach { createItem(block, it) } + return block + } + + private fun settingsMaskProtocolEnc(): Block { + val block = createBlock(BlockId.SettingsMaskProtocolEnc) + mutableListOf( + SettingsMaskProtocolEnc.AllowUnencrypted, + SettingsMaskProtocolEnc.AlloFastEncryption).forEach { createItem(block, it) } + return block + } + + private fun settingsMaskNdef(): Block { + val block = createBlock(BlockId.SettingsMaskNdef) + mutableListOf( + SettingsMaskNdef.UseNdef, + SettingsMaskNdef.DynamicNdef, + SettingsMaskNdef.DisablePrecomputedNdef, + SettingsMaskNdef.Aar, + SettingsMaskNdef.AarCustom + ).forEach { createItem(block, it) } + return block + } + + private fun pins(): Block { + val block = createBlock(BlockId.Pins) + mutableListOf(Pins.Pin, Pins.Pin2, Pins.Pin3, Pins.Cvc, Pins.PauseBeforePin2) + .forEach { createItem(block, it) } + return block + } + + private fun createBlock(id: Id): ListItemBlock { + return ListItemBlock(id).apply { addItem(TextItem(id)) } + } + + private fun addPayload(block: Block, from: PersonalizeConfig) { + block.payload[PayloadKey.incomingJson] = from + } + + private fun createItem(block: ListItemBlock, id: Id) { + val holder = associations.get(id) ?: return + val item = when { + IdItemHelper.blockIdList.contains(id) -> TextItem(id, holder.get() as? String) + IdItemHelper.listItemList.contains(id) -> ListItem(id, holder.list as List, holder.get()) + IdItemHelper.boolList.contains(id) -> BoolItem(id, holder.get() as? Boolean) + IdItemHelper.editTextList.contains(id) -> EditTextItem(id, holder.get() as? String) + IdItemHelper.numberList.contains(id) -> NumberItem(id, holder.get() as? Number) + else -> ListItemBlock(Additional.UNDEFINED) + } + block.addItem(item) + } + + fun toConfig(blockList: List, defaultConfig: PersonalizeConfig): PersonalizeConfig { + return valueMapper.mapOnObject(blockList, defaultConfig) + } + + fun createCardConfig(applyConfig: PersonalizeConfig): CardConfig { + val signingMethod = com.tangem.commands.SigningMethod.build( + signHash = applyConfig.SigningMethod0, + signRaw = applyConfig.SigningMethod1, + signHashValidatedByIssuer = applyConfig.SigningMethod2, + signRawValidatedByIssuer = applyConfig.SigningMethod3, + signHashValidatedByIssuerAndWriteIssuerData = applyConfig.SigningMethod4, + signRawValidatedByIssuerAndWriteIssuerData = applyConfig.SigningMethod5, + signPos = applyConfig.SigningMethod6 + ) + + val isNote = applyConfig.cardData.product_note + val isTag = applyConfig.cardData.product_tag + val isIdCard = applyConfig.cardData.product_id_card + + val productMaskBuilder = ProductMaskBuilder() + if (isNote) productMaskBuilder.add(com.tangem.commands.ProductMask.note) + if (isTag) productMaskBuilder.add(com.tangem.commands.ProductMask.tag) + if (isIdCard) productMaskBuilder.add(com.tangem.commands.ProductMask.idCard) + val productMask = productMaskBuilder.build() + + var tokenSymbol: String? = null + var tokenContractAddress: String? = null + var tokenDecimal: Int? = null + if (applyConfig.itsToken) { + tokenSymbol = applyConfig.symbol + tokenContractAddress = applyConfig.contractAddress + tokenDecimal = applyConfig.decimal.toInt() + } + + val cardData = CardData( + blockchainName = applyConfig.blockchain, + batchId = applyConfig.cardData.batch, + productMask = productMask, + tokenSymbol = tokenSymbol, + tokenContractAddress = tokenContractAddress, + tokenDecimal = tokenDecimal, + issuerName = null, + manufactureDateTime = Calendar.getInstance().time, + manufacturerSignature = null) + + + val ndefs = mutableListOf() + when (applyConfig.aar) { + "None" -> null + "--- CUSTOM ---" -> NdefRecord(NdefRecord.Type.AAR, applyConfig.aarCustom) + else -> NdefRecord(NdefRecord.Type.AAR, applyConfig.aar) + }?.let { ndefs.add(it) } + + return CardConfig( + "Tangem", + "Tangem Test", + applyConfig.series, + applyConfig.startNumber, + 1000, + applyConfig.PIN, + applyConfig.PIN2, + applyConfig.PIN3, + applyConfig.hexCrExKey, + applyConfig.CVC, + applyConfig.pauseBeforePIN2.toInt(), + applyConfig.smartSecurityDelay, + EllipticCurve.byName(applyConfig.curveID) ?: EllipticCurve.Secp256k1, + signingMethod, + applyConfig.MaxSignatures.toInt(), + applyConfig.isReusable, + applyConfig.allowSwapPIN, + applyConfig.allowSwapPIN2, + applyConfig.useActivation, + applyConfig.useCVC, + applyConfig.useNDEF, + applyConfig.useDynamicNDEF, + applyConfig.oneApdu, + applyConfig.useBlock, + applyConfig.allowSelectBlockchain, + applyConfig.forbidPurgeWallet, + applyConfig.protocolAllowUnencrypted, + applyConfig.protocolAllowStaticEncryption, + applyConfig.protectIssuerDataAgainstReplay, + applyConfig.forbidDefaultPIN, + applyConfig.disablePrecomputedNDEF, + applyConfig.skipSecurityDelayIfValidatedByIssuer, + applyConfig.skipCheckPIN2andCVCIfValidatedByIssuer, + applyConfig.skipSecurityDelayIfValidatedByLinkedTerminal, + applyConfig.restrictOverwriteIssuerDataEx, + applyConfig.requireTerminalTxSignature, + applyConfig.requireTerminalCertSignature, + applyConfig.checkPIN3onCard, + applyConfig.createWallet, + cardData, + ndefs + ) + } +} + +class IdItemHelper { + companion object { + val blockIdList = mutableListOf( + BlockId.CardNumber, BlockId.Common, BlockId.SigningMethod, BlockId.SignHashExProp, BlockId.Denomination, + BlockId.Token, BlockId.ProdMask, BlockId.SettingsMask, BlockId.SettingsMaskProtocolEnc, + BlockId.SettingsMaskNdef, BlockId.Pins + ) + + val listItemList = mutableListOf(Common.Curve, Common.Blockchain, SettingsMaskNdef.Aar, Pins.PauseBeforePin2) + + val boolList = mutableListOf( + Common.CreateWallet, SigningMethod.SignTx, SigningMethod.SignTxRaw, SigningMethod.SignValidatedTx, + SigningMethod.SignValidatedTxRaw, SigningMethod.SignValidatedTxIssuer, + SigningMethod.SignValidatedTxRawIssuer, SigningMethod.SignExternal, SignHashExProp.RequireTerminalCertSig, + SignHashExProp.RequireTerminalTxSig, SignHashExProp.CheckPin3, Denomination.WriteOnPersonalize, Token.ItsToken, + ProductMask.Note, ProductMask.Tag, ProductMask.CardId, SettingsMask.IsReusable, SettingsMask.NeedActivation, + SettingsMask.ForbidPurge, SettingsMask.AllowSelectBlockchain, SettingsMask.UseBlock, SettingsMask.OneApdu, + SettingsMask.UseCvc, SettingsMask.AllowSwapPin, SettingsMask.AllowSwapPin2, SettingsMask.ForbidDefaultPin, + SettingsMask.SmartSecurityDelay, SettingsMask.ProtectIssuerDataAgainstReplay, + SettingsMask.SkipSecurityDelayIfValidated, SettingsMask.SkipPin2CvcIfValidated, + SettingsMask.SkipSecurityDelayOnLinkedTerminal, SettingsMask.RestrictOverwriteExtraIssuerData, + SettingsMaskProtocolEnc.AllowUnencrypted, SettingsMaskProtocolEnc.AlloFastEncryption, + SettingsMaskNdef.UseNdef, SettingsMaskNdef.DynamicNdef, SettingsMaskNdef.DisablePrecomputedNdef + ) + + val editTextList = mutableListOf( + CardNumber.Series, Common.BlockchainCustom, SignHashExProp.CryptoExKey, Token.Symbol, + Token.ContractAddress, Pins.Pin, Pins.Pin2, Pins.Pin3, Pins.Cvc, SettingsMaskNdef.AarCustom + ) + + val numberList = mutableListOf( + CardNumber.Number, Common.MaxSignatures, SignHashExProp.PinLessFloorLimit, Denomination.Denomination, Token.Decimal + ) + } +} + +class JsonTails( + val count: Int, + val numberFormat: String, + val issuerData: Any?, + val releaseVersion: Boolean, + val issuerName: String +) \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/ValueMapper.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/ValueMapper.kt new file mode 100644 index 0000000000..9063a56fe3 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/ValueMapper.kt @@ -0,0 +1,117 @@ +package com.tangem.tangemtest.ucase.variants.personalize.converter + +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.Block +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.impl.KeyValue +import com.tangem.tangemtest._arch.structure.impl.ListValueWrapper +import com.tangem.tangemtest.ucase.variants.personalize.* +import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +class ValueMapper { + private val default: IdToValueAssociations = IdToValueAssociations() + private val updatedByItemList: IdToValueAssociations = IdToValueAssociations() + + + fun mapOnObject(itemList: List, defaultConfig: PersonalizeConfig): PersonalizeConfig { + default.init(defaultConfig) + updatedByItemList.init(defaultConfig) + startMapping(itemList) + return createConfig() + } + + private fun startMapping(itemList: List) { + itemList.forEach { item -> mapItem(item) } + } + + private fun mapItem(item: Item) { + when (item) { + is Block -> startMapping(item.itemList) + is BaseItem<*> -> { + val value = updatedByItemList.get(item.id) ?: return + value.set(item.viewModel.data) + } + } + } + + private fun createConfig(): PersonalizeConfig { + val export = PersonalizeConfig() + export.series = getTyped(CardNumber.Series) + export.startNumber = getTyped(CardNumber.Number) + export.curveID = getTyped(Common.Curve) + export.blockchain = getTyped(Common.Blockchain) + export.blockchainCustom = getTyped(Common.BlockchainCustom) + export.MaxSignatures = getTyped(Common.MaxSignatures) + export.createWallet = getTyped(Common.CreateWallet) + export.SigningMethod0 = getTyped(SigningMethod.SignTx) + export.SigningMethod1 = getTyped(SigningMethod.SignTxRaw) + export.SigningMethod2 = getTyped(SigningMethod.SignValidatedTx) + export.SigningMethod3 = getTyped(SigningMethod.SignValidatedTxRaw) + export.SigningMethod4 = getTyped(SigningMethod.SignValidatedTxIssuer) + export.SigningMethod5 = getTyped(SigningMethod.SignValidatedTxRawIssuer) + export.SigningMethod6 = getTyped(SigningMethod.SignExternal) + export.pinLessFloorLimit = getTyped(SignHashExProp.PinLessFloorLimit) + export.hexCrExKey = getTyped(SignHashExProp.CryptoExKey) + export.requireTerminalCertSignature = getTyped(SignHashExProp.RequireTerminalCertSig) + export.requireTerminalTxSignature = getTyped(SignHashExProp.RequireTerminalTxSig) + export.checkPIN3onCard = getTyped(SignHashExProp.CheckPin3) + export.writeOnPersonalization = getTyped(Denomination.WriteOnPersonalize) + export.denomination = getTyped(Denomination.Denomination) + export.itsToken = getTyped(Token.ItsToken) + export.symbol = getTyped(Token.Symbol) + export.contractAddress = getTyped(Token.ContractAddress) + export.decimal = getTyped(Token.Decimal) + export.cardData = export.cardData.apply { this.product_note = getTyped(ProductMask.Note) } + export.cardData = export.cardData.apply { this.product_tag = getTyped(ProductMask.Tag) } + export.cardData = export.cardData.apply { this.product_id_card = getTyped(ProductMask.CardId) } + export.isReusable = getTyped(SettingsMask.IsReusable) + export.useActivation = getTyped(SettingsMask.NeedActivation) + export.forbidPurgeWallet = getTyped(SettingsMask.ForbidPurge) + export.allowSelectBlockchain = getTyped(SettingsMask.AllowSelectBlockchain) + export.useBlock = getTyped(SettingsMask.UseBlock) + export.oneApdu = getTyped(SettingsMask.OneApdu) + export.useCVC = getTyped(SettingsMask.UseCvc) + export.allowSwapPIN = getTyped(SettingsMask.AllowSwapPin) + export.allowSwapPIN2 = getTyped(SettingsMask.AllowSwapPin2) + export.forbidDefaultPIN = getTyped(SettingsMask.ForbidDefaultPin) + export.smartSecurityDelay = getTyped(SettingsMask.SmartSecurityDelay) + export.protectIssuerDataAgainstReplay = getTyped(SettingsMask.ProtectIssuerDataAgainstReplay) + export.skipSecurityDelayIfValidatedByIssuer = getTyped(SettingsMask.SkipSecurityDelayIfValidated) + export.skipCheckPIN2andCVCIfValidatedByIssuer = getTyped(SettingsMask.SkipPin2CvcIfValidated) + export.skipSecurityDelayIfValidatedByLinkedTerminal = getTyped(SettingsMask.SkipSecurityDelayOnLinkedTerminal) + export.restrictOverwriteIssuerDataEx = getTyped(SettingsMask.RestrictOverwriteExtraIssuerData) + export.protocolAllowUnencrypted = getTyped(SettingsMaskProtocolEnc.AllowUnencrypted) + export.allowFastEncryption = getTyped(SettingsMaskProtocolEnc.AlloFastEncryption) + export.useNDEF = getTyped(SettingsMaskNdef.UseNdef) + export.useDynamicNDEF = getTyped(SettingsMaskNdef.DynamicNdef) + export.disablePrecomputedNDEF = getTyped(SettingsMaskNdef.DisablePrecomputedNdef) + export.aar = getTyped(SettingsMaskNdef.Aar) + export.aarCustom = getTyped(SettingsMaskNdef.AarCustom) + export.PIN = getTyped(Pins.Pin) + export.PIN2 = getTyped(Pins.Pin2) + export.PIN3 = getTyped(Pins.Pin3) + export.CVC = getTyped(Pins.Cvc) + export.pauseBeforePIN2 = getTyped(Pins.PauseBeforePin2) + return export + } + + private inline fun getTyped(id: Id): Type { + return getTypedBy(updatedByItemList, id) ?: getTypedBy(default, id)!! + } + + private inline fun getTypedBy(associations: IdToValueAssociations, id: Id): Type? { + Log.d(this, "getTyped for id: $id") + var typedValue = associations.get(id)?.get() + + typedValue = when (typedValue) { + is ListValueWrapper -> (typedValue.selectedItem as KeyValue).value as Type + else -> typedValue as? Type ?: null + } + return typedValue + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizeParams.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizeParams.kt new file mode 100644 index 0000000000..c8d9afa0d1 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizeParams.kt @@ -0,0 +1,51 @@ +package com.tangem.tangemtest.ucase.variants.personalize.dto + +import com.tangem.commands.personalization.entities.Acquirer +import com.tangem.commands.personalization.entities.Issuer +import com.tangem.commands.personalization.entities.Manufacturer +import com.tangem.common.KeyPair + +/** +[REDACTED_AUTHOR] + */ + +interface DefaultPersonalizeParams { + companion object { + + fun issuer(): Issuer { + val name = "TANGEM SDK" + return Issuer( + name = name, + id = name + "\u0000", + dataKeyPair = KeyPair( + "".toByteArray(), + "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92".toByteArray() + ), + transactionKeyPair = KeyPair( + "".toByteArray(), + "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312".toByteArray() + ) + ) + } + + fun acquirer(): Acquirer { + val name = "Smart Cash" + return Acquirer( + name = name, + id = name + "\u0000", + keyPair = KeyPair( + "".toByteArray(), + "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92".toByteArray() + ) + ) + } + + fun manufacturer(): Manufacturer { + val keys = Manufacturer(com.tangem.tangemtest.ucase.variants.personalize.dto.Manufacturer.Mode.Developer) + return Manufacturer( + name = "Tangem", + keyPair = KeyPair(keys.publicKey, keys.privateKey) + ) + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/Manufacturer.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/Manufacturer.kt new file mode 100644 index 0000000000..8a366eeb26 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/Manufacturer.kt @@ -0,0 +1,53 @@ +package com.tangem.tangemtest.ucase.variants.personalize.dto + +class Manufacturer(private val mode: Mode) { + + private val devPrivateKeyArray = byteArrayOf( + 0x1B.toByte(), 0x48.toByte(), 0xCF.toByte(), 0xD2.toByte(), 0x4B.toByte(), 0xBB.toByte(), 0x5B.toByte(), 0x39.toByte(), + 0x47.toByte(), 0x71.toByte(), 0xED.toByte(), 0x81.toByte(), 0xF2.toByte(), 0xBA.toByte(), 0xCF.toByte(), 0x57.toByte(), + 0x47.toByte(), 0x9E.toByte(), 0x47.toByte(), 0x35.toByte(), 0xEB.toByte(), 0x14.toByte(), 0x05.toByte(), 0x08.toByte(), + 0x39.toByte(), 0x27.toByte(), 0x37.toByte(), 0x2D.toByte(), 0x40.toByte(), 0xDA.toByte(), 0x9E.toByte(), 0x92.toByte() + ) + private val devPublicKeyArray = byteArrayOf(0x04, + 0xBA.toByte(), 0xB8.toByte(), 0x6D.toByte(), 0x56.toByte(), 0x29.toByte(), 0x8C.toByte(), 0x99.toByte(), 0x6F.toByte(), + 0x56.toByte(), 0x4A.toByte(), 0x84.toByte(), 0xFC.toByte(), 0x88.toByte(), 0xE2.toByte(), 0x8A.toByte(), 0xED.toByte(), + 0x38.toByte(), 0x18.toByte(), 0x4B.toByte(), 0x12.toByte(), 0xF0.toByte(), 0x7E.toByte(), 0x51.toByte(), 0x91.toByte(), + 0x13.toByte(), 0xBE.toByte(), 0xF4.toByte(), 0x8C.toByte(), 0x76.toByte(), 0xF3.toByte(), 0xDF.toByte(), 0x3A.toByte(), + 0xDC.toByte(), 0x30.toByte(), 0x35.toByte(), 0x99.toByte(), 0xB0.toByte(), 0x8A.toByte(), 0xC0.toByte(), 0x5B.toByte(), + 0x55.toByte(), 0xEC.toByte(), 0x3D.toByte(), 0xF9.toByte(), 0x8D.toByte(), 0x93.toByte(), 0x38.toByte(), 0x57.toByte(), + 0x3A.toByte(), 0x62.toByte(), 0x42.toByte(), 0xF7.toByte(), 0x6F.toByte(), 0x5D.toByte(), 0x28.toByte(), 0xF4.toByte(), + 0xF0.toByte(), 0xF3.toByte(), 0x64.toByte(), 0xE8.toByte(), 0x7E.toByte(), 0x8F.toByte(), 0xCA.toByte(), 0x2F.toByte() + ) + private val releasePublicKeyArray = byteArrayOf(0x04.toByte(), + 0x2E.toByte(), 0xDE.toByte(), 0x11.toByte(), 0x9B.toByte(), 0xF3.toByte(), 0x37.toByte(), 0xB2.toByte(), 0x64.toByte(), + 0xFD.toByte(), 0xA1.toByte(), 0x32.toByte(), 0xCF.toByte(), 0xC7.toByte(), 0xC1.toByte(), 0x77.toByte(), 0x82.toByte(), + 0x4D.toByte(), 0x36.toByte(), 0x17.toByte(), 0xDA.toByte(), 0xC8.toByte(), 0x0F.toByte(), 0x25.toByte(), 0xDB.toByte(), + 0xB2.toByte(), 0xA4.toByte(), 0xA8.toByte(), 0xA1.toByte(), 0x18.toByte(), 0x3C.toByte(), 0x03.toByte(), 0xB9.toByte(), + 0x15.toByte(), 0x23.toByte(), 0x05.toByte(), 0xF8.toByte(), 0xF1.toByte(), 0xDB.toByte(), 0x97.toByte(), 0x00.toByte(), + 0x45.toByte(), 0x18.toByte(), 0x48.toByte(), 0x0D.toByte(), 0x50.toByte(), 0x91.toByte(), 0xAD.toByte(), 0xC1.toByte(), + 0xCA.toByte(), 0xB9.toByte(), 0xEA.toByte(), 0xCC.toByte(), 0xC1.toByte(), 0x8E.toByte(), 0x1B.toByte(), 0x9E.toByte(), + 0x9C.toByte(), 0x3B.toByte(), 0xEF.toByte(), 0xB2.toByte(), 0x93.toByte(), 0xDD.toByte(), 0x37.toByte(), 0xB2.toByte() + ) + + val publicKey: ByteArray + get() = if (mode == Mode.Developer) { + devPublicKeyArray + } else { + releasePublicKeyArray + } + + @get:Throws(Exception::class) + val privateKey: ByteArray + get() = if (mode == Mode.Developer) { + devPrivateKeyArray + } else { + throw Exception("Real manufacturer key is very secret!") + } + + val name: String + get() = if (mode == Mode.Release) "SMART CASH" else "SMART CASH SDK" + + enum class Mode { + Developer, Release + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizeConfig.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizeConfig.kt new file mode 100644 index 0000000000..c3362c4971 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizeConfig.kt @@ -0,0 +1,108 @@ +package com.tangem.tangemtest.ucase.variants.personalize.dto + +/** +[REDACTED_AUTHOR] + */ +class PersonalizeConfig { + + // Card number + var series = "BB" + var startNumber: Long = 300000000000 + + + // Common + var curveID = "ed25519" + var blockchain = "BTC" + var blockchainCustom = "" + var MaxSignatures: Long = 100 + var createWallet = false + + // Signing method + var SigningMethod0 = true + var SigningMethod1 = false + var SigningMethod2 = false + var SigningMethod3 = false + var SigningMethod4 = false + var SigningMethod5 = false + var SigningMethod6 = false + + + // Sign hash external properties + var pinLessFloorLimit: Long = 100000 + var hexCrExKey = "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF" + var requireTerminalTxSignature = false + var requireTerminalCertSignature = false + var checkPIN3onCard = true + + + // Denomination + var writeOnPersonalization = false + var denomination: Long = 1000000 + + + // Token + var itsToken = false + var symbol = "" + var contractAddress = "" + var decimal: Long = 0 + + + var cardData = CardData() + + + // Settings mask + var isReusable = true + var useActivation = false + var forbidPurgeWallet = false + var allowSelectBlockchain = false + var useBlock = false + var oneApdu = false + var useCVC = false + var allowSwapPIN = true + var allowSwapPIN2 = true + var forbidDefaultPIN = false + var smartSecurityDelay = false + var protectIssuerDataAgainstReplay = true + var skipSecurityDelayIfValidatedByIssuer = true + var skipCheckPIN2andCVCIfValidatedByIssuer = true + var skipSecurityDelayIfValidatedByLinkedTerminal = true + var restrictOverwriteIssuerDataEx = false + + + // Settings mask - protocol encryption + var protocolAllowUnencrypted = true + var allowFastEncryption = false + var protocolAllowStaticEncryption = true + + + var useNDEF = true + var useDynamicNDEF = true + var disablePrecomputedNDEF = false + var aar = "com.tangem.wallet" + var aarCustom = "" + + + // Pins + var PIN = "000000" + var PIN2 = "000" + var PIN3 = "" + var CVC = "000" + var pauseBeforePIN2: Long = 15000 + + + // not used +// var count: Long = 20 +// var numberFormat = "" +// var issuerData = null +// var releaseVersion = false +// var issuerName = "TANGEM SDK" +} + +class CardData { + var date = "2020-02-17" + var batch = "FF87" + var blockchain = "IROHA" + var product_note = true + var product_tag = false + var product_id_card = false +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeFragment.kt new file mode 100644 index 0000000000..f7c8ed9ea9 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeFragment.kt @@ -0,0 +1,90 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.Snackbar +import com.tangem.CardManager +import com.tangem.tangem_sdk_new.extensions.init +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.abstraction.Block +import com.tangem.tangemtest._main.MainViewModel +import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager +import com.tangem.tangemtest.ucase.domain.paramsManager.ParamsManagerFactory +import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.ui.ActionViewModelFactory +import com.tangem.tangemtest.ucase.ui.ParamsViewModel +import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.WidgetBuilder +import kotlinx.android.synthetic.main.fg_personalize.* +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +class PersonalizeFragment : Fragment() { + + private val mainActivityVM: MainViewModel by activityViewModels() + private val personalizeVM: PersonalizeViewModel by viewModels { PersonalizeViewModelFactory(PersonalizeConfig()) } + + private val itemsManager: ItemsManager by lazy { ParamsManagerFactory.createFactory().get(ActionType.Personalize)!! } + private val paramsVM: ParamsViewModel by viewModels() { ActionViewModelFactory(itemsManager) } + + private val blockContainer: ViewGroup by lazy { + mainView.findViewById(R.id.ll_container) + } + private lateinit var mainView: View + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + Log.d(this, "onCreateView") + mainView = inflater.inflate(R.layout.fg_personalize, container, false) + return mainView + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val blockList = mutableListOf() + personalizeVM.ldBlockList.observe(viewLifecycleOwner, Observer { list -> + blockList.clear() + blockList.addAll(list) + blockList.forEach { WidgetBuilder().build(it, blockContainer) } + }) + mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer { + personalizeVM.toggleDescriptionVisibility(it) + }) + + val cardManager = CardManager.init(requireActivity()) + paramsVM.setCardManager(cardManager) + paramsVM.ldResponse.observe(viewLifecycleOwner, Observer { + Log.d(this, "action response: ${if (it.length > 50) it.substring(0..50) else it}") + showSnackbarMessage(it) + }) + paramsVM.seError.observe(viewLifecycleOwner, Observer { showSnackbarMessage(it) }) + + fab_action?.setOnClickListener { + val cardConfig = personalizeVM.createCardConfig(personalizeVM.createConfig(blockList)) + paramsVM.attachToPayload(mutableMapOf(PayloadKey.cardConfig to cardConfig)) + paramsVM.invokeMainAction() + } + } + + protected fun showSnackbarMessage(message: String) { + Snackbar.make(mainView, message, BaseTransientBottomBar.LENGTH_SHORT).show() + } +} + + + + + + + diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeViewModel.kt new file mode 100644 index 0000000000..8449ddcfad --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeViewModel.kt @@ -0,0 +1,51 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui + +import android.view.View +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.google.gson.Gson +import com.tangem.commands.personalization.entities.CardConfig +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.Block +import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizeConfigConverter +import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig + +/** +[REDACTED_AUTHOR] + */ +class PersonalizeViewModelFactory(private val config: PersonalizeConfig) : ViewModelProvider.NewInstanceFactory() { + override fun create(modelClass: Class): T = PersonalizeViewModel(config) as T +} + +class PersonalizeViewModel(private val config: PersonalizeConfig) : ViewModel() { + + val ldBlockList: MutableLiveData> by lazy { MutableLiveData(initBlockList()) } + + private fun initBlockList(): List = createBlocksFromConfig(config) + + fun createBlocksFromConfig(config: PersonalizeConfig): List { + return PersonalizeConfigConverter().toBlock(config) + } + + fun createConfig(blocList: List): PersonalizeConfig { + return PersonalizeConfigConverter().toConfig(blocList, PersonalizeConfig()) + } + + fun createCardConfig(config: PersonalizeConfig): CardConfig { + return PersonalizeConfigConverter().createCardConfig(config) + } + + fun convertToJson(config: PersonalizeConfig): String { + return Gson().toJson(config) + } + + fun toggleDescriptionVisibility(state: Boolean) { + ldBlockList.value?.forEach { block -> + block.itemList.forEach { item -> + val vm = item as? BaseItem<*> ?: return@forEach + vm.viewModel.viewState.descriptionVisibility = if (state) View.VISIBLE else View.GONE + } + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/WidgetBuilder.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/WidgetBuilder.kt new file mode 100644 index 0000000000..a9aa921bcf --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/WidgetBuilder.kt @@ -0,0 +1,48 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets + +import android.view.ViewGroup +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.Block +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.abstraction.ListItemBlock +import com.tangem.tangemtest._arch.structure.impl.* +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.ViewWidget +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.StubWidget +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.block.LinearBlockWidget +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item.* + +/** +[REDACTED_AUTHOR] + */ +class WidgetBuilder { + + fun build(item: Item, parent: ViewGroup): ViewWidget? { + return when (item) { + is Block -> buildBlock(item, parent) + is BaseItem<*> -> buildUnitItem(item, parent) + else -> StubWidget(parent) + } + } + + private fun buildBlock(block: Block, parent: ViewGroup): ViewWidget { + return when (block) { + is ListItemBlock -> { + val linearBlock = LinearBlockWidget(parent, block) + block.getItems().forEach { build(it, linearBlock.view as ViewGroup) } + linearBlock + } + else -> StubWidget(parent) + } + } + + private fun buildUnitItem(item: BaseItem<*>, parent: ViewGroup): ViewWidget? { + return when (item) { + is TextItem -> TextWidget(parent, item) + is EditTextItem -> EditTextWidget(parent, item) + is NumberItem -> NumberWidget(parent, item) + is BoolItem -> SwitchWidget(parent, item) + is ListItem -> SpinnerWidget(parent, item) + else -> null + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/abstraction/ViewWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/abstraction/ViewWidget.kt new file mode 100644 index 0000000000..6295598d1f --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/abstraction/ViewWidget.kt @@ -0,0 +1,48 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.DataHolder +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel +import com.tangem.tangemtest.ucase.resources.MainResourceHolder +import com.tangem.tangemtest.ucase.resources.Resources +import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder + +/** +[REDACTED_AUTHOR] + */ +interface ViewWidget : LayoutHolder { + val view: View +} + +interface DataWidget : ViewWidget { + var dataItem: BaseItem +} + +interface BlockViewWidget : ViewWidget, DataHolder>> + +abstract class BaseWidget( + parent: ViewGroup, + override var dataItem: BaseItem +) : ViewWidget, DataWidget { + + override val view: View = inflate(getLayoutId(), parent) +} + +abstract class BaseBlockWidget(parent: ViewGroup) : BlockViewWidget { + override val view: View = inflate(getLayoutId(), parent) +} + +fun DataWidget<*>.getResNameId(): Int = MainResourceHolder.safeGet(dataItem.id).resName + +fun DataWidget<*>.getResDescription(): Int? = MainResourceHolder.safeGet(dataItem.id).resDescription + +internal fun inflate(id: Int, parent: ViewGroup): View { + val layoutId = if (id <= 0) R.layout.w_empty else id + val inflatedView = LayoutInflater.from(parent.context).inflate(layoutId, parent, false) + parent.addView(inflatedView) + return inflatedView +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/StubWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/StubWidget.kt new file mode 100644 index 0000000000..a087c6e0cb --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/StubWidget.kt @@ -0,0 +1,14 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl + +import android.view.ViewGroup +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseBlockWidget + +/** +[REDACTED_AUTHOR] + */ +class StubWidget(parent: ViewGroup) : BaseBlockWidget(parent) { + override fun getLayoutId(): Int = R.layout.w_empty + override var viewModel: List> = listOf() +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/block/LinearBlockWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/block/LinearBlockWidget.kt new file mode 100644 index 0000000000..fb4be30444 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/block/LinearBlockWidget.kt @@ -0,0 +1,21 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.block + +import android.view.ViewGroup +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel +import com.tangem.tangemtest._arch.structure.abstraction.ListItemBlock +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseBlockWidget + +/** +[REDACTED_AUTHOR] + */ +class LinearBlockWidget( + parent: ViewGroup, + private val children: ListItemBlock +) : BaseBlockWidget(parent) { + override fun getLayoutId(): Int = R.layout.w_personilize_block + + override var viewModel: List> = listOf() + get() = children.itemList.map { it as BaseItem<*> }.map { it.viewModel } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/DescriptionWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/DescriptionWidget.kt new file mode 100644 index 0000000000..c50a5e91dd --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/DescriptionWidget.kt @@ -0,0 +1,54 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item + +import android.view.ViewGroup +import android.widget.TextView +import androidx.transition.AutoTransition +import androidx.transition.TransitionManager +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseWidget +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResDescription +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +abstract class DescriptionWidget( + parent: ViewGroup, + data: BaseItem +) : BaseWidget(parent, data) { + + private val descriptionContainer: ViewGroup by lazy { view.findViewById(R.id.container_description) } + private val tvDescription: TextView? by lazy { descriptionContainer.findViewById(R.id.tv_description) } + + init { + Log.d(this, "init id: ${dataItem.id}") + initDescriptionWidget() + } + + private fun initDescriptionWidget() { + dataItem.viewModel.viewState.onDescriptionVisibilityChanged = { changeDescriptionVisibility(it) } + } + + protected fun changeDescriptionVisibility(state: Int) { + val tv = tvDescription ?: return + val descriptionId = getResDescription() ?: return + val description = tv.context.getString(descriptionId) + if (description.isEmpty()) return + + tv.text = description + TransitionManager.beginDelayedTransition(view.parent as ViewGroup, AutoTransition()) + descriptionContainer.visibility = state + } +} + + + + + + + + + + + diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/EditTextWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/EditTextWidget.kt new file mode 100644 index 0000000000..a60b17fe68 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/EditTextWidget.kt @@ -0,0 +1,41 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item + +import android.text.Editable +import android.text.TextWatcher +import android.view.ViewGroup +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.impl.EditTextItem +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId +import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf + +/** +[REDACTED_AUTHOR] + */ +class EditTextWidget(parent: ViewGroup, data: EditTextItem) : DescriptionWidget(parent, data) { + override fun getLayoutId(): Int = R.layout.w_personalize_item_edit_text + + private val tilItem = view.findViewById(R.id.til_item) + private val etItem = view.findViewById(R.id.et_item) + + private val watcher = object : TextWatcher { + override fun afterTextChanged(s: Editable?) { + dataItem.viewModel.updateDataByView(stringOf(s)) + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + } + + init { + tilItem.hint = tilItem.context.getString(getResNameId()) + etItem.setText(dataItem.viewModel.data) + etItem.addTextChangedListener(watcher) + dataItem.viewModel.onDataUpdated = { + etItem.removeTextChangedListener(watcher) + etItem.setText(it) + etItem.addTextChangedListener(watcher) + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/NumberWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/NumberWidget.kt new file mode 100644 index 0000000000..0c77806970 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/NumberWidget.kt @@ -0,0 +1,46 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item + +import android.text.Editable +import android.text.TextWatcher +import android.view.ViewGroup +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.impl.NumberItem +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId +import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf + +/** +[REDACTED_AUTHOR] + */ +class NumberWidget(parent: ViewGroup, data: NumberItem) : DescriptionWidget(parent, data) { + override fun getLayoutId(): Int = R.layout.w_personalize_item_number + + private val tilItem = view.findViewById(R.id.til_item) + private val etItem = view.findViewById(R.id.et_item) + + private val watcher = object : TextWatcher { + override fun afterTextChanged(s: Editable?) { + dataItem.viewModel.updateDataByView(getIntValue(stringOf(s))) + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + } + + init { + tilItem.hint = tilItem.context.getString(getResNameId()) + etItem.setText(stringOf(dataItem.viewModel.data)) + etItem.addTextChangedListener(watcher) + dataItem.viewModel.onDataUpdated = { + etItem.removeTextChangedListener(watcher) + etItem.setText(stringOf(it)) + etItem.addTextChangedListener(watcher) + } + } + + private fun getIntValue(value: String): Long { + return if (value.isEmpty()) 0L else value.toLong() + } + +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SpinnerWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SpinnerWidget.kt new file mode 100644 index 0000000000..35243b70aa --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SpinnerWidget.kt @@ -0,0 +1,84 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item + +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.BaseAdapter +import android.widget.Spinner +import android.widget.TextView +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.impl.KeyValue +import com.tangem.tangemtest._arch.structure.impl.ListItem +import com.tangem.tangemtest._arch.structure.impl.ListValueWrapper +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId +import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate +import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf + +/** +[REDACTED_AUTHOR] + */ +class SpinnerWidget(parent: ViewGroup, data: ListItem) : DescriptionWidget(parent, data) { + override fun getLayoutId(): Int = R.layout.w_personalize_item_spinner + + private val spItem = view.findViewById(R.id.sp_item) + private val spAdapter = SpItemAdapter(dataItem.viewModel.data?.itemList) + + private val onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) {} + + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + val vmData = dataItem.viewModel.data ?: return + + vmData.selectedItem = vmData.itemList[position] + dataItem.viewModel.updateDataByView(vmData) + } + } + + init { + val name = view.findViewById(R.id.tv_name) + name.setText(getResNameId()) + spItem.adapter = spAdapter + spItem.onItemSelectedListener = onItemSelectedListener + dataItem.viewModel.onDataUpdated = { + it?.apply { + spItem.onItemSelectedListener = null + this.itemList.firstOrNull { item -> item.value == selectedItem }?.let { + val position = itemList.indexOf(it) + spItem.setSelection(position) + } + spItem.onItemSelectedListener = onItemSelectedListener + } + } + } +} + +class SpItemAdapter(list: List?) : BaseAdapter() { + + private val itemList: List = list ?: listOf() + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = convertView ?: parent.inflate(R.layout.vh_sp_item) + + val tv = view.findViewById(R.id.tv_sp_item) + tv.text = extractData(itemList[position]) + + return view + } + + override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = convertView ?: parent.inflate(R.layout.vh_sp_dropdown_item) + + val tv = view.findViewById(R.id.tv_sp_item) + tv.text = extractData(itemList[position]) + + return view + } + + private fun extractData(item: KeyValue): String? = item.key + + override fun getItem(position: Int): String = stringOf(itemList[position]) + + override fun getItemId(position: Int): Long = position.toLong() + + override fun getCount(): Int = itemList.size +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SwitchWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SwitchWidget.kt new file mode 100644 index 0000000000..3b7d85bed0 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SwitchWidget.kt @@ -0,0 +1,32 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item + +import android.view.ViewGroup +import android.widget.CompoundButton +import androidx.appcompat.widget.SwitchCompat +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.impl.BoolItem +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId + +/** +[REDACTED_AUTHOR] + */ +class SwitchWidget(parent: ViewGroup, data: BoolItem) : DescriptionWidget(parent, data) { + override fun getLayoutId(): Int = R.layout.w_personalize_item_switch + + private val switchItem = view.findViewById(R.id.sw_item) + + private val changeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked -> + dataItem.viewModel.updateDataByView(isChecked) + } + + init { + switchItem.setText(getResNameId()) + switchItem.isChecked = dataItem.viewModel.data ?: false + switchItem.setOnCheckedChangeListener(changeListener) + dataItem.viewModel.onDataUpdated = { + switchItem.setOnCheckedChangeListener(null) + switchItem.isChecked = it ?: false + switchItem.setOnCheckedChangeListener(changeListener) + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/TextWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/TextWidget.kt new file mode 100644 index 0000000000..ae12a19231 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/TextWidget.kt @@ -0,0 +1,20 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item + +import android.view.ViewGroup +import android.widget.TextView +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.impl.TextItem +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId + +/** +[REDACTED_AUTHOR] + */ +class TextWidget(parent: ViewGroup, data: TextItem) : DescriptionWidget(parent, data) { + override fun getLayoutId(): Int = R.layout.w_personalize_item_text + + private val tvName = view.findViewById(R.id.tv_name) + + init { + tvName?.setText(getResNameId()) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/scan/ui/ScanActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/scan/ui/ScanActionFragment.kt new file mode 100644 index 0000000000..ef5e6d8f01 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/scan/ui/ScanActionFragment.kt @@ -0,0 +1,37 @@ +package com.tangem.tangemtest.ucase.variants.scan.ui + +import android.os.Bundle +import android.view.View +import android.widget.TextView +import androidx.lifecycle.Observer +import com.tangem.tangemtest.R +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment +import ru.dev.gbixahue.eu4d.lib.android._android.views.show +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +class ScanActionFragment : BaseCardActionFragment() { + + override fun getLayoutId(): Int = R.layout.fg_action_card_scan + + override fun getAction(): ActionType = ActionType.Scan + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + showActionFab(true) + } + + override fun listenResponse() { + val tvStub = mainView.findViewById(R.id.tv_screen_stub) + val tvResponse by lazy { mainView.findViewById(R.id.tv_action_response_json) } + paramsVM.ldReadResponse.observe(viewLifecycleOwner, Observer { + Log.d(this, "action response: ${if (it.length > 50) it.substring(0..50) else it}") + tvStub.show(false) + tvResponse.text = it + }) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/sign/ui/SignActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/sign/ui/SignActionFragment.kt new file mode 100644 index 0000000000..9ce99ee075 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/sign/ui/SignActionFragment.kt @@ -0,0 +1,15 @@ +package com.tangem.tangemtest.ucase.variants.sign.ui + +import com.tangem.tangemtest.R +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment + +/** +[REDACTED_AUTHOR] + */ +class SignActionFragment : BaseCardActionFragment() { + + override fun getLayoutId(): Int = R.layout.fg_action_card_sign + + override fun getAction(): ActionType = ActionType.Sign +} \ No newline at end of file diff --git a/tangem-demo/src/main/res/anim/fade_in.xml b/tangem-demo/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000000..1052d2aa22 --- /dev/null +++ b/tangem-demo/src/main/res/anim/fade_in.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/anim/fade_out.xml b/tangem-demo/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000000..cfe4193dc3 --- /dev/null +++ b/tangem-demo/src/main/res/anim/fade_out.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/anim/slide_in_left.xml b/tangem-demo/src/main/res/anim/slide_in_left.xml new file mode 100644 index 0000000000..3ab232f97f --- /dev/null +++ b/tangem-demo/src/main/res/anim/slide_in_left.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/anim/slide_in_right.xml b/tangem-demo/src/main/res/anim/slide_in_right.xml new file mode 100644 index 0000000000..5c18975a6f --- /dev/null +++ b/tangem-demo/src/main/res/anim/slide_in_right.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/tangem-demo/src/main/res/anim/slide_out_left.xml b/tangem-demo/src/main/res/anim/slide_out_left.xml new file mode 100644 index 0000000000..3e5db825b6 --- /dev/null +++ b/tangem-demo/src/main/res/anim/slide_out_left.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/tangem-demo/src/main/res/anim/slide_out_right.xml b/tangem-demo/src/main/res/anim/slide_out_right.xml new file mode 100644 index 0000000000..486d3ae08c --- /dev/null +++ b/tangem-demo/src/main/res/anim/slide_out_right.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/color/menu_switch_color_thumb.xml b/tangem-demo/src/main/res/color/menu_switch_color_thumb.xml new file mode 100644 index 0000000000..ad3f42fb35 --- /dev/null +++ b/tangem-demo/src/main/res/color/menu_switch_color_thumb.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/color/menu_switch_color_track.xml b/tangem-demo/src/main/res/color/menu_switch_color_track.xml new file mode 100644 index 0000000000..d3fb4ae385 --- /dev/null +++ b/tangem-demo/src/main/res/color/menu_switch_color_track.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/color/mtrl_fab_bg_color_selector.xml b/tangem-demo/src/main/res/color/mtrl_fab_bg_color_selector.xml new file mode 100644 index 0000000000..a6155affe9 --- /dev/null +++ b/tangem-demo/src/main/res/color/mtrl_fab_bg_color_selector.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tangem-demo/src/main/res/color/mtrl_fab_icon_text_color_selector.xml b/tangem-demo/src/main/res/color/mtrl_fab_icon_text_color_selector.xml new file mode 100644 index 0000000000..0e2e94a4af --- /dev/null +++ b/tangem-demo/src/main/res/color/mtrl_fab_icon_text_color_selector.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tangem-demo/src/main/res/layout/a_main.xml b/tangem-demo/src/main/res/layout/a_main.xml new file mode 100644 index 0000000000..a3736051f4 --- /dev/null +++ b/tangem-demo/src/main/res/layout/a_main.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/tangem-demo/src/main/res/layout/activity_main.xml b/tangem-demo/src/main/res/layout/activity_main.xml deleted file mode 100644 index d3bb8bbabe..0000000000 --- a/tangem-demo/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - -