Updated on 2026-08-14

This commit is contained in:
Tangem 2020-04-06 02:02:40 +03:00
commit 433aa15a6a
188 changed files with 5507 additions and 1517 deletions

541
.idea/navEditor.xml generated
View file

@ -1,541 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="navEditor-manualLayoutAlgorithm2">
<option name="myPositions">
<map>
<entry key="main_graph.xml">
<value>
<LayoutPositions>
<option name="myPositions">
<map>
<entry key="cameraFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-397" />
<option name="y" value="645" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="confirmTransactionFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="744" />
<option name="y" value="2390" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_confirmTransactionFragment_to_pinRequestFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_confirmTransactionFragment_to_signTransactionFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="createNewWalletFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="500" />
<option name="y" value="105" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="emptyWalletFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="256" />
<option name="y" value="105" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_emptyWalletFragment_to_createNewWalletFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_emptyWalletFragment_to_pinRequestFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_emptyWalletFragment_to_verifyCard">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="idFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-272" />
<option name="y" value="1404" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_idFragment_to_issueNewIdFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_idFragment_to_verifyCard">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="issueNewIdFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-394" />
<option name="y" value="1004" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_issueNewIdFragment_to_cameraFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_issueNewIdFragment_to_validateIdFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="loadedWalletFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="256" />
<option name="y" value="1279" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_loadedWalletFragment_to_pinRequestFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_loadedWalletFragment_to_pinSwapFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_loadedWalletFragment_to_prepareCryptonitWithdrawalFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_loadedWalletFragment_to_prepareKrakenWithdrawalFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_loadedWalletFragment_to_prepareTransactionFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_loadedWalletFragment_to_verifyCard">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="logoFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="256" />
<option name="y" value="2390" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_logoFragment_to_main">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_logoFragment_to_prepareTransactionFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="main">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="12" />
<option name="y" value="1279" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_main_to_emptyWalletFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_main_to_idFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_main_to_loadedWalletFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_main_to_logoFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_main_to_pinRequestFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_main_to_pinSaveFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_main_to_settingsFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_main_to_tagFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="pinRequestFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="988" />
<option name="y" value="12" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="pinSaveFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="256" />
<option name="y" value="461" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="pinSwapFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="744" />
<option name="y" value="1039" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="prepareCryptonitOtherApiWithdrawalFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="500" />
<option name="y" value="1992" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_prepareCryptonitOtherApiWithdrawalFragment_to_qrScanFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="prepareCryptonitWithdrawalFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="500" />
<option name="y" value="1618" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="prepareKrakenWithdrawalFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="500" />
<option name="y" value="1261" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="prepareTransactionFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="500" />
<option name="y" value="2348" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_prepareTransactionFragment_to_confirmTransactionFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_prepareTransactionFragment_to_qrScanFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="purgeFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="744" />
<option name="y" value="682" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_purgeFragment_to_mainFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="qrScanFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="744" />
<option name="y" value="2034" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="sendTransactionFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="1232" />
<option name="y" value="2433" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="settingsFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="256" />
<option name="y" value="923" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="signTransactionFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="988" />
<option name="y" value="2433" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_signTransactionFragment_to_sendTransactionFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="tagFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="256" />
<option name="y" value="1635" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="validateIdFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-628" />
<option name="y" value="1001" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_validateIdFragment_to_writeIdFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="verifyCard">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="500" />
<option name="y" value="860" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_verifyCard_to_pinRequestFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_verifyCard_to_pinSwapFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_verifyCard_to_purgeFragment">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="writeIdFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-661" />
<option name="y" value="701" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</component>
</project>

View file

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

View file

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

View file

@ -7,10 +7,6 @@
<application
tools:ignore="GoogleAppIndexingWarning">
<meta-data
android:name="io.fabric.ApiKey"
android:value="${fabric_api_key}" />
<provider
android:name="com.tangem.data.LogFileProvider"
android:authorities="@string/log_file_provider_authorities"

View file

@ -2,7 +2,8 @@ package com.tangem
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
import com.crashlytics.android.Crashlytics
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.tangem.data.dp.PrefsManager
@ -15,8 +16,8 @@ import com.tangem.server_android.data.LocalStorage
import com.tangem.tangem_card.data.Issuer
import com.tangem.tangem_sdk.android.data.Firmwares
import com.tangem.tangem_sdk.android.data.PINStorage
import com.tangem.util.Analytics
import com.tangem.wallet.BuildConfig
import io.fabric.sdk.android.Fabric
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
@ -77,8 +78,10 @@ class App : Application() {
}
}
)
if (BuildConfig.CRASHLYTICS) Fabric.with(this, Crashlytics())
}
FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(Analytics.isEnabled())
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(Analytics.isEnabled())
}
private fun buildToastHelperComponent(): ToastHelperComponent {

View file

@ -11,18 +11,18 @@ public enum Blockchain {
BitcoinTestNet("BTC/test", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin_testnet, "Bitcoin Testnet"),
BitcoinDual("BTC/dual", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Bitcoin"),
Ethereum("ETH", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"),
EthereumId("ETH/ID", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum ID"),
EthereumId("ETH/ID", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"),
EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ic_logo_ethereum_testnet, "Ethereum Testnet"),
Token("Token", "ETH", 1.0, R.drawable.ic_logo_bat_token, "Ethereum"),
NftToken("NftToken", "", 1.0, R.drawable.tangem2, "Ethereum"),
Token("Token", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"),
NftToken("NftToken", "", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"),
BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash"),
Litecoin("LTC", "LTC", 100000000.0, R.drawable.tangem2, "Litecoin"),
Litecoin("LTC", "LTC", 100000000.0, R.drawable.ic_logo_litecoin, "Litecoin"),
Rootstock("RSK", "RBTC", 1.0, R.drawable.tangem2, "RSK"),
RootstockToken("RskToken", "RBTC", 1.0, R.drawable.tangem2, "RSK"),
Cardano("CARDANO", "ADA", 1000000.0, R.drawable.tangem2, "Cardano"),
Ripple ("XRP", "XRP", 1000000.0, R.drawable.tangem2, "XRP"),
Binance("BINANCE", "BNB", 100000000.0, R.drawable.tangem2, "Binance"),
BinanceTestNet("BINANCE/test", "BNB", 100000000.0, R.drawable.tangem2, "Binance Testnet"),
Ripple ("XRP", "XRP", 1000000.0, R.drawable.ic_logo_xrp, "XRP"),
Binance("BINANCE", "BNB", 100000000.0, R.drawable.ic_logo_binance, "Binance"),
BinanceTestNet("BINANCE/test", "BNB", 100000000.0, R.drawable.ic_logo_binance, "Binance Testnet"),
Matic("MATIC", "MTX", 1.0, R.drawable.tangem2, "Matic"),
MaticTestNet("MATIC/test", "MTX", 1.0, R.drawable.tangem2, "Matic Testnet"),
Stellar("XLM", "XLM", 10000000.0, R.drawable.ic_logo_stellar, "Stellar"),
@ -31,7 +31,7 @@ public enum Blockchain {
StellarTag("XLM-Tag", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar"),
Eos("EOS", "EOS", 10000.0, R.drawable.tangem2, "EOS"),
Ducatus("DUC", "DUC", 100000000.0, R.drawable.tangem2, "Ducatus"),
Tezos("TEZOS", "XTZ", 10000000.0, R.drawable.tangem2, "Tezos");
Tezos("TEZOS", "XTZ", 10000000.0, R.drawable.ic_logo_tezos, "Tezos");
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
mID = ID;

View file

@ -3,6 +3,7 @@ package com.tangem.data.dp
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import com.orhanobut.hawk.Hawk
import com.tangem.Constant
import com.tangem.tangem_card.reader.CardCrypto
@ -10,6 +11,7 @@ import com.tangem.tangem_card.reader.CardCrypto
class PrefsManager {
companion object {
const val PREF_NAME = "tangem_access"
@SuppressLint("StaticFieldLeak")
private var instance: PrefsManager? = null
@ -41,6 +43,11 @@ class PrefsManager {
Hawk.delete(Constant.PREF_LAST_WALLET_ADDRESS)
}
fun getSettingsBoolean(key: Int, default: Boolean): Boolean {
return PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(context.getString(key), default)
}
fun appendCid(newCid: String) {
val prevCids: String = Hawk.get("CID_Key", "")
if (!prevCids.contains(newCid)) Hawk.put("CID_Key", "$prevCids$newCid, ")

View file

@ -45,7 +45,7 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
private fun navigateSafelyToMainFragment() {
try {
findNavController(R.id.nav_host_fragment).navigate(R.id.main)
findNavController(R.id.nav_host_fragment).popBackStack()
} catch (e: IllegalArgumentException) {
Log.w(this::class.java.simpleName, e.message)
} catch (e: IllegalStateException) {

View file

@ -20,6 +20,8 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.lifecycle.ViewModelProviders
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.App
import com.tangem.Constant
import com.tangem.data.Blockchain
@ -46,9 +48,7 @@ import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
import com.tangem.ui.dialog.WaitSecurityDelayDialog
import com.tangem.ui.fragment.pin.PinRequestFragment
import com.tangem.ui.navigation.NavigationResultListener
import com.tangem.util.CommonUtil
import com.tangem.util.LOG
import com.tangem.util.UtilHelper
import com.tangem.util.*
import com.tangem.util.extensions.colorFrom
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.CoinEngineFactory
@ -117,6 +117,9 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
setCardStoreLink()
FirebaseAnalytics.getInstance(requireActivity())
.logEvent(AnalyticsEvent.READY_TO_SCAN.event, bundleOf())
}
private fun setCardStoreLink() {
@ -270,6 +273,9 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
val ctx = TangemContext(card)
FirebaseAnalytics.getInstance(requireActivity())
.logEvent(AnalyticsEvent.CARD_IS_SCANNED.event, Analytics.setCardData(ctx))
when {
card.status == TangemCard.Status.Loaded -> 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() }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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()+ "<br><small><small> " + getBlockchain().getOfficialName() + " smart contract token</small></small>";
return card.getTokenSymbol() + "<br><small><small> " + getBlockchain().getOfficialName() + " smart contract token</small></small>";
}
if (blockchain == Blockchain.NftToken) {
return card.getTokenSymbol().substring(4) + "<br><small><small> " + getBlockchain().getOfficialName() + " non-fungible token</small></small>";
@ -67,7 +69,7 @@ public class TangemContext {
return card.getTokenSymbol() + "<br><small><small> " + getBlockchain().getOfficialName() + " asset</small></small>";
}
if (blockchain == Blockchain.StellarTag) {
return "TANGEM TAG<br><small><small> "+ getBlockchain().getOfficialName() + " non-fungible token </small></small>";
return "TANGEM TAG<br><small><small> " + getBlockchain().getOfficialName() + " non-fungible token </small></small>";
}
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;
}

View file

@ -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(), "", "");

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -122,6 +122,6 @@ public class XrpData extends CoinData {
}
public boolean hasUnconfirmed() {
return !balanceConfirmed.equals(balanceUnconfirmed);
return hasBalanceInfo() && !balanceConfirmed.equals(balanceUnconfirmed);
}
}

View file

@ -159,32 +159,6 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="@dimen/verify_card_left_col_width"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:fontFamily="@font/maax"
android:text="@string/details_is_card_reusable"
android:textSize="@dimen/text_size_1_small" />
<TextView
android:id="@+id/tvReusable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/maax"
android:textColor="@color/primary_dark"
android:textSize="@dimen/text_size_1_small"
android:textStyle="normal|bold"
tools:text="Reusable" />
</LinearLayout>
<LinearLayout
android:id="@+id/llLinkedCard"
android:layout_width="match_parent"
@ -288,32 +262,6 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="@dimen/verify_card_left_col_width"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:fontFamily="@font/maax"
android:text="@string/details_features"
android:textSize="@dimen/text_size_1_small" />
<TextView
android:id="@+id/tvFeatures"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/maax"
android:textColor="@color/primary_dark"
android:textSize="@dimen/text_size_2_small"
android:textStyle="normal|bold"
tools:text="Allows change PIN1 and PIN2 \n Dynamic NDEF" />
</LinearLayout>
<View
android:id="@+id/view2"
android:layout_width="match_parent"
@ -584,34 +532,13 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<ImageView
android:id="@+id/imgPIN"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="false"
android:layout_alignParentEnd="false"
android:layout_alignParentBottom="true"
android:layout_toStartOf="@+id/imgPIN2orSecurityDelay"
android:src="@drawable/unlock_pin1" />
<ImageView
android:id="@+id/imgPIN2orSecurityDelay"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="false"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:src="@drawable/unlock_pin2" />
<ImageView
android:id="@+id/imgDeveloperVersion"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="4dp"
android:layout_marginStart="10dp"
android:src="@drawable/ic_developer_version" />
</RelativeLayout>

View file

@ -28,6 +28,7 @@
<string name="settings_title">Settings</string>
<string name="settings_category_сommon">Common</string>
<string name="settings_option_manual_editing_fee">Manual editing Fee</string>
<string name="settings_option_analytics">Share anonymous data with developers</string>
<string name="settings_option_encryption_modes">Encryption modes</string>
<string-array name="settings_encryption_modes_entries">
<item>None</item>
@ -187,6 +188,9 @@
<string name="prepare_transaction_error_incorrect_destination">Incorrect destination wallet address</string>
<string name="prepare_transaction_error_same_address">Destination wallet address equal source address</string>
<string name="prepare_transaction_error_amount_empty">Amount is empty</string>
<string name="prepare_transaction_warning_last_signature">Only one signature left</string>
<string name="prepare_transaction_warning_send_full_amount">You need to send all available funds in this transaction</string>
<!-- ConfirmTransaction -->
<string name="confirm_transaction_hint_target_address">target wallet address</string>

View file

@ -26,6 +26,7 @@
<string name="pref_category_common" translatable="false">category_common</string>
<string name="pref_manual_editing_fee" translatable="false">manual_editing_fee</string>
<string name="pref_analytics" translatable="false">analytics</string>
<string name="pref_select_nodes" translatable="false">select_nodes</string>
<string name="pref_encryption_modes" translatable="false">encryption_modes</string>

View file

@ -4,11 +4,16 @@
android:key="@string/pref_category_common"
android:title="@string/settings_category_сommon">
<CheckBoxPreference
<SwitchPreference
android:defaultValue="false"
android:key="@string/pref_manual_editing_fee"
android:title="@string/settings_option_manual_editing_fee" />
<SwitchPreference
android:defaultValue="true"
android:key="@string/pref_analytics"
android:title="@string/settings_option_analytics" />
<Preference
android:key="@string/pref_select_nodes"
android:title="@string/settings_option_select_nodes" />

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
ext.versions = [
kotlin : '1.3.70',
kotlin : '1.3.71',
build_gradle: '3.6.1',
]

View file

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

5
fastlane/Pluginfile Normal file
View file

@ -0,0 +1,5 @@
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
gem 'fastlane-plugin-firebase_app_distribution'

View file

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

Binary file not shown.

View file

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

22
gradlew vendored
View file

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

18
gradlew.bat vendored
View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Before After
Before After

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<NdefRecord>
) {
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
}

View file

@ -20,8 +20,7 @@ class DepersonalizeCommand : CommandSerializer<DepersonalizeResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
return CommandApdu(
Instruction.Depersonalize, byteArrayOf(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
Instruction.Depersonalize, byteArrayOf()
)
}

View file

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

View file

@ -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<Card>() {
class PersonalizeCommand(
private val config: CardConfig,
private val issuer: Issuer, private val manufacturer: Manufacturer,
private val acquirer: Acquirer? = null
) : CommandSerializer<Card>() {
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<NdefRecord>): ByteArray {
return NdefEncoder(ndefRecords, config.useDynamicNdef).encode()
}
companion object {
val devPersonalizationKey = "1234".calculateSha256().copyOf(32)
}

View file

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

View file

@ -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<NdefRecord>
) {
companion object
}

View file

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

View file

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

View file

@ -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)
class KeyPair(val publicKey: ByteArray, val privateKey: ByteArray) {
constructor(privateKey: ByteArray, curve: EllipticCurve = EllipticCurve.Secp256k1) :
this(generatePublicKey(privateKey, curve), privateKey)
}

View file

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

View file

@ -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)
}
/**

View file

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

View file

@ -8,8 +8,8 @@
<uses-permission android:name="android.permission.NFC" />
<application
android:name=".AppTangemDemo"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@ -17,15 +17,15 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<activity android:name="._main.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="www.tangem.com"
android:scheme="http" />

View file

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

View file

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

View file

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

View file

@ -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<T> : MutableLiveData<T>() {
private val mPending: AtomicBoolean = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
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"
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.tangemtest._arch.structure
/**
[REDACTED_AUTHOR]
*/
interface DataHolder<D> {
var viewModel: D
}
typealias Payload = MutableMap<String, Any?>
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<I> {
fun setItems(list: MutableList<I>)
fun getItems(): MutableList<I>
fun addItem(item: I)
fun removeItem(item: I)
fun clear()
}

View file

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

View file

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

View file

@ -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<Item>
}
abstract class BaseBlock : Block {
override var parent: Item? = null
override val itemList: MutableList<Item> = mutableListOf()
override val payload: MutableMap<String, Any?> = mutableMapOf()
}
open class ListItemBlock(
override val id: Id
) : BaseBlock(), ItemListHolder<Item> {
override fun setItems(list: MutableList<Item>) {
ILog.d(this, "setItems into: $id, count: ${list.size}")
itemList.clear()
itemList.addAll(list)
itemList.forEach { it.parent = this }
}
override fun getItems(): MutableList<Item> {
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()
}
}

View file

@ -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<D>(
override var viewModel: ItemViewModel<D>
) : Item, DataHolder<ItemViewModel<D>> {
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)
}
}

View file

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

View file

@ -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> = (V?) -> Unit
typealias SafeValueChange<V> = (V) -> Unit
interface ItemViewModel<D : Any?> : PayloadHolder {
val viewState: ViewState
var data: D?
var defaultData: D?
var onDataUpdated: ValueChange<D>?
fun updateDataByView(data: D?)
}
open class BaseItemViewModel<D> : ItemViewModel<D> {
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<D>? = 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<Int>? = null
}

View file

@ -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<Any>(AnyViewModel(value))
class TextItem(override val id: Id, value: String? = null) : BaseItem<String>(StringViewModel(value))
class EditTextItem(override val id: Id, value: String? = null) : BaseItem<String>(StringViewModel(value))
class NumberItem(override val id: Id, value: Number? = null) : BaseItem<Number>(NumberViewModel(value))
class BoolItem(override val id: Id, value: Boolean? = null) : BaseItem<Boolean>(BoolViewModel(value))
class ListItem(override val id: Id, value: List<KeyValue>, selectedValue: Any?)
: BaseItem<ListValueWrapper>(ListViewModel(ListValueWrapper(selectedValue, value))
)
inline fun<reified T> BaseItem<T>.getData(): T? = viewModel.data

View file

@ -0,0 +1,29 @@
package com.tangem.tangemtest._arch.structure.impl
import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel
/**
[REDACTED_AUTHOR]
*/
open class TransitiveViewModel<D>(value: D?) : BaseItemViewModel<D>() {
init {
defaultData = value
}
}
class AnyViewModel(value: Any? = null) : TransitiveViewModel<Any>(value)
class StringViewModel(value: String? = null) : TransitiveViewModel<String>(value)
class NumberViewModel(value: Number? = null) : TransitiveViewModel<Number>(value)
class BoolViewModel(value: Boolean? = null) : TransitiveViewModel<Boolean>(value)
class ListViewModel(value: ListValueWrapper? = null) : TransitiveViewModel<ListValueWrapper>(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<KeyValue>)

View file

@ -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<MainViewModel>()
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.a_main)
val toolbar = findViewById<Toolbar>(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)
}
}

View file

@ -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<Boolean>(false)
fun switchToggled(state: Boolean) {
ldDescriptionSwitch.postValue(state)
}
}

View file

@ -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<ActionType> {
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
)
}
}

View file

@ -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<Int>
) : RvBaseAdapter<RvActionsVH, ActionType>() {
override fun onBindViewHolder(holder: RvActionsVH, position: Int) {
holder.bindData(itemList[position])
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RvActionsVH {
val view = parent.inflate<View>(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<Int>?
) : RvBaseVH<ActionType>(itemView) {
private val tvAction = itemView.findViewById<TextView>(R.id.tv_title)
private val containerDescription = itemView.findViewById<ViewGroup>(R.id.container_description)
private val tvDescription = itemView.findViewById<TextView>(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)
}

View file

@ -0,0 +1,8 @@
package com.tangem.tangemtest.commons
/**
[REDACTED_AUTHOR]
*/
fun <A, B> performAction(a: A?, b: B?, action: (A, B) -> Unit) {
if (a != null && b != null) action(a, b)
}

View file

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

View file

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

View file

@ -0,0 +1,16 @@
package com.tangem.tangemtest.extensions
/**
[REDACTED_AUTHOR]
*/
fun <T> List<T>.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
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<ActionType, ItemsManager>() {
companion object {
fun createFactory(): ParamsManagerFactory {
return ParamsManagerFactory().apply {
register(ActionType.Scan, ScanItemsManager())
register(ActionType.Sign, SignItemsManager())
register(ActionType.Personalize, PersonalizeItemsManager())
register(ActionType.Depersonalize, DepersonalizeItemsManager())
}
}
}
}

View file

@ -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<Item>
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<Item>
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<Item>.findItem(id: Id): Item? {
return firstOrNull { it.id == id }
}
fun List<Item>.findDataItem(id: Id): BaseItem<Any?>? {
val item = firstOrNull { it.id == id }
return if (item is BaseItem<*>) item as BaseItem<Any?>
else null
}

View file

@ -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<Item> by lazy { createItemsList() }
override val consequence: ItemsChangeConsequence? = null
override val payload: MutableMap<String, Any?> = 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<Item> = 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<Item>
}

View file

@ -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<Item> {
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))
}
}

View file

@ -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<Item> {
return listOf(EditTextItem(TlvId.CardId))
}
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
}
}

View file

@ -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<Item> = listOf()
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
}
}

View file

@ -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<Item> {
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))
}
}

View file

@ -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<Item>): List<Item>
}

View file

@ -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<Item>): List<Item> {
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
}

View file

@ -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<Item>): List<Item>?
}
class SignScanConsequence: ItemsChangeConsequence {
override fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List<Item>): List<Item>? {
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()
}
}

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