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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/activity_test_user_data.xml b/tangem-demo/src/main/res/layout/activity_test_user_data.xml
index 261a6efd64..c1874b434b 100644
--- a/tangem-demo/src/main/res/layout/activity_test_user_data.xml
+++ b/tangem-demo/src/main/res/layout/activity_test_user_data.xml
@@ -1,238 +1,238 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:orientation="vertical">
-
-
-
-
-
-
-
-
-
-
+ android:id="@+id/btn_scan"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="16dp"
+ android:text="Scan card" />
-
-
+ android:visibility="gone">
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
+
+
+
+
-
-
-
-
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/fg_action_card_scan.xml b/tangem-demo/src/main/res/layout/fg_action_card_scan.xml
new file mode 100644
index 0000000000..aa343731bc
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/fg_action_card_scan.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/fg_action_card_sign.xml b/tangem-demo/src/main/res/layout/fg_action_card_sign.xml
new file mode 100644
index 0000000000..45978494a3
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/fg_action_card_sign.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/fg_depersonalize.xml b/tangem-demo/src/main/res/layout/fg_depersonalize.xml
new file mode 100644
index 0000000000..45978494a3
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/fg_depersonalize.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/fg_entry_point.xml b/tangem-demo/src/main/res/layout/fg_entry_point.xml
new file mode 100644
index 0000000000..e31b40ea04
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/fg_entry_point.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/fg_personalize.xml b/tangem-demo/src/main/res/layout/fg_personalize.xml
new file mode 100644
index 0000000000..81cd81dc72
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/fg_personalize.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/m_delimiter_h.xml b/tangem-demo/src/main/res/layout/m_delimiter_h.xml
deleted file mode 100644
index 4cf2bb6fbf..0000000000
--- a/tangem-demo/src/main/res/layout/m_delimiter_h.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/m_divider_h.xml b/tangem-demo/src/main/res/layout/m_divider_h.xml
new file mode 100644
index 0000000000..4a5dbcbae5
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/m_divider_h.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/m_divider_v.xml b/tangem-demo/src/main/res/layout/m_divider_v.xml
new file mode 100644
index 0000000000..dd56a750c9
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/m_divider_v.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/menu_item_switch.xml b/tangem-demo/src/main/res/layout/menu_item_switch.xml
new file mode 100644
index 0000000000..0be36f8973
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/menu_item_switch.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/old_activity_main.xml b/tangem-demo/src/main/res/layout/old_activity_main.xml
new file mode 100644
index 0000000000..fa56a8213d
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/old_activity_main.xml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/vh_actions.xml b/tangem-demo/src/main/res/layout/vh_actions.xml
new file mode 100644
index 0000000000..a6081ea0a7
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/vh_actions.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/vh_sp_dropdown_item.xml b/tangem-demo/src/main/res/layout/vh_sp_dropdown_item.xml
new file mode 100644
index 0000000000..bcc0449b44
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/vh_sp_dropdown_item.xml
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/vh_sp_item.xml b/tangem-demo/src/main/res/layout/vh_sp_item.xml
new file mode 100644
index 0000000000..28661a7af2
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/vh_sp_item.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/w_action_response_json.xml b/tangem-demo/src/main/res/layout/w_action_response_json.xml
new file mode 100644
index 0000000000..f775a57533
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/w_action_response_json.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/w_card_incoming_param.xml b/tangem-demo/src/main/res/layout/w_card_incoming_param.xml
new file mode 100644
index 0000000000..f2a99ab8d3
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/w_card_incoming_param.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/w_empty.xml b/tangem-demo/src/main/res/layout/w_empty.xml
new file mode 100644
index 0000000000..53e9ed5043
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/w_empty.xml
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/w_field_description.xml b/tangem-demo/src/main/res/layout/w_field_description.xml
new file mode 100644
index 0000000000..219dc9455f
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/w_field_description.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/w_personalize_item_edit_text.xml b/tangem-demo/src/main/res/layout/w_personalize_item_edit_text.xml
new file mode 100644
index 0000000000..03c96897ba
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/w_personalize_item_edit_text.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/w_personalize_item_number.xml b/tangem-demo/src/main/res/layout/w_personalize_item_number.xml
new file mode 100644
index 0000000000..7544928bd8
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/w_personalize_item_number.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/w_personalize_item_spinner.xml b/tangem-demo/src/main/res/layout/w_personalize_item_spinner.xml
new file mode 100644
index 0000000000..b489f4fc28
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/w_personalize_item_spinner.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tangem-demo/src/main/res/layout/w_personalize_item_switch.xml b/tangem-demo/src/main/res/layout/w_personalize_item_switch.xml
new file mode 100644
index 0000000000..388f3dcc73
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/w_personalize_item_switch.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tangem-demo/src/main/res/layout/w_personalize_item_text.xml b/tangem-demo/src/main/res/layout/w_personalize_item_text.xml
new file mode 100644
index 0000000000..43997729b0
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/w_personalize_item_text.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tangem-demo/src/main/res/layout/w_personalize_widgets_together.xml b/tangem-demo/src/main/res/layout/w_personalize_widgets_together.xml
new file mode 100644
index 0000000000..83787d982c
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/w_personalize_widgets_together.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/layout/w_personilize_block.xml b/tangem-demo/src/main/res/layout/w_personilize_block.xml
new file mode 100644
index 0000000000..630c5cfb96
--- /dev/null
+++ b/tangem-demo/src/main/res/layout/w_personilize_block.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/menu/main_menu.xml b/tangem-demo/src/main/res/menu/main_menu.xml
new file mode 100644
index 0000000000..6241fdec1d
--- /dev/null
+++ b/tangem-demo/src/main/res/menu/main_menu.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/navigation/navigation.xml b/tangem-demo/src/main/res/navigation/navigation.xml
new file mode 100644
index 0000000000..42db9c6d1c
--- /dev/null
+++ b/tangem-demo/src/main/res/navigation/navigation.xml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tangem-demo/src/main/res/values/colors.xml b/tangem-demo/src/main/res/values/colors.xml
index 85ab4dd6a3..20e2693328 100644
--- a/tangem-demo/src/main/res/values/colors.xml
+++ b/tangem-demo/src/main/res/values/colors.xml
@@ -4,5 +4,15 @@
#027AFF
#027AFF
- #000000
+ #26000000
+ #E6959595
+
+ #2B2B2B
+ #888888
+
+ #C6C6C6
+ #FFE391
+ #FFFFFF
+ #FFA100
+
diff --git a/tangem-demo/src/main/res/values/dimens.xml b/tangem-demo/src/main/res/values/dimens.xml
index 544e3be648..4dd3305097 100644
--- a/tangem-demo/src/main/res/values/dimens.xml
+++ b/tangem-demo/src/main/res/values/dimens.xml
@@ -1,6 +1,10 @@
- 1dp
+ 1dp
+
+ 8dp
+ 16dp
+ 32dp
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/values/dimens_item_widget.xml b/tangem-demo/src/main/res/values/dimens_item_widget.xml
new file mode 100644
index 0000000000..63a37b0ac0
--- /dev/null
+++ b/tangem-demo/src/main/res/values/dimens_item_widget.xml
@@ -0,0 +1,12 @@
+
+
+
+ 86dp
+ 60dp
+
+ 16sp
+ 20sp
+ 12sp
+ 13sp
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/values/strings.xml b/tangem-demo/src/main/res/values/strings.xml
index 821087e07e..3d2be7b86b 100644
--- a/tangem-demo/src/main/res/values/strings.xml
+++ b/tangem-demo/src/main/res/values/strings.xml
@@ -1,3 +1,15 @@
- TangemTest
+ Tangem Demo
+
+ @string/app_name
+ "Current card ID: "
+
+ unknown
+ Docs
+
+ Your card hasn\'t been personalized yet. You need to run personalize command first.
+
+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+
+
diff --git a/tangem-demo/src/main/res/values/strings_action.xml b/tangem-demo/src/main/res/values/strings_action.xml
new file mode 100644
index 0000000000..ed962e51fb
--- /dev/null
+++ b/tangem-demo/src/main/res/values/strings_action.xml
@@ -0,0 +1,31 @@
+
+
+
+ Read Card
+ Sign
+ Personalize
+ Depersonalize
+ Create wallet
+ Purge wallet
+ Read issuer data
+ Write issuer data
+ Read issuer extra data
+ Write issuer extra data
+ Read user data
+ Write user data
+
+ This command returns all data about the card and the wallet, including unique card number (CID) that has to be submitted while calling all other commands
+ Depending on Signing_Method parameter defined during personalization, this command signs data using Wallet_PrivateKey
+ Personalization is an initialization procedure, required before start using a card. During this procedure all card setting are performed. During this procedure all data exchange are encrypted because it\'s usually done at the third-party factory.
+ This command reset card to initial state - erase all data, writed during previous personalization and previous usage.
+ \nCommand available on SDK cards only
+ SomeDescription
+ SomeDescription
+ SomeDescription
+ SomeDescription
+ SomeDescription
+ SomeDescription
+ SomeDescription
+ SomeDescription
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/values/strings_personalization.xml b/tangem-demo/src/main/res/values/strings_personalization.xml
new file mode 100644
index 0000000000..365ba71b3b
--- /dev/null
+++ b/tangem-demo/src/main/res/values/strings_personalization.xml
@@ -0,0 +1,160 @@
+
+
+
+ Personalize
+ Depersonalize
+ Card number
+ Common
+ Signing method
+ Sign hash external properties
+ Denomination
+ Token
+ Product mask
+ Settings mask
+ Settings mask protocol encryption
+ Settings mask ndef
+ Pins
+
+ Series
+ Number
+ Curve
+ Blockchain
+ Custom blockchain
+ Max signatures
+ Create wallet
+ Sign TX hashes (0)
+ Sign raw TX (1)
+ Sign validated TX hashes (2)
+ Sign validated raw TX (3)
+ Sign validated TX hashes with issuer data (4)
+ Sign validated raw TX with issuer data (5)
+ Sign hash external (6)
+ Pin-less floor limit
+ Cr(ypto)Ex(tract) key
+ Require terminal Cert signature
+ Require terminal TX signature
+ Check PIN3 on card
+ Write on personalize
+ Denomination
+ It\'s token
+ Symbol
+ Contract address
+ Decimal
+ Note
+ Tag
+ ID card
+ Is reusable
+ Need activation
+ Forbid purge
+ Allow select blockchain
+ Use block
+ One session – one command
+ Use CVC
+ Allow swap PIN
+ Allow swap PIN2
+ Forbid default PIN
+ Smart Security Delay
+ Protect issuer data against replay
+ Security delay if validated
+ Skip PIN2 and CVC if validated
+ Skip security delay on linked terminal
+ Restrict overwrite extra issuer data
+ Allow unencrypted
+ Allow \'FAST\' encryption
+ Use NDEF
+ Dynamic NDEF
+ Disable precomputed NDEF
+ AAR
+ Custom AAR package name
+ PIN
+ PIN2
+ PIN3
+ CVC
+ Pause before PIN2
+
+ Unique Tangem card ID number. It will be generated automatically with Series and Number.
+
+ Depending on these parameters, SIGN command signs following data using Wallet_PrivateKey: array of transaction hashes, single raw transaction. In case the card issuer supports 2FA, the card will firstly verify issuer’s signature of transaction hash before signing transaction hash using Wallet_PrivateKey. In case the card issuer requires the card to store additional data (e.g. wallet balance, SPV proof, timestamps, etc. – see details in Security section), COS will verify issuer’s signature of hash of concatenated transaction hashes and Issuer_Data hash, and issuer’s signature of Issuer_Data hash, and then proceed to signing transaction hash using Wallet_PrivateKey.
+
+
+ These fields can be used by the host application to provide additional infromation about tokens stored within the wallet of the card.
+ These fields can be used by the host application to provide additional infromation about product type.
+
+ Card Operation System supports three options of communication
+
+
+
+
+
+ Elliptic curve used for all wallet key operations. Supported curves: ‘secp256k1’, ‘ed25519’.
+ Name of the blockchain. It\'s just a textfield in the card. It\'s needed for host app to tell what blockchain should it use.
+ You can use this field, to create something special
+ Total number of signatures allowed for the wallet when the card was personalized
+ Off - don’t create wallet (user must be create wallet by himself with App)
+ \nOn - create wallet (after personalization wallet alredy will be created and card ready to use)
+ Allow card to sign hashed transaction
+ Allow card to sign raw transaction. The raw transaction will be hashed by a hash function before signing.
+ Allow card to sign hashed transaction, that was signed by issuer before
+ Allow card to sign raw transaction, that was signed by issuer before
+ Allow card to sign hashed transaction, that was signed by issuer before, and update issuer data at the same time
+ Allow card to sign raw transaction, that was signed by issuer before, and update issuer data at the same time
+ Allow card to sign POS transactions
+
+ Unique card secret key (shared with acquirers server)
+
+
+
+
+
+
+ Symbol of the token
+ Token\'s Smart contract address
+ Number of decimals in token value
+ Self-custodial wallet or note
+ Anti-counterfeit tag
+ Personal ID card
+ Defines what happens when user calls PURGE_WALLET command:
+ \nOff - Card will switch to Purged state
+ \nOn - Card will switch to Empty state and let create a new wallet again
+
+
+ If this option is turned on, card will reject PURGE_WALLET command
+ If this option is turned on, wallet elliptic curve and blockchain information can be changed on CREATE_WALLET command
+
+ Card will execute only one command during one communication session, thus requiring user to physically take the card away from the host after each action (all commands except for READ_CARD)
+ All commands requiring PIN2 will also require additional CVC code printed on the card
+ Is user allowed to change PIN1 with SET_PIN command
+ Is user allowed to change PIN2 with SET_PIN command
+ SET_PIN commands will not change PIN1 to default value (‘000000’)
+ Security delay Pause_Before_PIN2 will not be applied if PIN2 is not default
+ Off – No replay protection on write issuer data
+ \nOn – Enable replay protection on write issuer data (card will require additional Issuer_Data_Counter incremented on each write)
+
+ Off – Enforce security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5)
+ \nOn – Skip security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5)
+
+ Off – Require and check PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5)
+ \nOn – Skip checking PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5)
+
+ App can optionally generate ECDSA key pair Terminal_PrivateKey / Terminal_PublicKey. And then submit Terminal_PublicKey to the card in any SIGN command. Once SIGN is successfully executed by COS, including PIN2 verification and/or completion of security delay, the submitted Terminal_PublicKey key is stored by COS. After that, the App instance is deemed trusted by COS and COS will allow skipping security delay for subsequent SIGN operations thus improving convenience without sacrificing security.
+ Prohibit overwriting Issuer_Extra_Data
+ Allow plain data packets with non-encrypted Payload:
+ \nThis option is not recommended for large-scale commercial projects, because it potentially exposes the card to eavesdropping and replay attacks.
+ Allow Fast symmetric encryption with mutual challenges
+ Whether the card should emulate NDEF. In default configuration, two NDEF records are loaded during personalization: (1) Tangem web site address, (2) name of Android App package in Google Play Store.
+ Tangem card supports a special wallet validation mechanism for smartphones that can only read NDEF tags
+ \nOff – Disable dynamic generation of NDEF for iOS
+ \nOn – Enable dynamic NDEF for iOS
+
+ Off – Enable precomputed dynamic NDEF to work around iPhone 7+ NFC bug
+ \nOn – Disable precomputed dynamic NDEF
+
+ Android Application Record. Android Application ID, Package name of Android apllication. Android OS will open this app or send user to Google Play page if it\'s not installed. If this field is empty, Android OS will try to open URI from URI field.
+
+ PIN1 code to access the card. Default unhashed value: ‘000000’.
+ PIN2 code to confirm transaction signatures and swap PIN operations. Default unhashed value: ‘000’.
+ PIN3 code to confirm POS transaction with amount that exceed pin-less floor limit.
+ 3-digit code, that can be printed on the card
+ Delay in seconds before COS executes commands protected by PIN2
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/values/strings_scan.xml b/tangem-demo/src/main/res/values/strings_scan.xml
new file mode 100644
index 0000000000..33703cac79
--- /dev/null
+++ b/tangem-demo/src/main/res/values/strings_scan.xml
@@ -0,0 +1,6 @@
+
+
+
+ To read the card, press the button at the right bottom corner of the screen
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/values/strings_tlv.xml b/tangem-demo/src/main/res/values/strings_tlv.xml
new file mode 100644
index 0000000000..fe18360367
--- /dev/null
+++ b/tangem-demo/src/main/res/values/strings_tlv.xml
@@ -0,0 +1,10 @@
+
+
+
+ Card Id (CID)
+ Hash to sign
+
+ Unique Tangem card ID number
+ Hashed transaction you want to be signed
+
+
\ No newline at end of file
diff --git a/tangem-demo/src/main/res/values/styles.xml b/tangem-demo/src/main/res/values/styles.xml
index 5885930df6..c4f78e90bd 100644
--- a/tangem-demo/src/main/res/values/styles.xml
+++ b/tangem-demo/src/main/res/values/styles.xml
@@ -1,11 +1,23 @@
-
+
+
+
+
diff --git a/tangem-sdk-old/src/main/res/drawable/ic_logo_binance.png b/tangem-sdk-old/src/main/res/drawable/ic_logo_binance.png
new file mode 100644
index 0000000000..18bb591f33
Binary files /dev/null and b/tangem-sdk-old/src/main/res/drawable/ic_logo_binance.png differ
diff --git a/tangem-sdk-old/src/main/res/drawable/ic_logo_litecoin.png b/tangem-sdk-old/src/main/res/drawable/ic_logo_litecoin.png
new file mode 100644
index 0000000000..e188b785cd
Binary files /dev/null and b/tangem-sdk-old/src/main/res/drawable/ic_logo_litecoin.png differ
diff --git a/tangem-sdk-old/src/main/res/drawable/ic_logo_stellar.png b/tangem-sdk-old/src/main/res/drawable/ic_logo_stellar.png
index b8fbbd75b4..e05a6acd37 100644
Binary files a/tangem-sdk-old/src/main/res/drawable/ic_logo_stellar.png and b/tangem-sdk-old/src/main/res/drawable/ic_logo_stellar.png differ
diff --git a/tangem-sdk-old/src/main/res/drawable/ic_logo_tezos.png b/tangem-sdk-old/src/main/res/drawable/ic_logo_tezos.png
new file mode 100644
index 0000000000..3c7ed3f503
Binary files /dev/null and b/tangem-sdk-old/src/main/res/drawable/ic_logo_tezos.png differ
diff --git a/tangem-sdk-old/src/main/res/drawable/ic_logo_xrp.png b/tangem-sdk-old/src/main/res/drawable/ic_logo_xrp.png
new file mode 100644
index 0000000000..fe84014f47
Binary files /dev/null and b/tangem-sdk-old/src/main/res/drawable/ic_logo_xrp.png differ