Updated on 2026-08-14
This commit is contained in:
commit
433aa15a6a
188 changed files with 5507 additions and 1517 deletions
541
.idea/navEditor.xml
generated
541
.idea/navEditor.xml
generated
|
|
@ -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>
|
||||
3
Gemfile
3
Gemfile
|
|
@ -1,3 +1,6 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
|
||||
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
|
||||
eval_gemfile(plugins_path) if File.exist?(plugins_path)
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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, ")
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
39
app/src/main/java/com/tangem/util/Analytics.kt
Normal file
39
app/src/main/java/com/tangem/util/Analytics.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(), "", "");
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(), "", "");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,6 @@ public class XrpData extends CoinData {
|
|||
}
|
||||
|
||||
public boolean hasUnconfirmed() {
|
||||
return !balanceConfirmed.equals(balanceUnconfirmed);
|
||||
return hasBalanceInfo() && !balanceConfirmed.equals(balanceUnconfirmed);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ext.versions = [
|
||||
kotlin : '1.3.70',
|
||||
kotlin : '1.3.71',
|
||||
build_gradle: '3.6.1',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
5
fastlane/Pluginfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Autogenerated by fastlane
|
||||
#
|
||||
# Ensure this file is checked in to source control!
|
||||
|
||||
gem 'fastlane-plugin-firebase_app_distribution'
|
||||
|
|
@ -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
|
||||
|
||||
----
|
||||
|
||||
|
|
|
|||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -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
22
gradlew
vendored
|
|
@ -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
18
gradlew.bat
vendored
|
|
@ -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 |
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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> {
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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?
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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>)
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue