Updated on 2026-08-14

This commit is contained in:
Tangem 2020-04-16 20:20:33 +03:00
commit 4cf313b915
98 changed files with 1886 additions and 585 deletions

View file

@ -158,4 +158,7 @@ dependencies {
//dependencies for flow demo
implementation 'io.grpc:grpc-okhttp:1.28.0'
implementation 'io.grpc:grpc-stub:1.28.0'
//dependencies for TokenEmvEngine
implementation 'com.github.walleth.kethereum:extensions_kotlin:0.81.4'
}

View file

@ -6,7 +6,7 @@ import com.tangem.tangem_sdk.R;
* Created by dvol on 06.08.2017.
*/
public enum Blockchain {
Unknown("", "", 1.0, R.drawable.ic_logo_unknown, ""),
Unknown("", "", 1.0, R.drawable.ic_logo_unknown, "Unknown"),
Bitcoin("BTC", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Bitcoin"),
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"),
@ -32,7 +32,8 @@ public enum Blockchain {
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.ic_logo_tezos, "Tezos"),
FlowDemo("FLOW/demo", "", 1.0, R.drawable.tangem2, "Flow demo");
FlowDemo("FLOW/demo", "", 1.0, R.drawable.tangem2, "Flow demo"),
TokenEmv("TTW", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum");
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
mID = ID;
@ -67,7 +68,7 @@ public enum Blockchain {
for (Blockchain blockchain : values()) {
if (blockchain.getID().equals(id)) return blockchain;
}
return null;
return Blockchain.Unknown;
}
public static Blockchain fromCurrency(String currency) {

View file

@ -40,6 +40,14 @@ public class Server {
}
}
public static class ApiInfuraRopsten {
public static final String URL_INFURA_ROPSTEN = ServerURL.API_INFURA_ROPSTEN;
public static class Method {
public static final String MAIN = "v3/613a0b14833145968b1f656240c7d245";
}
}
public static class ApiSoChain {
public static final String URL = ServerURL.API_SOCHAIN_V2;

View file

@ -42,6 +42,8 @@ public class ServerApiInfura {
public ServerApiInfura(Blockchain blockchain) {
if (blockchain == Blockchain.EthereumTestNet) {
infuraApi = App.Companion.getNetworkComponent().getRetrofitInfuraTestnet().create(InfuraApi.class);
} else if (blockchain == Blockchain.TokenEmv) {
infuraApi = App.Companion.getNetworkComponent().getRetrofitInfuraRopsten().create(InfuraApi.class);
}
}

View file

@ -0,0 +1,53 @@
package com.tangem.data.network
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import com.tangem.data.network.model.TokenEmvGetTransferFeeAnswer
import com.tangem.data.network.model.TokenEmvGetTransferFeeBody
import com.tangem.data.network.model.TokenEmvTransferAnswer
import com.tangem.data.network.model.TokenEmvTransferBody
import com.tangem.tangem_card.util.Log
import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import java.util.concurrent.TimeUnit
class ServerApiTokenEmv {
private val TAG = ServerApiTokenEmv::class.java.simpleName
private val tangemServer = "https://emvsupport.appspot.com/"
private val tokenEmvApi = Retrofit.Builder()
.baseUrl(tangemServer)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //logging for testing
.client(OkHttpClient.Builder().addInterceptor(
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
).build())
.build()
.create(TokenEmvApi::class.java)
fun transfer(tokenEmvTransferBody: TokenEmvTransferBody, transferObserver: SingleObserver<TokenEmvTransferAnswer>) {
Log.i(TAG, "new transfer request")
tokenEmvApi.transfer(tokenEmvTransferBody)
.timeout(30, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(transferObserver)
}
fun getTransferFee(tokenEmvGetTransferFeeBody: TokenEmvGetTransferFeeBody, transferObserver: SingleObserver<TokenEmvGetTransferFeeAnswer>) {
Log.i(TAG, "new get transfer fee request")
tokenEmvApi.getTransferFee(tokenEmvGetTransferFeeBody)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(transferObserver)
}
}

View file

@ -5,6 +5,7 @@ class ServerURL {
static final String API_COINMARKETCAP = "https://pro-api.coinmarketcap.com/";
static final String API_INFURA = "https://mainnet.infura.io/";
static final String API_INFURA_TESTNET = "https://rinkeby.infura.io/";
static final String API_INFURA_ROPSTEN = "https://ropsten.infura.io/";
static final String API_SOCHAIN_V2 = "https://chain.so/";
static final String API_ESTIMATEFEE = "https://estimatefee.com/";
static final String API_UPDATE_VERSION = "https://raw.githubusercontent.com/";

View file

@ -0,0 +1,17 @@
package com.tangem.data.network
import com.tangem.data.network.model.TokenEmvGetTransferFeeAnswer
import com.tangem.data.network.model.TokenEmvGetTransferFeeBody
import com.tangem.data.network.model.TokenEmvTransferAnswer
import com.tangem.data.network.model.TokenEmvTransferBody
import io.reactivex.Completable
import io.reactivex.Single
import retrofit2.http.Body
import retrofit2.http.POST
interface TokenEmvApi {
@POST("./card/transfer")
fun transfer(@Body tokenEmvTransferBody: TokenEmvTransferBody): Single<TokenEmvTransferAnswer>
@POST("./card/transfer/fee")
fun getTransferFee(@Body tokenEmvGetTransferFeeBody: TokenEmvGetTransferFeeBody): Single<TokenEmvGetTransferFeeAnswer>
}

View file

@ -0,0 +1,36 @@
package com.tangem.data.network.model
import com.google.gson.annotations.SerializedName
data class TokenEmvTransferBody(
val CID: String,
val publicKey: String,
val amount: String,
val currency: String,
val recipient: String,
@SerializedName("fee_limit")
val feeLimit: String,
val sequence: Int,
val signature: String
)
data class TokenEmvTransferAnswer(
val error: String?,
val errorCode: Int?,
val success: Boolean?,
val tx_id: String?,
val blockchain_tx_id: String?
)
data class TokenEmvGetTransferFeeBody(
val CID: String,
val publicKey: String
)
data class TokenEmvGetTransferFeeAnswer(
val error: String?,
val errorCode: Int?,
val success: Boolean?,
val fee: String?,
val currency: String?
)

View file

@ -17,6 +17,9 @@ interface NetworkComponent {
@get:Named(Server.ApiInfuraTestnet.URL_INFURA_TESTNET)
val retrofitInfuraTestnet: Retrofit
@get:Named(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN)
val retrofitInfuraRopsten: Retrofit
@get:Named(Server.ApiMaticTesnet.URL_MATIC_TESTNET)
val retrofitMaticTesnet: Retrofit

View file

@ -44,6 +44,18 @@ internal class NetworkModule {
return builder.build()
}
@Singleton
@Provides
@Named(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN)
fun provideRetrofitInfuraRopsten(): Retrofit {
val builder = Retrofit.Builder()
.baseUrl(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN)
.addConverterFactory(GsonConverterFactory.create())
if (BuildConfig.DEBUG)
builder.client(createOkHttpClient())
return builder.build()
}
@Singleton
@Provides
@Named(Server.ApiRootstock.URL_ROOTSTOCK)

View file

@ -8,6 +8,7 @@ import android.nfc.Tag
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.findNavController
import com.scottyab.rootbeer.RootBeer
@ -16,6 +17,7 @@ import com.tangem.di.ToastHelper
import com.tangem.tangem_sdk.android.nfc.NfcLifecycleObserver
import com.tangem.tangem_sdk.android.reader.NfcManager
import com.tangem.ui.dialog.RootFoundDialog
import com.tangem.ui.fragment.MainFragment
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import javax.inject.Inject
@ -44,6 +46,8 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
}
private fun navigateSafelyToMainFragment() {
if (getActiveFragment() is MainFragment) return
try {
findNavController(R.id.nav_host_fragment).popBackStack()
} catch (e: IllegalArgumentException) {
@ -73,8 +77,7 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
}
override fun onTagDiscovered(tag: Tag) {
val activeFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
?.childFragmentManager?.primaryNavigationFragment
val activeFragment = getActiveFragment()
if (activeFragment is NfcAdapter.ReaderCallback) {
activeFragment.onTagDiscovered(tag)
} else {
@ -82,4 +85,8 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
}
}
private fun getActiveFragment(): Fragment? {
return supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
?.childFragmentManager?.primaryNavigationFragment
}
}

View file

@ -147,21 +147,23 @@ public class WaitSecurityDelayDialog extends DialogFragment {
}
private void setRemainingTimeout(final int msec) {
progressBar.post(() -> {
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
if (timer != null) {
// we get delay latency from card for first time - don't change progress by timer, only by card answer
progressBar.setMax(progress + msec);
timer.cancel();
timer = null;
} else {
int newProgress = progressBar.getMax() - msec;
if (newProgress > progress)
progressBar.setProgress(newProgress);
else
if (progressBar != null) {
progressBar.post(() -> {
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
if (timer != null) {
// we get delay latency from card for first time - don't change progress by timer, only by card answer
progressBar.setMax(progress + msec);
}
});
timer.cancel();
timer = null;
} else {
int newProgress = progressBar.getMax() - msec;
if (newProgress > progress)
progressBar.setProgress(newProgress);
else
progressBar.setMax(progress + msec);
}
});
}
}
}

View file

@ -20,6 +20,7 @@ import com.tangem.wallet.rsk.RskEngine
import com.tangem.wallet.rsk.RskTokenEngine
import com.tangem.wallet.token.TokenEngine
import com.tangem.wallet.tezos.TezosEngine
import com.tangem.wallet.tokenEmv.TokenEmvEngine
import com.tangem.wallet.xlm.XlmAssetEngine
import com.tangem.wallet.xlm.XlmEngine
import com.tangem.wallet.xlmTag.XlmTagEngine
@ -60,6 +61,7 @@ object CoinEngineFactory {
Blockchain.Tezos -> TezosEngine()
Blockchain.BitcoinDual -> BtcMultisigEngine()
Blockchain.FlowDemo -> FlowDemoEngine()
Blockchain.TokenEmv -> TokenEmvEngine()
else -> null
}
}
@ -109,6 +111,8 @@ object CoinEngineFactory {
BtcMultisigEngine(context)
else if (Blockchain.FlowDemo == context.blockchain)
FlowDemoEngine(context)
else if (Blockchain.TokenEmv == context.blockchain)
TokenEmvEngine(context)
else
return null
} catch (e: Exception) {

View file

@ -110,7 +110,7 @@ public class EthTransaction {
return kec.digest(plainMsg);
}
public int BruteRecoveryID2(ECDSASignatureETH sig, byte[] messageHash, byte[] thisKey) {
public static int BruteRecoveryID2(ECDSASignatureETH sig, byte[] messageHash, byte[] thisKey) {
Log.e("ETH_KZ", BTCUtils.toHex(thisKey));
int recId = -1;
for (int i = 0; i < 4; i++) {

View file

@ -8,31 +8,46 @@ import com.tangem.wallet.eth.EthData;
public class TokenData extends EthData {
private CoinEngine.InternalAmount balanceAlter = null;
// private Integer sequence = null;
@Override
public void clearInfo() {
super.clearInfo();
balanceAlter = null;
// sequence = null;
}
public CoinEngine.InternalAmount getBalanceAlterInInternalUnits() {
return balanceAlter;
}
public void setBalanceAlterInInternalUnits(CoinEngine.InternalAmount value) {
balanceAlter = value;
}
// public Integer getSequence() {
// return sequence;
// }
//
// public void setSequence(Integer sequence) {
// this.sequence = sequence;
// }
@Override
public void loadFromBundle(Bundle B) {
super.loadFromBundle(B);
if( B.containsKey("BalanceDecimalAlter" )) {
if (B.containsKey("BalanceDecimalAlter")) {
balanceAlter = new CoinEngine.InternalAmount(B.getString("BalanceDecimalAlter"), "wei");
}else{
balanceAlter=null;
} else {
balanceAlter = null;
}
// if (B.containsKey("Sequence")) {
// sequence = B.getInt("Sequence");
// } else {
// sequence = null;
// }
}
@Override
@ -40,9 +55,12 @@ public class TokenData extends EthData {
super.saveToBundle(B);
try {
if( balanceAlter!=null ) {
if (balanceAlter != null) {
B.putString("BalanceDecimalAlter", balanceAlter.toString());
}
// if (sequence != null) {
// B.putInt("Sequence", sequence);
// }
} catch (Exception e) {
Log.e("Can't save to bundle ", e.getMessage());
}

View file

@ -0,0 +1,349 @@
package com.tangem.wallet.tokenEmv
import android.net.Uri
import android.text.InputFilter
import android.util.Log
import com.google.gson.Gson
import com.tangem.data.Blockchain
import com.tangem.data.network.ServerApiInfura
import com.tangem.data.network.ServerApiTokenEmv
import com.tangem.data.network.model.*
import com.tangem.tangem_card.data.TangemCard
import com.tangem.tangem_card.reader.CardProtocol.TangemException
import com.tangem.tangem_card.reader.TLV
import com.tangem.tangem_card.reader.TLVList
import com.tangem.tangem_card.tasks.SignTask
import com.tangem.tangem_card.util.Util
import com.tangem.util.DecimalDigitsInputFilter
import com.tangem.wallet.EthTransaction
import com.tangem.wallet.Keccak256
import com.tangem.wallet.R
import com.tangem.wallet.TangemContext
import com.tangem.wallet.token.TokenEngine
import io.reactivex.observers.DisposableSingleObserver
import org.apache.commons.lang3.SerializationUtils
import org.kethereum.extensions.toBytesPadded
import java.math.BigInteger
class TokenEmvEngine : TokenEngine {
constructor() : super()
constructor(context: TangemContext) : super(context)
private val TAG = TokenEmvEngine::class.java.simpleName
private fun hasLinkedContract(): Boolean {
return ctx.card.issuerData != null && ctx.card.issuerData.size == 44
}
override fun getBlockchain(): Blockchain {
return Blockchain.TokenEmv
}
override fun getChainIdNum(): Int {
return EthTransaction.ChainEnum.Ropsten.value
}
override fun getBalance(): Amount? {
return if (!hasBalanceInfo()) {
null
} else {
convertToAmount(coinData.balanceInInternalUnits)
}
}
override fun getBalanceHTML(): String? {
return if (hasLinkedContract()) {
if (balance != null) {
balance!!.toDescriptionString(tokenDecimals)
} else {
""
}
} else {
"NO LINKED CONTRACT"
}
}
override fun getBalanceCurrency(): String? {
return ctx.card.getTokenSymbol()
}
override fun getAmountInputFilters(): Array<InputFilter>? {
return arrayOf(DecimalDigitsInputFilter(tokenDecimals))
}
override fun getFeeCurrency(): String? {
return balanceCurrency
}
override fun isBalanceNotZero(): Boolean {
if (coinData == null) return false
return if (coinData.balanceInInternalUnits == null) {
false
} else {
coinData.balanceInInternalUnits.notZero()
}
}
override fun getBalanceEquivalent(): String? {
return ""
}
override fun evaluateFeeEquivalent(fee: String?): String? {
return ""
}
override fun defineWallet() {
try {
if (hasLinkedContract()) {
ctx.coinData.wallet = TLVList.fromBytes(ctx.card.issuerData).getTLV(TLV.Tag.TAG_Token_Contract_Address).asString
} else {
ctx.coinData.wallet = calculateAddress(ctx.card.walletPublicKey)
}
} catch (e: Exception) {
ctx.coinData.wallet = "ERROR"
throw TangemException("Can't define wallet address")
}
}
override fun hasBalanceInfo(): Boolean {
return coinData.balanceInInternalUnits != null
}
override fun getWalletExplorerUri(): Uri {
return Uri.parse("https://ropsten.etherscan.io/token/" + getContractAddress(ctx.card) + "?a=" + ctx.coinData.wallet)
}
override fun isExtractPossible(): Boolean {
if (!hasBalanceInfo()) {
ctx.setMessage(R.string.loaded_wallet_error_obtaining_blockchain_data)
} else if (!isBalanceNotZero) {
ctx.setMessage(R.string.general_wallet_empty)
} else if (awaitingConfirmation()) {
ctx.setMessage(R.string.loaded_wallet_message_wait)
} else {
return true
}
return false
}
override fun checkNewTransactionAmountAndFee(amount: Amount, fee: Amount?, isFeeIncluded: Boolean): Boolean {
try {
if (isFeeIncluded && (amount > balance || amount < fee)) return false
if (!isFeeIncluded && amount.add(fee) > balance) return false
} catch (e: NumberFormatException) {
e.printStackTrace()
}
return true
}
override fun constructTransaction(amountValue: Amount, feeValue: Amount?, IncFee: Boolean, targetAddress: String?): SignTask.TransactionToSign? {
val functionBytes = "transfer".toByteArray()
val contractHex = coinData.wallet.substring(2)
val contractBytes = Util.hexToBytes(contractHex)
val recipientHex = targetAddress!!.substring(2)
val recipientBytes = Util.hexToBytes(recipientHex)
val amountBytes = convertToInternalAmount(amountValue).toBigInteger().toBytesPadded(32)
val feeLimitBytes = convertToInternalAmount(feeValue).toBigInteger().toBytesPadded(32)
val sequence = ctx.card.SignedHashes
val sequenceBytes = sequence.toBigInteger().toBytesPadded(4)
val hashToSign = Keccak256().digest(contractBytes + functionBytes + amountBytes + recipientBytes + feeLimitBytes + sequenceBytes)
return object : SignTask.TransactionToSign {
override fun isSigningMethodSupported(signingMethod: TangemCard.SigningMethod): Boolean {
return signingMethod == TangemCard.SigningMethod.Sign_Hash
}
override fun getHashesToSign(): Array<ByteArray> {
return arrayOf(hashToSign)
}
@Throws(java.lang.Exception::class)
override fun getRawDataToSign(): ByteArray {
throw java.lang.Exception("Signing of raw transaction not supported for " + this.javaClass.simpleName)
}
@Throws(java.lang.Exception::class)
override fun getHashAlgToSign(): String {
throw java.lang.Exception("Signing of raw transaction not supported for " + this.javaClass.simpleName)
}
@Throws(java.lang.Exception::class)
override fun getIssuerTransactionSignature(dataToSignByIssuer: ByteArray): ByteArray {
throw java.lang.Exception("Transaction validation by issuer not supported in this version")
}
@Throws(java.lang.Exception::class)
override fun onSignCompleted(signFromCard: ByteArray): ByteArray {
// val r = BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32))
// var s: BigInteger? = BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64))
// s = CryptoUtil.toCanonicalised(s)
//
// val publicKey = ctx.getCard().getWalletPublicKey()
//
// val verified = ECKey.verify(hashToSign, ECKey.ECDSASignature(r, s), publicKey)
// if (!verified) {
// Log.e(this.javaClass.simpleName + "-CHECK", "sign Failed.")
// }
//
// val v = BruteRecoveryID2(ECDSASignatureETH(r, s), hashToSign, publicKey)
// if (v != 27 && v != 28) {
// Log.e(TAG, "invalid v")
// throw java.lang.Exception("Error in " + this.javaClass.simpleName + " - invalid v")
// }
// Log.e(TAG, this.javaClass.simpleName + " V: " + v.toString())
//
// var rBytes = r.toByteArray()
// if (rBytes.size == 33) {
// rBytes = rBytes.copyOfRange(1,33)
// }
// val sBytes = s.toByteArray()
//
// val tokenEmvTransferBody = TokenEmvTransferBody(
// CID = Util.bytesToHex(ctx.card.cid),
// publicKey = Util.bytesToHex(ctx.card.walletPublicKey),
// contract = contractHex,
// amount = Util.byteArrayToHexString(amountBytes),
// recipient = recipientHex,
// feeLimit = Util.byteArrayToHexString(feeLimitBytes),
// sequence = sequence,
// r = Util.byteArrayToHexString(rBytes),
// s = Util.byteArrayToHexString(sBytes),
// v = v
// )
val tokenEmvTransferBody = TokenEmvTransferBody(
CID = Util.bytesToHex(ctx.card.cid),
publicKey = Util.bytesToHex(ctx.card.walletPublicKey),
amount = amountValue.toValueString(),
currency = amountValue.currency,
recipient = targetAddress,
feeLimit = feeValue!!.toValueString(),
sequence = sequence,
signature = Util.bytesToHex(signFromCard)
)
val jsonBody = Gson().toJson(tokenEmvTransferBody)
val serializedBody = SerializationUtils.serialize(jsonBody)
notifyOnNeedSendTransaction(serializedBody)
return serializedBody
}
}
}
override fun requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks: BlockchainRequestsCallbacks) {
val serverApiInfura = ServerApiInfura(ctx.blockchain)
val responseListener: ServerApiInfura.ResponseListener = object : ServerApiInfura.ResponseListener {
override fun onSuccess(method: String, infuraResponse: InfuraResponse) {
try {
var balanceCap = infuraResponse.result
balanceCap = balanceCap!!.substring(2)
val l = BigInteger(balanceCap, 16)
coinData.balanceInInternalUnits = InternalAmount(l, ctx.card.tokenSymbol)
coinData.isBalanceReceived = true
// Log.i("$TAG eth_call", balanceCap)
} catch (e: java.lang.Exception) {
onFail(method, e.message ?: "invalid response")
}
blockchainRequestsCallbacks.onComplete(true)
}
override fun onFail(method: String, message: String) {
Log.e(TAG, "onFail: $method $message")
ctx.error = message
blockchainRequestsCallbacks.onComplete(false)
}
}
serverApiInfura.setResponseListener(responseListener)
if (validateAddress(getContractAddress(ctx.card))) {
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.wallet, getContractAddress(ctx.card), "")
} else {
ctx.error = "Smart contract address not defined"
blockchainRequestsCallbacks.onComplete(false)
}
}
override fun requestFee(blockchainRequestsCallbacks: BlockchainRequestsCallbacks, targetAddress: String?, amount: Amount) {
val tokenEmvTransferBody = TokenEmvGetTransferFeeBody(
CID = Util.bytesToHex(ctx.card.cid),
publicKey = Util.bytesToHex(ctx.card.walletPublicKey)
)
val observer = object : DisposableSingleObserver<TokenEmvGetTransferFeeAnswer>() {
override fun onError(e: Throwable) {
ctx.error = "Can't get transfer fee, ${e.message}"
blockchainRequestsCallbacks.onComplete(false)
}
override fun onSuccess(t: TokenEmvGetTransferFeeAnswer) {
if (t.success != null && t.success) {
ctx.error = null
coinData.minFee = Amount(t.fee, t.currency)
coinData.normalFee = Amount(t.fee, t.currency)
coinData.maxFee = Amount(t.fee, t.currency)
blockchainRequestsCallbacks.onComplete(true)
} else {
if (t.error != null) {
ctx.error = t.error
} else {
ctx.error = "Can't get transfer fee, code ${t.errorCode}"
}
blockchainRequestsCallbacks.onComplete(false)
}
}
}
ServerApiTokenEmv().getTransferFee(tokenEmvTransferBody, observer)
blockchainRequestsCallbacks.onComplete(true)
}
override fun requestSendTransaction(blockchainRequestsCallbacks: BlockchainRequestsCallbacks, txForSend: ByteArray?) {
val jsonBody = SerializationUtils.deserialize<String>(txForSend)
Log.e(TAG, jsonBody)
val tokenEmvTransferBody = Gson().fromJson(jsonBody, TokenEmvTransferBody::class.java)
val observer = object : DisposableSingleObserver<TokenEmvTransferAnswer>() {
override fun onError(e: Throwable) {
ctx.error = "Can't send transfer, ${e.message}"
blockchainRequestsCallbacks.onComplete(false)
}
override fun onSuccess(t: TokenEmvTransferAnswer) {
if (t.success != null && t.success) {
ctx.error = null
blockchainRequestsCallbacks.onComplete(true)
} else {
if (t.error != null) {
ctx.error = t.error
} else {
ctx.error = "Can't send transfer, code ${t.errorCode}"
}
blockchainRequestsCallbacks.onComplete(false)
}
}
}
ServerApiTokenEmv().transfer(tokenEmvTransferBody, observer)
}
override fun allowSelectFeeLevel(): Boolean {
return false
}
override fun pendingTransactionTimeoutInSeconds(): Int {
return 60
}
}

View file

@ -74,7 +74,10 @@ class CardSession(
}
runnable.run(this) { result ->
stop()
when (result) {
is CompletionResult.Success -> stop()
is CompletionResult.Failure -> stopWithError(result.error)
}
callback(result)
}
}
@ -99,7 +102,6 @@ class CardSession(
}
is CompletionResult.Success -> {
callback(this, null)
}
}
}

View file

@ -91,7 +91,7 @@ abstract class Command<T : CommandResponse> : CardSessionRunnable<T> {
}
}
is CompletionResult.Failure ->
if (result.error == SessionError.TagLost()) {
if (result.error is SessionError.TagLost) {
session.viewDelegate.onTagLost()
} else {
callback(CompletionResult.Failure(result.error))

View file

@ -14,67 +14,50 @@ import java.util.*
/**
* Determines which type of data is required for signing.
*/
data class SigningMethod(val rawValue: Int) {
data class SigningMethodMask(val rawValue: Int) {
fun contains(value: Int): Boolean {
fun contains(signingMethod: SigningMethod): Boolean {
return if (rawValue and 0x80 == 0) {
value == rawValue
signingMethod.code == rawValue
} else {
rawValue and (0x01 shl value) != 0
rawValue and (0x01 shl signingMethod.code) != 0
}
}
}
companion object {
const val signHash = 0
const val signRaw = 1
const val signHashValidatedByIssuer = 2
const val signRawValidatedByIssuer = 3
const val signHashValidatedByIssuerAndWriteIssuerData = 4
const val signRawValidatedByIssuerAndWriteIssuerData = 5
const val signPos = 6
enum class SigningMethod(val code: Int) {
SignHash(0),
SignRaw(1),
SignHashValidateByIssuer(2),
SignRawValidateByIssuer(3),
SignHashValidateByIssuerWriteIssuerData(4),
SignRawValidateByIssuerWriteIssuerData(5),
SignPos(6)
}
fun build(
signHash: Boolean = false,
signRaw: Boolean = false,
signHashValidatedByIssuer: Boolean = false,
signRawValidatedByIssuer: Boolean = false,
signHashValidatedByIssuerAndWriteIssuerData: Boolean = false,
signRawValidatedByIssuerAndWriteIssuerData: Boolean = false,
signPos: Boolean = false
class SigningMethodMaskBuilder() {
): SigningMethod {
fun Boolean.toInt() = if (this) 1 else 0
private val signingMethods = mutableSetOf<SigningMethod>()
val signingMethodsCount = 0 +
signHash.toInt() +
signRaw.toInt() +
signHashValidatedByIssuer.toInt() +
signRawValidatedByIssuer.toInt() +
signHashValidatedByIssuerAndWriteIssuerData.toInt() +
signRawValidatedByIssuerAndWriteIssuerData.toInt() +
signPos.toInt()
fun add(signingMethod: SigningMethod) {
signingMethods.add(signingMethod)
}
var signingMethod: Int = 0
if (signingMethodsCount == 1) {
if (signHash) signingMethod += SigningMethod.signHash
if (signRaw) signingMethod += SigningMethod.signRaw
if (signHashValidatedByIssuer) signingMethod += SigningMethod.signHashValidatedByIssuer
if (signRawValidatedByIssuer) signingMethod += SigningMethod.signRawValidatedByIssuer
if (signHashValidatedByIssuerAndWriteIssuerData) signingMethod += SigningMethod.signHashValidatedByIssuerAndWriteIssuerData
if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += SigningMethod.signRawValidatedByIssuerAndWriteIssuerData
if (signPos) signingMethod += SigningMethod.signPos
} else if (signingMethodsCount > 1) {
signingMethod = 0x80
if (signHash) signingMethod += 0x01
if (signRaw) signingMethod += 0x01 shl SigningMethod.signRaw
if (signHashValidatedByIssuer) signingMethod += 0x01 shl SigningMethod.signHashValidatedByIssuer
if (signRawValidatedByIssuer) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuer
if (signHashValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signHashValidatedByIssuerAndWriteIssuerData
if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuerAndWriteIssuerData
if (signPos) signingMethod += 0x01 shl SigningMethod.signPos
fun build(): SigningMethodMask {
val rawValue: Int = when {
signingMethods.count() == 0 -> {
0
}
signingMethods.count() == 1 -> {
signingMethods.iterator().next().code
}
else -> {
signingMethods.fold(
0x80, { acc, singingMethod -> acc + (0x01 shl singingMethod.code) }
)
}
return SigningMethod(signingMethod)
}
return SigningMethodMask(rawValue)
}
}
@ -113,22 +96,23 @@ enum class CardStatus(val code: Int) {
*/
data class ProductMask(val rawValue: Int) {
fun contains(value: Int): Boolean = (rawValue and value) != 0
fun contains(product: Product): Boolean = (rawValue and product.code) != 0
companion object {
const val note = 0x01
const val tag = 0x02
const val idCard = 0x04
const val idIssuer = 0x08
}
}
enum class Product(val code: Int) {
Note(0x01),
Tag(0x02),
IdCard(0x04),
IdIssuer(0x08)
}
class ProductMaskBuilder() {
private var productMaskValue = 0
fun add(productCode: Int) {
productMaskValue = productMaskValue or productCode
fun add(product: Product) {
productMaskValue = productMaskValue or product.code
}
fun build() = ProductMask(productMaskValue)
@ -299,7 +283,7 @@ class Card(
/**
* Defines what data should be submitted to SIGN command.
*/
val signingMethod: SigningMethod?,
val signingMethods: SigningMethodMask?,
/**
* Delay in seconds before COS executes commands protected by PIN2.
@ -415,7 +399,7 @@ class ReadCommand : Command<Card>() {
issuerPublicKey = decoder.decodeOptional(TlvTag.IssuerDataPublicKey),
curve = decoder.decodeOptional(TlvTag.CurveId),
maxSignatures = decoder.decodeOptional(TlvTag.MaxSignatures),
signingMethod = decoder.decodeOptional(TlvTag.SigningMethod),
signingMethods = decoder.decodeOptional(TlvTag.SigningMethod),
pauseBeforePin2 = decoder.decodeOptional(TlvTag.PauseBeforePin2),
walletPublicKey = decoder.decodeOptional(TlvTag.WalletPublicKey),
walletRemainingSignatures = decoder.decodeOptional(TlvTag.RemainingSignatures),

View file

@ -60,7 +60,7 @@ class PersonalizeCommand(
issuerPublicKey = decoder.decodeOptional(TlvTag.IssuerDataPublicKey),
curve = decoder.decodeOptional(TlvTag.CurveId),
maxSignatures = decoder.decodeOptional(TlvTag.MaxSignatures),
signingMethod = decoder.decodeOptional(TlvTag.SigningMethod),
signingMethods = decoder.decodeOptional(TlvTag.SigningMethod),
pauseBeforePin2 = decoder.decodeOptional(TlvTag.PauseBeforePin2),
walletPublicKey = decoder.decodeOptional(TlvTag.WalletPublicKey),
walletRemainingSignatures = decoder.decodeOptional(TlvTag.RemainingSignatures),
@ -105,7 +105,7 @@ class PersonalizeCommand(
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.SigningMethod, config.signingMethods)
tlvBuilder.append(TlvTag.SettingsMask, config.createSettingsMask())
tlvBuilder.append(TlvTag.PauseBeforePin2, config.pauseBeforePin2 / 10)
tlvBuilder.append(TlvTag.Cvc, config.cvc.toByteArray())

View file

@ -2,7 +2,7 @@ package com.tangem.commands.personalization.entities
import com.tangem.commands.CardData
import com.tangem.commands.EllipticCurve
import com.tangem.commands.SigningMethod
import com.tangem.commands.SigningMethodMask
data class NdefRecord(
val type: Type,
@ -33,7 +33,7 @@ data class CardConfig(
val pauseBeforePin2: Int,
val smartSecurityDelay: Boolean,
val curveID: EllipticCurve,
val signingMethod: SigningMethod,
val signingMethods: SigningMethodMask,
val maxSignatures: Int,
val isReusable: Boolean,
val allowSwapPin: Boolean,

View file

@ -122,9 +122,9 @@ class TlvDecoder(val tlvList: List<Tlv>) {
}
}
TlvValueType.SigningMethod -> {
typeCheck<T, SigningMethod>(tag)
typeCheck<T, SigningMethodMask>(tag)
try {
SigningMethod(tlvValue.toInt()) as T
SigningMethodMask(tlvValue.toInt()) as T
} catch (exception: Exception) {
logException(tag, tlvValue.toInt().toString(), exception)
throw SessionError.DecodingFailed()

View file

@ -87,8 +87,8 @@ class TlvEncoder {
(value as CardStatus).code.toByteArray()
}
TlvValueType.SigningMethod -> {
typeCheck<T, SigningMethod>(tag)
byteArrayOf((value as SigningMethod).rawValue.toByte())
typeCheck<T, SigningMethodMask>(tag)
byteArrayOf((value as SigningMethodMask).rawValue.toByte())
}
TlvValueType.IssuerDataMode -> {
typeCheck<T, IssuerDataMode>(tag)

View file

@ -20,7 +20,7 @@ internal class ScanTask : CardSessionRunnable<Card> {
if (card == null) {
callback(CompletionResult.Failure(SessionError.MissingPreflightRead()))
} else if (card.cardData?.productMask?.contains(ProductMask.tag) != false) {
} else if (card.cardData?.productMask?.contains(Product.Tag) != false) {
callback(CompletionResult.Success(card))
} else if (card.status != CardStatus.Loaded) {

View file

@ -82,8 +82,8 @@ class TlvDecoderTest {
@Test
fun `map SigningMethods single value returns correct value`() {
val signingMethods: SigningMethod = tlvMapper.decode(TlvTag.SigningMethod)
assertThat(signingMethods.contains(SigningMethod.signHash))
val signingMethods: SigningMethodMask = tlvMapper.decode(TlvTag.SigningMethod)
assertThat(signingMethods.contains(SigningMethod.SignHash))
.isTrue()
}
@ -91,20 +91,20 @@ class TlvDecoderTest {
fun `map SigningMethods set of methods returns correct value`() {
val localMapper = TlvDecoder(Tlv.deserialize("070195".hexToBytes())!!)
val signingMethod: SigningMethod = localMapper.decode(TlvTag.SigningMethod)
assertThat(signingMethod.contains(SigningMethod.signHash))
val signingMethods: SigningMethodMask = localMapper.decode(TlvTag.SigningMethod)
assertThat(signingMethods.contains(SigningMethod.SignHash))
.isTrue()
assertThat(signingMethod.contains(SigningMethod.signHashValidatedByIssuer))
assertThat(signingMethods.contains(SigningMethod.SignHashValidateByIssuer))
.isTrue()
assertThat(signingMethod.contains(SigningMethod.signHashValidatedByIssuerAndWriteIssuerData))
assertThat(signingMethods.contains(SigningMethod.SignHashValidateByIssuerWriteIssuerData))
.isTrue()
assertThat(signingMethod.contains(SigningMethod.signRaw))
assertThat(signingMethods.contains(SigningMethod.SignRaw))
.isFalse()
assertThat(signingMethod.contains(SigningMethod.signRawValidatedByIssuer))
assertThat(signingMethods.contains(SigningMethod.SignRawValidateByIssuer))
.isFalse()
assertThat(signingMethod.contains(SigningMethod.signRawValidatedByIssuerAndWriteIssuerData))
assertThat(signingMethods.contains(SigningMethod.SignRawValidateByIssuerWriteIssuerData))
.isFalse()
assertThat(signingMethod.contains(SigningMethod.signPos))
assertThat(signingMethods.contains(SigningMethod.SignPos))
.isFalse()
}
@ -119,7 +119,7 @@ class TlvDecoderTest {
fun `map ProductMask with raw value 5 returns correct value`() {
val localMapper = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(5))))
val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask)
assertThat(productMask.contains(ProductMask.note) && productMask.contains(ProductMask.idCard))
assertThat(productMask.contains(Product.Note) && productMask.contains(Product.IdCard))
.isTrue()
}
@ -127,7 +127,7 @@ class TlvDecoderTest {
fun `map ProductMask with raw value 1 returns correct value`() {
val localMapper = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(1))))
val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask)
assertThat(productMask.contains(ProductMask.note))
assertThat(productMask.contains(Product.Note))
.isTrue()
}

View file

@ -46,10 +46,11 @@ dependencies {
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 "androidx.recyclerview:recyclerview:1.2.0-alpha02"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
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'
implementation 'com.github.gbIxaHue:eu4d:0.3.8'
}

View file

@ -20,7 +20,10 @@ fun List<Item>.iterate(func: (Item) -> Unit) {
forEach {
when (it) {
is BaseItem -> func(it)
is ItemGroup -> it.itemList.iterate(func)
is ItemGroup -> {
func(it)
it.itemList.iterate(func)
}
}
}
}

View file

@ -5,7 +5,12 @@ import com.tangem.tangemtest._arch.structure.Id
/**
[REDACTED_AUTHOR]
*/
interface Item {
interface UpdateBy<B>{
fun update(value: B)
}
interface Item: UpdateBy<Item> {
val id: Id
var parent: Item?
var viewModel: ItemViewModel
@ -36,4 +41,7 @@ open class BaseItem(
override var parent: Item? = null
override fun update(value: Item) {
viewModel.update(value.viewModel)
}
}

View file

@ -49,4 +49,8 @@ open class SimpleItemGroup(
ILog.d(this, "clear $id")
itemList.clear()
}
override fun update(value: Item) {
// nothing to do
}
}

View file

@ -16,7 +16,7 @@ class KeyValue(val key: String, val value: Any)
class ViewState(
isVisible: Boolean? = null,
bgColor: Int? = -1
) {
) : UpdateBy<ViewState> {
class State<T>(
stateValue: T,
@ -41,9 +41,15 @@ class ViewState(
val states = listOf(isVisibleState, backgroundColor, descriptionVisibility)
states.forEach { it.preventSameChanges = isPrevented }
}
override fun update(value: ViewState) {
// isVisibleState.update(value.isVisibleState)
// backgroundColor.update(value.backgroundColor)
// descriptionVisibility.update(value.descriptionVisibility)
}
}
interface ItemViewModel : PayloadHolder {
interface ItemViewModel : PayloadHolder, UpdateBy<ItemViewModel> {
val viewState: ViewState
var data: Any?
var defaultData: Any?
@ -92,6 +98,14 @@ open class BaseItemViewModel(
this.data = data
onDataUpdated = callback
}
override fun update(value: ItemViewModel) {
viewState.update(value.viewState)
defaultData = value.defaultData
data = value.data
payload.clear()
payload.putAll(value.payload)
}
}
class ListViewModel(

View file

@ -11,29 +11,29 @@ open class TypedItem<D>(id: Id, viewModel: ItemViewModel) : BaseItem(id, viewMod
open fun getTypedData(): D? = viewModel.data as? D
}
class TextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
open class TextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
: this(id, BaseItemViewModel(value, viewState))
}
class NumberItem(id: Id, viewModel: ItemViewModel) : TypedItem<Number>(id, viewModel) {
open class NumberItem(id: Id, viewModel: ItemViewModel) : TypedItem<Number>(id, viewModel) {
constructor(id: Id, value: Number? = null, viewState: ViewState = ViewState())
: this(id, BaseItemViewModel(value, viewState))
}
class BoolItem(id: Id, viewModel: ItemViewModel) : TypedItem<Boolean>(id, viewModel) {
open class BoolItem(id: Id, viewModel: ItemViewModel) : TypedItem<Boolean>(id, viewModel) {
constructor(id: Id, value: Boolean? = null, viewState: ViewState = ViewState())
: this(id, BaseItemViewModel(value, viewState))
}
class EditTextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
open class EditTextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
: this(id, BaseItemViewModel(value, viewState))
}
class SpinnerItem(id: Id, viewModel: ListViewModel) : TypedItem<ListViewModel>(id, viewModel) {
open class SpinnerItem(id: Id, viewModel: ListViewModel) : TypedItem<ListViewModel>(id, viewModel) {
constructor(id: Id, list: List<KeyValue>, selectedValue: Any?, viewState: ViewState = ViewState())
: this(id, ListViewModel(list, selectedValue, viewState))
}

View file

@ -3,9 +3,9 @@ package com.tangem.tangemtest._main
import android.content.res.Resources
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
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
@ -20,7 +20,7 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
*/
class MainActivity : AppCompatActivity() {
private val vm: MainViewModel by viewModels<MainViewModel>()
private val mainVM: MainViewModel by viewModels<MainViewModel>()
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
@ -52,11 +52,24 @@ class MainActivity : AppCompatActivity() {
}
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) }
menuInflater.inflate(R.menu.menu_activity_main, menu)
return true
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
val switchItem = menu.findItem(R.id.action_toggle_description_visibility)
switchItem.isChecked = mainVM.descriptionSwitchState
return super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val result = when (item.itemId) {
R.id.action_toggle_description_visibility -> {
item.isChecked = !item.isChecked
mainVM.switchToggled(item.isChecked)
}
else -> null
}
return super.onCreateOptionsMenu(menu)
return if (result == null) super.onOptionsItemSelected(item) else true
}
}

View file

@ -10,10 +10,12 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
*/
class MainViewModel : ViewModel() {
val ldDescriptionSwitch = MutableLiveData<Boolean>(false)
var descriptionSwitchState = false
var commandResponse: CommandResponse? = null
fun switchToggled(state: Boolean) {
descriptionSwitchState = state
ldDescriptionSwitch.postValue(state)
}

View file

@ -2,16 +2,14 @@ package com.tangem.tangemtest._main.entryPoint
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
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.extensions.view.beginDelayedTransition
import com.tangem.tangemtest.ucase.getDefaultNavigationOptions
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
@ -47,7 +45,7 @@ class ActionListFragment : BaseFragment() {
}
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
vhDataWrapper.descriptionIsVisible = it
TransitionManager.beginDelayedTransition(rvActions as ViewGroup, AutoTransition())
rvActions.beginDelayedTransition()
rvActions.adapter?.notifyDataSetChanged()
})
}

View file

@ -59,8 +59,6 @@ class RvActionsVH(
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 {

View file

@ -4,6 +4,13 @@ package com.tangem.tangemtest.commons
[REDACTED_AUTHOR]
*/
interface Store<M> {
fun save(config: M)
fun save(value: M)
fun restore(): M
}
interface KeyedStore<M> {
fun save(key: String, value: M)
fun restore(key: String): M
fun restoreAll(): MutableMap<String, M>
fun delete(key: String)
}

View file

@ -0,0 +1,76 @@
package com.tangem.tangemtest.commons
import android.app.Activity
import android.app.Dialog
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AlertDialog
class DialogController {
var onDismissCallback: (() -> Unit)? = null
var onShowCallback: (() -> Unit)? = null
var view: View? = null
private var rawDialog: Dialog? = null
private var inShowingProcess = false
private var inDismissingProcess = false
private var autoReleaseOnDismiss = false
fun createAlert(context: Activity, resLayout: Int): AlertDialog {
view = LayoutInflater.from(context).inflate(resLayout, null)
rawDialog = AlertDialog.Builder(context).setView(view).create().apply {
setOnShowListener { onShow() }
setOnDismissListener { onDismiss() }
}
return rawDialog as AlertDialog
}
fun set(dialog: Dialog) {
rawDialog = dialog
dialog.setOnShowListener { onShow() }
dialog.setOnDismissListener { onDismiss() }
}
private fun onShow() {
inShowingProcess = false
onShowCallback?.invoke()
}
private fun onDismiss() {
inDismissingProcess = false
if (autoReleaseOnDismiss) release()
onDismissCallback?.invoke()
}
fun show() {
val dialog = rawDialog ?: return
if (inShowingProcess) return
if (dialog.isShowing) return
inShowingProcess = true
dialog.show()
}
fun dismiss(autoRelease: Boolean = true) {
val dialog = rawDialog ?: return
if (inDismissingProcess) return
if (!dialog.isShowing) return
autoReleaseOnDismiss = autoRelease
inDismissingProcess = true
dialog.dismiss()
}
fun release() {
onDismissCallback = null
onShowCallback = null
inShowingProcess = false
inDismissingProcess = false
autoReleaseOnDismiss = false
view = null
rawDialog = null
}
}

View file

@ -11,24 +11,38 @@ fun CardConfig.Companion.create(application: Application): CardConfig {
val preferences = application.getSharedPreferences("prefs", Context.MODE_PRIVATE)
val signingMethod = SigningMethod.build(
signHash = preferences.getBoolean("personalization_SigningMethod_0", false),
signRaw = preferences.getBoolean("personalization_SigningMethod_1", false),
signHashValidatedByIssuer = preferences.getBoolean("personalization_SigningMethod_2", false),
signRawValidatedByIssuer = preferences.getBoolean("personalization_SigningMethod_3", false),
signHashValidatedByIssuerAndWriteIssuerData = preferences.getBoolean("personalization_SigningMethod_4", false),
signRawValidatedByIssuerAndWriteIssuerData = preferences.getBoolean("personalization_SigningMethod_5", false),
signPos = preferences.getBoolean("personalization_SigningMethod_6", false)
)
val signingMethodMaskBuilder = SigningMethodMaskBuilder()
if (preferences.getBoolean("personalization_SigningMethod_0", false)) {
signingMethodMaskBuilder.add(SigningMethod.SignHash)
}
if (preferences.getBoolean("personalization_SigningMethod_1", false)) {
signingMethodMaskBuilder.add(SigningMethod.SignRaw)
}
if (preferences.getBoolean("personalization_SigningMethod_2", false)) {
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuer)
}
if (preferences.getBoolean("personalization_SigningMethod_3", false)) {
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuer)
}
if (preferences.getBoolean("personalization_SigningMethod_4", false)) {
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuerWriteIssuerData)
}
if (preferences.getBoolean("personalization_SigningMethod_5", false)) {
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuerWriteIssuerData)
}
if (preferences.getBoolean("personalization_SigningMethod_6", false)) {
signingMethodMaskBuilder.add(SigningMethod.SignHash)
}
val signingMethod = signingMethodMaskBuilder.build()
val isNote = preferences.getBoolean("personalization_ProductMask_IsNote", true)
val isTag = preferences.getBoolean("personalization_ProductMask_IsTag", false)
val isIdCard = preferences.getBoolean("personalization_ProductMask_IsIDCard", false)
val productMaskBuilder = ProductMaskBuilder()
if (isNote) productMaskBuilder.add(ProductMask.note)
if (isTag) productMaskBuilder.add(ProductMask.tag)
if (isIdCard) productMaskBuilder.add(ProductMask.idCard)
if (isNote) productMaskBuilder.add(Product.Note)
if (isTag) productMaskBuilder.add(Product.Tag)
if (isIdCard) productMaskBuilder.add(Product.IdCard)
val productMask = productMaskBuilder.build()
var tokenSymbol: String? = null
@ -80,7 +94,7 @@ fun CardConfig.Companion.create(application: Application): CardConfig {
cardData = cardData,
curveID = EllipticCurve.byName(preferences.getString("personalization_CurveId", "secp256k1")!!)
?: EllipticCurve.Secp256k1,
signingMethod = signingMethod,
signingMethods = signingMethod,
createWallet = preferences.getBoolean("personalization_CreateWallet", true),
maxSignatures = preferences.getString("personalization_MaxSignatures", "1000")!!.toInt(),
isReusable = preferences.getBoolean("personalization_SettingsMask_IsReusable", true),

View file

@ -0,0 +1,13 @@
package com.tangem.tangemtest.extensions.view
import android.view.ViewGroup
import androidx.transition.AutoTransition
import androidx.transition.Transition
import androidx.transition.TransitionManager
/**
[REDACTED_AUTHOR]
*/
fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) {
TransitionManager.beginDelayedTransition(this, transition)
}

View file

@ -37,7 +37,7 @@ class PersonalizeAction : BaseAction() {
val acquirer = DefaultPersonalizationParams.acquirer()
val manufacturer = DefaultPersonalizationParams.manufacturer()
val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig())
val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig.default())
val cardConfig = PersonalizationConfigToCardConfig().convert(personalizeConfig)
attrs.tangemSdk.personalize(cardConfig, issuer, manufacturer, acquirer) {

View file

@ -13,10 +13,10 @@ import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
class SignAction : BaseAction() {
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
val dataForHashing = attrs.itemList.findItem(TlvId.TransactionOutHash) ?: return
val hash = dataForHashing.getData() as? ByteArray ?: return
val hash = dataForHashing.getData() as? String ?: return
val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return
attrs.tangemSdk.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
attrs.tangemSdk.sign(arrayOf(hash.toByteArray()), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
}
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {

View file

@ -25,6 +25,7 @@ interface ItemsManager : PayloadHolder {
fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback)
fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)?
fun attachPayload(payload: Payload)
fun updateByItemList(list: List<Item>)
}
interface PayloadKey {

View file

@ -6,6 +6,7 @@ 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._arch.structure.abstraction.findItem
import com.tangem.tangemtest._arch.structure.abstraction.iterate
import com.tangem.tangemtest.ucase.domain.actions.Action
import com.tangem.tangemtest.ucase.domain.actions.AttrForAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
@ -47,6 +48,10 @@ open class BaseItemsManager(protected val action: Action) : ItemsManager, Lifecy
this.changeConsequence = consequence
}
override fun updateByItemList(list: List<Item>) {
list.iterate { itemList.findItem(it.id)?.update(it) }
}
override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(tangemSdk), callback)
}

View file

@ -29,7 +29,7 @@ class PersonalizationItemsManager(
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
val config = converter.convert(itemList, PersonalizationConfig())
val config = converter.convert(itemList, PersonalizationConfig.default())
store.save(config)
}
}

View file

@ -4,6 +4,7 @@ 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.common.extensions.toHexString
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.findItem
@ -40,7 +41,7 @@ class SignScanConsequence : ItemsChangeConsequence {
EllipticCurve.Ed25519 -> dataForHashing.calculateSha512()
else -> return null
}
hashItem.setData(hashedData)
hashItem.setData(hashedData.toHexString())
}
return affectedItems.toList()
}

View file

@ -3,6 +3,7 @@ package com.tangem.tangemtest.ucase.domain.responses
import com.google.gson.*
import com.tangem.commands.*
import com.tangem.common.extensions.toHexString
import com.tangem.tangemtest.extensions.print
import java.lang.reflect.Type
import java.text.DateFormat
import java.util.*
@ -11,14 +12,17 @@ import java.util.*
[REDACTED_AUTHOR]
*/
class ResponseJsonConverter {
val gson: Gson by lazy { init() }
private val fieldConverter = ResponseFieldConverter()
private fun init(): Gson {
val builder = GsonBuilder().apply {
registerTypeAdapter(ByteArray::class.java, ByteTypeAdapter())
registerTypeAdapter(SigningMethod::class.java, SigningMethodTypeAdapter())
registerTypeAdapter(SettingsMask::class.java, SettingsMaskTypeAdapter())
registerTypeAdapter(ProductMask::class.java, ProductMaskTypeAdapter())
registerTypeAdapter(ByteArray::class.java, ByteTypeAdapter(fieldConverter))
registerTypeAdapter(SigningMethodMask::class.java, SigningMethodTypeAdapter(fieldConverter))
registerTypeAdapter(SettingsMask::class.java, SettingsMaskTypeAdapter(fieldConverter))
registerTypeAdapter(ProductMask::class.java, ProductMaskTypeAdapter(fieldConverter))
registerTypeAdapter(Date::class.java, DateTypeAdapter())
}
builder.setPrettyPrinting()
@ -28,31 +32,41 @@ class ResponseJsonConverter {
fun convertResponse(response: CommandResponse?): String = gson.toJson(response)
}
class ByteTypeAdapter : JsonSerializer<ByteArray> {
class ByteTypeAdapter(
private val fieldConverter: ResponseFieldConverter
) : JsonSerializer<ByteArray> {
override fun serialize(src: ByteArray, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.toHexString())
return JsonPrimitive(fieldConverter.byteArray(src))
}
}
class SettingsMaskTypeAdapter : JsonSerializer<SettingsMask> {
class SettingsMaskTypeAdapter(
private val fieldConverter: ResponseFieldConverter
) : JsonSerializer<SettingsMask> {
override fun serialize(src: SettingsMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
val arrayElement = JsonArray()
Settings.values()
.filter { src.contains(it) }
.forEach { arrayElement.add(it.name) }
return arrayElement
return JsonArray().apply {
fieldConverter.settingsMaskList(src).forEach { add(it) }
}
}
}
class ProductMaskTypeAdapter : JsonSerializer<ProductMask> {
class ProductMaskTypeAdapter(
private val fieldConverter: ResponseFieldConverter
) : JsonSerializer<ProductMask> {
override fun serialize(src: ProductMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.rawValue.toString())
return JsonArray().apply {
fieldConverter.productMaskList(src).forEach { add(it) }
}
}
}
class SigningMethodTypeAdapter : JsonSerializer<SigningMethod> {
override fun serialize(src: SigningMethod, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.rawValue.toString())
class SigningMethodTypeAdapter(
private val fieldConverter: ResponseFieldConverter
) : JsonSerializer<SigningMethodMask> {
override fun serialize(src: SigningMethodMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonArray().apply {
fieldConverter.signingMethodList(src).forEach { add(it) }
}
}
}
@ -61,4 +75,41 @@ class DateTypeAdapter : JsonSerializer<Date> {
val formatter = DateFormat.getDateInstance(DateFormat.DEFAULT, Locale("en_US"))
return JsonPrimitive(formatter.format(src).toString())
}
}
class ResponseFieldConverter {
fun productMask(productMask: ProductMask?): String {
return productMaskList(productMask).print(wrap = false)
}
fun productMaskList(productMask: ProductMask?): List<String> {
val mask = productMask ?: return emptyList()
return Product.values().filter { mask.contains(it) }.map { it.name }
}
fun signingMethod(signingMask: SigningMethodMask?): String {
return signingMethodList(signingMask).print(wrap = false)
}
fun signingMethodList(signingMask: SigningMethodMask?): List<String> {
val mask = signingMask ?: return emptyList()
return SigningMethod.values().filter { mask.contains(it) }.map { it.name }
}
fun settingsMask(settingsMask: SettingsMask?): String {
return settingsMaskList(settingsMask).print(wrap = false)
}
fun settingsMaskList(settingsMask: SettingsMask?): List<String> {
val masks = settingsMask ?: return emptyList()
return Settings.values().filter { masks.contains(it) }.map { it.name }
}
fun byteArray(byteArray: ByteArray?): String? {
return byteArray?.toHexString()
}
}

View file

@ -5,6 +5,8 @@ import com.tangem.tangemtest.ucase.resources.MainResourceHolder
import com.tangem.tangemtest.ucase.resources.Resources
import com.tangem.tangemtest.ucase.variants.responses.CardDataId
import com.tangem.tangemtest.ucase.variants.responses.CardId
import com.tangem.tangemtest.ucase.variants.responses.DepersonalizeId
import com.tangem.tangemtest.ucase.variants.responses.SignId
/**
[REDACTED_AUTHOR]
@ -13,6 +15,8 @@ class ResponseResources {
fun init(holder: MainResourceHolder) {
initCard(holder)
initCardData(holder)
initSignResponse(holder)
initDepersonalizeResponse(holder)
}
private fun initCard(holder: MainResourceHolder) {
@ -26,7 +30,7 @@ class ResponseResources {
holder.register(CardId.issuerPublicKey, Resources(R.string.response_card_issuer_data_public_key, R.string.info_response_card_issuer_data_public_key))
holder.register(CardId.curve, Resources(R.string.response_card_curve, R.string.info_response_card_curve))
holder.register(CardId.maxSignatures, Resources(R.string.response_card_max_signatures, R.string.info_response_card_max_signatures))
holder.register(CardId.signingMethod, Resources(R.string.response_card_signing_method, R.string.response_card_signing_method))
holder.register(CardId.signingMethod, Resources(R.string.response_card_signing_method, R.string.info_response_card_signing_method))
holder.register(CardId.pauseBeforePin2, Resources(R.string.response_card_pause_before_pin2, R.string.info_response_card_allow_pin2))
holder.register(CardId.walletPublicKey, Resources(R.string.response_card_wallet_public_key, R.string.info_response_card_wallet_public_key))
holder.register(CardId.walletRemainingSignatures, Resources(R.string.response_card_wallet_remaining_signatures, R.string.info_response_card_wallet_remaining_signatures))
@ -50,4 +54,15 @@ class ResponseResources {
holder.register(CardDataId.tokenContractAddress, Resources(R.string.response_card_card_data_token_contract_address, R.string.info_response_card_card_data_token_contract_address))
holder.register(CardDataId.tokenDecimal, Resources(R.string.response_card_card_data_token_decimal, R.string.info_response_card_card_data_token_decimal))
}
private fun initSignResponse(holder: MainResourceHolder) {
holder.register(SignId.cid, Resources(R.string.response_sign_cid, R.string.info_response_sign_cid))
holder.register(SignId.walletSignedHashes, Resources(R.string.response_sign_wallet_signed_hashes, R.string.info_response_sign_wallet_signed_hashes))
holder.register(SignId.walletRemainingSignatures, Resources(R.string.response_sign_wallet_remaining_signatures, R.string.info_response_sign_wallet_remaining_signatures))
holder.register(SignId.signature, Resources(R.string.response_sign_signature, R.string.info_response_sign_signature))
}
private fun initDepersonalizeResponse(holder: MainResourceHolder) {
holder.register(DepersonalizeId.isSuccess, Resources(R.string.response_depersonalize_is_success, R.string.info_response_depersonalize_is_success))
}
}

View file

@ -8,9 +8,11 @@ import androidx.core.view.plusAssign
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.tangem.TangemSdk
import com.tangem.commands.Card
import com.tangem.commands.CommandResponse
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
@ -28,7 +30,8 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
*/
abstract class BaseCardActionFragment : BaseFragment(), ActionView {
protected lateinit var itemContainer: ViewGroup
protected lateinit var swrLayout: SwipeRefreshLayout
protected lateinit var contentContainer: ViewGroup
protected lateinit var actionFab: FloatingActionButton
protected abstract val itemsManager: ItemsManager
@ -38,6 +41,8 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
private val paramsWidgetList = mutableListOf<ParameterWidget>()
override fun getLayoutId(): Int = R.layout.fg_base_action_layout
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d(this, "onViewCreated")
@ -47,7 +52,7 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
actionVM.setCardManager(TangemSdk.init(requireActivity()))
actionVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView))
initFab()
initViews()
createWidgets {
widgetsWasCreated()
subscribeToViewModelChanges()
@ -57,11 +62,13 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
protected open fun widgetsWasCreated() {}
protected open fun bindViews() {
itemContainer = mainView.findViewById(R.id.ll_container)
swrLayout = mainView.findViewById(R.id.swr_layout)
contentContainer = mainView.findViewById(R.id.ll_content_container)
actionFab = mainView.findViewById(R.id.fab_action)
}
protected open fun initFab() {
protected open fun initViews() {
swrLayout.isEnabled = false
enableActionFab(false)
actionFab.setOnClickListener { actionVM.invokeMainAction() }
}
@ -70,7 +77,7 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
Log.d(this, "createWidgets")
actionVM.ldItemList.observe(viewLifecycleOwner, Observer { itemList ->
itemList.forEach { param ->
val widget = ParameterWidget(inflateParamView(itemContainer), param)
val widget = ParameterWidget(inflateParamView(contentContainer), param)
widget.onValueChanged = { id, value -> actionVM.userChangedItem(id, value) }
widget.onActionBtnClickListener = actionVM.getItemAction(param.id)
paramsWidgetList.add(widget)
@ -81,39 +88,46 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
protected open fun subscribeToViewModelChanges() {
Log.d(this, "subscribeToViewModelChanges")
listenResponse()
listenResponseData()
listenResponseCardData()
listenError()
listenChangedItems()
listenDescriptionSwitchChanges()
}
private fun listenResponse() {
actionVM.seResponse.observe(viewLifecycleOwner, Observer {
Log.d(this, "listen response: $it")
mainActivityVM.changeResponseEvent(it)
Log.d(this, "handle response: $it")
handleResponse(it)
})
}
protected open fun listenResponseData() {
actionVM.seResponseData.observe(viewLifecycleOwner, Observer {
Log.d(this, "listen responseData: $it")
navigateTo(R.id.action_nav_card_action_to_response_screen)
Log.d(this, "handle responseData: $it")
handleResponseData(it)
})
}
protected open fun listenResponseCardData() {
actionVM.seResponseCardData.observe(viewLifecycleOwner, Observer {
Log.d(this, "listen responseCardData: $it")
responseCardDataHandled(it)
Log.d(this, "handle responseCardData: $it")
handleResponseCardData(it)
})
actionVM.seError.observe(viewLifecycleOwner, Observer {
Log.d(this, "handle error: $it")
handleError(it)
})
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
Log.d(this, "handle descriptionVisibilityState: $it")
handleDescriptionSwitchChanges(it)
})
listenChangedItems()
}
protected open fun responseCardDataHandled(card: Card?) {}
protected open fun handleResponse(response: CommandResponse) {
mainActivityVM.changeResponseEvent(response)
}
protected open fun listenError() {
actionVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) })
protected open fun handleResponseData(response: CommandResponse) {
navigateTo(R.id.action_nav_card_action_to_response_screen, options = null)
}
protected open fun handleResponseCardData(card: Card) {}
protected open fun handleError(error: String) {
showSnackbar(error)
}
protected open fun handleDescriptionSwitchChanges(descriptionVisibilityState: Boolean) {
actionVM.toggleDescriptionVisibility(descriptionVisibilityState)
paramsWidgetList.forEach { it.toggleDescriptionVisibility(descriptionVisibilityState) }
}
@Deprecated("Start to use itemViewModel")
@ -126,12 +140,6 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
})
}
protected open fun listenDescriptionSwitchChanges() {
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
actionVM.toggleDescriptionVisibility(it)
})
}
private fun inflateParamView(where: ViewGroup): ViewGroup {
val inflater = LayoutInflater.from(where.context)
val view = inflater.inflate(R.layout.w_card_incoming_param, where, false)

View file

@ -6,14 +6,13 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.impl.EditTextItem
import com.tangem.tangemtest.extensions.view.beginDelayedTransition
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
import com.tangem.tangemtest.ucase.resources.Resources
@ -73,7 +72,7 @@ class ParameterWidget(
}
fun toggleDescriptionVisibility(state: Boolean) {
TransitionManager.beginDelayedTransition(parent.parent as ViewGroup, AutoTransition())
(parent.parent as ViewGroup).beginDelayedTransition()
descriptionContainer.visibility = if (state) View.VISIBLE else View.GONE
}
@ -95,7 +94,7 @@ class ParameterWidget(
private fun toggleActionBtnVisibility() {
fun switchVisibilityState(newState: Int) {
actionBtnVisibilityState = newState
TransitionManager.beginDelayedTransition(parent, AutoTransition())
parent.beginDelayedTransition()
btnAction.visibility = actionBtnVisibilityState
}
when {

View file

@ -1,6 +1,5 @@
package com.tangem.tangemtest.ucase.variants.depersonalize.ui
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.DepersonalizeItemsManager
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
@ -11,6 +10,4 @@ import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
class DepersonalizeActionFragment : BaseCardActionFragment() {
override val itemsManager: ItemsManager by lazy { DepersonalizeItemsManager() }
override fun getLayoutId(): Int = R.layout.fg_depersonalize
}

View file

@ -5,22 +5,49 @@ import android.content.SharedPreferences
import androidx.core.content.edit
import com.google.gson.Gson
import com.tangem.tangemtest.AppTangemDemo
import com.tangem.tangemtest.commons.KeyedStore
import com.tangem.tangemtest.commons.Store
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig
class PersonalizationConfigStore(context: Context) : Store<PersonalizationConfig> {
class PersonalizationConfigStore(context: Context) : Store<PersonalizationConfig>, KeyedStore<PersonalizationConfig> {
private val key = "personalization_config"
companion object {
val defaultKey = "default"
}
private val sp: SharedPreferences = (context.applicationContext as AppTangemDemo).sharedPreferences()
private val sharedPreferencesKey = "personalization_presets"
private val sp: SharedPreferences = (context.applicationContext as AppTangemDemo).sharedPreferences(sharedPreferencesKey)
private val gson: Gson = Gson()
override fun save(config: PersonalizationConfig) {
sp.edit(true) { putString(key, gson.toJson(config)) }
override fun save(value: PersonalizationConfig) {
save(defaultKey, value)
}
override fun restore(): PersonalizationConfig {
val json = sp.getString(key, gson.toJson(PersonalizationConfig()))
return gson.fromJson(json, PersonalizationConfig::class.java)
override fun restore(): PersonalizationConfig = restore(defaultKey)
override fun save(key: String, value: PersonalizationConfig) {
sp.edit(true) { putString(key, toJson(value)) }
}
override fun restore(key: String): PersonalizationConfig {
val json = sp.getString(key, toJson(getDefaultConfig()))
return fromJson(json!!)
}
override fun restoreAll(): MutableMap<String, PersonalizationConfig> {
val map = mutableMapOf<String, PersonalizationConfig>()
sp.all.forEach { map[it.key] = fromJson(it.value as String) }
return map.toSortedMap()
}
override fun delete(key: String) {
sp.edit(true) { remove(key) }
}
private fun getDefaultConfig(): PersonalizationConfig = PersonalizationConfig.default()
private fun toJson(value: PersonalizationConfig): String = gson.toJson(value)
private fun fromJson(json: String): PersonalizationConfig = gson.fromJson(json, PersonalizationConfig::class.java)
}

View file

@ -19,7 +19,7 @@ class ConfigValuesHolder : BaseTypedHolder<Id, Value>() {
register(CardNumber.BatchId, Value(default.batchId))
register(Common.Curve, Value(default.curveID, Helper.listOfCurves()))
register(Common.Blockchain, Value(default.blockchain, Helper.listOfBlockchain()))
register(Common.BlockchainCustom, Value(""))
register(Common.BlockchainCustom, Value(default.blockchainCustom))
register(Common.MaxSignatures, Value(default.MaxSignatures))
register(Common.CreateWallet, Value(default.createWallet))
register(SigningMethod.SignTx, Value(default.SigningMethod0))
@ -66,7 +66,7 @@ class ConfigValuesHolder : BaseTypedHolder<Id, Value>() {
register(SettingsMaskNdef.DynamicNdef, Value(default.useDynamicNDEF))
register(SettingsMaskNdef.DisablePrecomputedNdef, Value(default.disablePrecomputedNDEF))
register(SettingsMaskNdef.Aar, Value(default.aar, Helper.aarList()))
register(SettingsMaskNdef.AarCustom, Value(default.aar))
register(SettingsMaskNdef.AarCustom, Value(default.aarCustom))
register(SettingsMaskNdef.Uri, Value(default.uri))
register(Pins.Pin, Value(default.PIN))
register(Pins.Pin2, Value(default.PIN2))

View file

@ -52,6 +52,7 @@ class ItemsToPersonalizationConfig : ItemsToModel<PersonalizationConfig> {
val export = PersonalizationConfig()
export.series = getTyped(CardNumber.Series)
export.startNumber = getTyped(CardNumber.Number)
export.batchId = getTyped(CardNumber.BatchId)
export.curveID = getTyped(Common.Curve)
export.blockchain = getTyped(Common.Blockchain)
export.blockchainCustom = getTyped(Common.BlockchainCustom)

View file

@ -1,8 +1,6 @@
package com.tangem.tangemtest.ucase.variants.personalize.converter
import com.tangem.commands.CardData
import com.tangem.commands.EllipticCurve
import com.tangem.commands.ProductMaskBuilder
import com.tangem.commands.*
import com.tangem.commands.personalization.entities.CardConfig
import com.tangem.commands.personalization.entities.NdefRecord
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig
@ -12,15 +10,29 @@ import java.util.*
class PersonalizationConfigToCardConfig : Converter<PersonalizationConfig, CardConfig> {
override fun convert(from: PersonalizationConfig): CardConfig {
val signingMethod = com.tangem.commands.SigningMethod.build(
signHash = from.SigningMethod0,
signRaw = from.SigningMethod1,
signHashValidatedByIssuer = from.SigningMethod2,
signRawValidatedByIssuer = from.SigningMethod3,
signHashValidatedByIssuerAndWriteIssuerData = from.SigningMethod4,
signRawValidatedByIssuerAndWriteIssuerData = from.SigningMethod5,
signPos = from.SigningMethod6
)
val signingMethodMaskBuilder = SigningMethodMaskBuilder()
if (from.SigningMethod0) {
signingMethodMaskBuilder.add(SigningMethod.SignHash)
}
if (from.SigningMethod1) {
signingMethodMaskBuilder.add(SigningMethod.SignRaw)
}
if (from.SigningMethod2) {
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuer)
}
if (from.SigningMethod3) {
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuer)
}
if (from.SigningMethod4) {
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuerWriteIssuerData)
}
if (from.SigningMethod5) {
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuerWriteIssuerData)
}
if (from.SigningMethod6) {
signingMethodMaskBuilder.add(SigningMethod.SignHash)
}
val signingMethod = signingMethodMaskBuilder.build()
val isNote = from.cardData.product_note
val isTag = from.cardData.product_tag
@ -28,10 +40,10 @@ class PersonalizationConfigToCardConfig : Converter<PersonalizationConfig, CardC
val isIdIssuer = from.cardData.product_id_issuer
val productMaskBuilder = ProductMaskBuilder()
if (isNote) productMaskBuilder.add(com.tangem.commands.ProductMask.note)
if (isTag) productMaskBuilder.add(com.tangem.commands.ProductMask.tag)
if (isIdCard) productMaskBuilder.add(com.tangem.commands.ProductMask.idCard)
if (isIdIssuer) productMaskBuilder.add(com.tangem.commands.ProductMask.idIssuer)
if (isNote) productMaskBuilder.add(com.tangem.commands.Product.Note)
if (isTag) productMaskBuilder.add(com.tangem.commands.Product.Tag)
if (isIdCard) productMaskBuilder.add(com.tangem.commands.Product.IdCard)
if (isIdIssuer) productMaskBuilder.add(com.tangem.commands.Product.IdIssuer)
val productMask = productMaskBuilder.build()
var tokenSymbol: String? = null

View file

@ -1,25 +1,26 @@
package com.tangem.tangemtest.ucase.variants.personalize.dto
import com.tangem.commands.EllipticCurve
/**
[REDACTED_AUTHOR]
*/
class PersonalizationConfig {
// Card number
var series = "BB"
var startNumber: Long = 300000000000
var batchId = "ffff"
var series = ""
var startNumber: Long = 0
var batchId = ""
// Common
var curveID = "ed25519"
var blockchain = "BTC/test"
var curveID = ""
var blockchain = ""
var blockchainCustom = ""
var MaxSignatures: Long = 999999
var createWallet = true
var MaxSignatures: Long = 0
var createWallet = false
// Signing method
var SigningMethod0 = true
var SigningMethod0 = false
var SigningMethod1 = false
var SigningMethod2 = false
var SigningMethod3 = false
@ -27,19 +28,16 @@ class PersonalizationConfig {
var SigningMethod5 = false
var SigningMethod6 = false
// Sign hash external properties
var pinLessFloorLimit: Long = 100000
var hexCrExKey = "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"
var pinLessFloorLimit: Long = 0
var hexCrExKey = ""
var requireTerminalTxSignature = false
var requireTerminalCertSignature = false
var checkPIN3onCard = true
var checkPIN3onCard = false
// Denomination
var writeOnPersonalization = false
var denomination: Long = 1000000
var denomination: Long = 0
// Token
var itsToken = false
@ -47,49 +45,125 @@ class PersonalizationConfig {
var contractAddress = ""
var decimal: Long = 0
var cardData = CardData()
// Settings mask
var isReusable = true
var isReusable = false
var useActivation = false
var forbidPurgeWallet = false
var allowSelectBlockchain = false
var useBlock = false
var oneApdu = false
var useCVC = false
var allowSwapPIN = true
var allowSwapPIN2 = true
var allowSwapPIN = false
var allowSwapPIN2 = false
var forbidDefaultPIN = false
var smartSecurityDelay = true
var protectIssuerDataAgainstReplay = true
var skipSecurityDelayIfValidatedByIssuer = true
var skipCheckPIN2andCVCIfValidatedByIssuer = true
var skipSecurityDelayIfValidatedByLinkedTerminal = true
var smartSecurityDelay = false
var protectIssuerDataAgainstReplay = false
var skipSecurityDelayIfValidatedByIssuer = false
var skipCheckPIN2andCVCIfValidatedByIssuer = false
var skipSecurityDelayIfValidatedByLinkedTerminal = false
var restrictOverwriteIssuerDataEx = false
// Settings mask - protocol encryption
var protocolAllowUnencrypted = true
var protocolAllowStaticEncryption = true
var protocolAllowUnencrypted = false
var protocolAllowStaticEncryption = false
var useNDEF = true
var useDynamicNDEF = true
var useNDEF = false
var useDynamicNDEF = false
var disablePrecomputedNDEF = false
var aar = "com.tangem.wallet"
var aar = ""
var aarCustom = ""
var uri = "https://tangem.com"
var uri = ""
// Pins
var PIN = "000000"
var PIN2 = "000"
var PIN = ""
var PIN2 = ""
var PIN3 = ""
var CVC = "000"
var pauseBeforePIN2: Long = 5000L
var CVC = ""
var pauseBeforePIN2: Long = 0
companion object {
fun default(): PersonalizationConfig {
return PersonalizationConfig().apply {
// Card number
series = "BB"
startNumber = 300000000000L
batchId = "ffff"
// Common
curveID = EllipticCurve.Secp256k1.curve
blockchain = "ETH"
blockchainCustom = ""
MaxSignatures = 999999L
createWallet = true
// Signing method
SigningMethod0 = true
SigningMethod1 = false
SigningMethod2 = false
SigningMethod3 = false
SigningMethod4 = false
SigningMethod5 = false
SigningMethod6 = false
// Sign hash external properties
pinLessFloorLimit = 100000L
hexCrExKey = "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"
requireTerminalTxSignature = false
requireTerminalCertSignature = false
checkPIN3onCard = true
// Denomination
writeOnPersonalization = false
denomination = 1000000L
// Token
itsToken = false
symbol = ""
contractAddress = ""
decimal = 0L
cardData = CardData()
// Settings mask
isReusable = true
useActivation = false
forbidPurgeWallet = false
allowSelectBlockchain = false
useBlock = false
oneApdu = false
useCVC = false
allowSwapPIN = true
allowSwapPIN2 = true
forbidDefaultPIN = false
smartSecurityDelay = true
protectIssuerDataAgainstReplay = true
skipSecurityDelayIfValidatedByIssuer = true
skipCheckPIN2andCVCIfValidatedByIssuer = true
skipSecurityDelayIfValidatedByLinkedTerminal = true
restrictOverwriteIssuerDataEx = false
// Settings mask - protocol encryption
protocolAllowUnencrypted = true
protocolAllowStaticEncryption = true
useNDEF = true
useDynamicNDEF = true
disablePrecomputedNDEF = false
aar = "com.tangem.wallet"
aarCustom = ""
uri = "https://tangem.com"
// Pins
PIN = "000000"
PIN2 = "000"
PIN3 = ""
CVC = "000"
pauseBeforePIN2 = 5000L
}
}
}
}
class CardData {

View file

@ -1,18 +1,30 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.*
import android.view.inputmethod.InputMethodManager
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat.getSystemService
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.Fade
import com.tangem.commands.Card
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.StringId
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.SafeValueChanged
import com.tangem.tangemtest._arch.widget.WidgetBuilder
import com.tangem.tangemtest.commons.DialogController
import com.tangem.tangemtest.commons.view.MultiActionView
import com.tangem.tangemtest.commons.view.ViewAction
import com.tangem.tangemtest.extensions.view.beginDelayedTransition
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.PersonalizationItemsManager
@ -21,28 +33,80 @@ import com.tangem.tangemtest.ucase.tunnel.ActionView
import com.tangem.tangemtest.ucase.tunnel.ItemError
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
import com.tangem.tangemtest.ucase.variants.personalize.PersonalizationConfigStore
import com.tangem.tangemtest.ucase.variants.personalize.ui.presets.PersonalizationPresetManager
import com.tangem.tangemtest.ucase.variants.personalize.ui.presets.PersonalizationPresetView
import com.tangem.tangemtest.ucase.variants.personalize.ui.presets.RvPresetNamesAdapter
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.PersonalizationItemBuilder
import com.tangem.tangemtest.ucase.variants.responses.ui.ResponseFragment
import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
import ru.dev.gbixahue.eu4d.lib.android.global.threading.post
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postWork
/**
[REDACTED_AUTHOR]
*/
class PersonalizationFragment : BaseCardActionFragment() {
class PersonalizationFragment : BaseCardActionFragment(), PersonalizationPresetView {
override val itemsManager: ItemsManager by lazy { PersonalizationItemsManager(PersonalizationConfigStore(requireContext())) }
override fun getLayoutId(): Int = R.layout.fg_personalization
override fun getLayoutId(): Int = R.layout.fg_base_action_layout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycle.addObserver(itemsManager as PersonalizationItemsManager)
}
override fun bindViews() {
super.bindViews()
swrLayout.isRefreshing = true
}
override fun initViews() {
actionFab.setOnClickListener { actionVM.invokeMainAction() }
}
override fun createWidgets(widgetCreatedCallback: () -> Unit) {
Log.d(this, "createWidgets")
val itemList = mutableListOf<Item>()
val maxDelay = 500
val timeStart = System.currentTimeMillis()
actionVM.ldItemList.observe(viewLifecycleOwner, Observer { list ->
Log.d(this, "ldBlockList size: ${list.size}")
itemList.addAll(list)
val llContainer = LinearLayout(requireContext()).apply { orientation = LinearLayout.VERTICAL }
postWork {
val builder = WidgetBuilder(PersonalizationItemBuilder())
itemList.forEach { builder.build(it, llContainer) }
actionVM.attachToPayload(mutableMapOf(
PayloadKey.actionView to this as ActionView,
PayloadKey.itemList to itemList
))
val timeEnd = System.currentTimeMillis()
val diff = timeEnd - timeStart
postUI(maxDelay - diff) {
contentContainer.beginDelayedTransition(Fade())
contentContainer.addView(llContainer)
widgetCreatedCallback()
swrLayout.isRefreshing = false
swrLayout.isEnabled = false
}
}
})
}
override fun widgetsWasCreated() {
super.widgetsWasCreated()
val btnContainer = itemContainer.inflate<ViewGroup>(R.layout.view_simple_button)
val btnContainer = contentContainer.inflate<ViewGroup>(R.layout.view_simple_button)
val btn = btnContainer.findViewById<Button>(R.id.button)
val show = StringId("show")
@ -55,28 +119,30 @@ class PersonalizationFragment : BaseCardActionFragment() {
multiAction.state = if (it == show) hide else show
}
multiAction.performAction(hide)
itemContainer.addView(btnContainer)
contentContainer.addView(btnContainer)
}
override fun initFab() {
actionFab.setOnClickListener { actionVM.invokeMainAction() }
override fun handleResponseCardData(card: Card) {
super.handleResponseCardData(card)
val bundle = ResponseFragment.setTittle(R.string.fg_name_response_personalization)
navigateTo(R.id.action_nav_card_action_to_response_screen, bundle, null)
}
override fun createWidgets(widgetCreatedCallback: () -> Unit) {
Log.d(this, "createWidgets")
val itemList = mutableListOf<Item>()
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_fg_peronalization, menu)
super.onCreateOptionsMenu(menu, inflater)
}
actionVM.ldItemList.observe(viewLifecycleOwner, Observer { list ->
Log.d(this, "ldBlockList size: ${list.size}")
itemList.clear()
itemList.addAll(list)
itemList.forEach { WidgetBuilder(PersonalizationItemBuilder()).build(it, itemContainer) }
actionVM.attachToPayload(mutableMapOf(
PayloadKey.actionView to this as ActionView,
PayloadKey.itemList to itemList
))
widgetCreatedCallback()
})
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val store = PersonalizationConfigStore(requireContext())
val presetManager = PersonalizationPresetManager(itemsManager, store, this)
val result = when (item.itemId) {
R.id.action_reset -> presetManager.resetToDefault()
R.id.action_save -> presetManager.savePreset()
R.id.action_load -> presetManager.loadPreset()
else -> null
}
return if (result == null) super.onOptionsItemSelected(item) else true
}
override fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)?) {
@ -89,8 +155,51 @@ class PersonalizationFragment : BaseCardActionFragment() {
}
}
override fun responseCardDataHandled(card: Card?) {
super.responseCardDataHandled(card)
navigateTo(R.id.action_nav_card_action_to_response_screen)
override fun showSavePresetDialog(onOk: SafeValueChanged<String>) {
val dlgController = DialogController()
val dlg = dlgController.createAlert(requireActivity(), R.layout.dlg_personalization_preset_save)
dlg.setTitle(R.string.menu_personalization_preset_save)
dlg.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.btn_cancel)) { dialog, which -> }
dlg.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.btn_ok)) { dialog, which ->
val tvName = dlgController.view?.findViewById<EditText>(R.id.et_item)
?: return@setButton
val name = tvName.text.toString()
if (name.isEmpty()) showSnackbar("Not saved")
else onOk.invoke(name)
}
dlgController.onShowCallback = {
dlgController.view?.findViewById<TextView>(R.id.et_item)?.let {
post(150) {
it.requestFocus()
val imm = getSystemService(requireContext(), InputMethodManager::class.java)
imm?.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
}
}
}
dlgController.show()
}
override fun showLoadPresetDialog(namesList: List<String>, onChoose: SafeValueChanged<String>, onDelete: SafeValueChanged<String>) {
val dlgController = DialogController()
dlgController.createAlert(requireActivity(), R.layout.dlg_personalization_preset_load)
.setTitle(R.string.menu_personalization_preset_load)
val rvPresetNames: RecyclerView = dlgController.view?.findViewById(R.id.recycler_view) ?: return
val layoutManager = LinearLayoutManager(context)
rvPresetNames.layoutManager = layoutManager
rvPresetNames.addItemDecoration(DividerItemDecoration(activity, layoutManager.orientation))
val adapter = RvPresetNamesAdapter({
onChoose(it)
dlgController.dismiss()
}, {
onDelete(it)
if (rvPresetNames.adapter?.itemCount == 0)
dlgController.dismiss()
})
adapter.setItemList(namesList.toMutableList())
rvPresetNames.adapter = adapter
dlgController.show()
}
}

View file

@ -0,0 +1,47 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.presets
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.variants.personalize.PersonalizationConfigStore
import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizationConfigConverter
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig
class PersonalizationPresetManager(
private val itemsManager: ItemsManager,
private val store: PersonalizationConfigStore,
private val view: PersonalizationPresetView
) {
fun resetToDefault() {
val config = PersonalizationConfig.default()
val converter = PersonalizationConfigConverter()
itemsManager.updateByItemList(converter.convert(config))
store.save(config)
}
fun loadPreset() {
val presets = store.restoreAll()
presets.remove(PersonalizationConfigStore.defaultKey)
val namesList = presets.map { it.key }.toMutableList()
if (namesList.isEmpty()) {
view.showSnackbar(R.string.error_nothing_to_load)
return
}
view.showLoadPresetDialog(namesList, {
val converter = PersonalizationConfigConverter()
val config = store.restore(it)
itemsManager.updateByItemList(converter.convert(config))
}, {
store.delete(it)
})
}
fun savePreset() {
view.showSavePresetDialog { name ->
val converter = PersonalizationConfigConverter()
val config = converter.convert(itemsManager.getItems(), PersonalizationConfig.default())
store.save(name, config)
}
}
}

View file

@ -0,0 +1,9 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.presets
import com.tangem.tangemtest._arch.structure.abstraction.SafeValueChanged
import com.tangem.tangemtest.ucase.tunnel.SnackbarHolder
interface PersonalizationPresetView : SnackbarHolder {
fun showSavePresetDialog(onOk: SafeValueChanged<String>)
fun showLoadPresetDialog(namesList: List<String>, onChoose: SafeValueChanged<String>, onDelete: SafeValueChanged<String>)
}

View file

@ -0,0 +1,47 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.presets
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.abstraction.SafeValueChanged
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.RvVH
/**
[REDACTED_AUTHOR]
*/
class RvPresetNamesAdapter(
private val onItemClicked: SafeValueChanged<String>,
private val onDeleteClicked: SafeValueChanged<String>
) : RvBaseAdapter<PresetNameVH, String>() {
override fun onBindViewHolder(holder: PresetNameVH, position: Int) {
holder.bindData(itemList[position])
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PresetNameVH {
val view = parent.inflate<View>(R.layout.vh_personalization_preset_name, false)
return PresetNameVH(view, onItemClicked, { position, value ->
itemList.removeAt(position)
this.notifyItemRemoved(position)
onDeleteClicked(value)
})
}
}
class PresetNameVH(
itemView: View,
private val onItemClicked: SafeValueChanged<String>,
private val onDeleteClicked: (Int, String) -> Unit
) : RvVH<String>(itemView) {
private val tvName = itemView.findViewById<TextView>(R.id.tv_name)
private val btnDelete = itemView.findViewById<View>(R.id.btn_delete)
override fun onDataBound(data: String) {
tvName.text = data
tvName.isClickable = false
itemView.setOnClickListener { onItemClicked(data) }
btnDelete.setOnClickListener { onDeleteClicked(absoluteAdapterPosition, data) }
}
}

View file

@ -2,11 +2,10 @@ package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
import android.view.ViewGroup
import android.widget.TextView
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ViewState
import com.tangem.tangemtest.extensions.view.beginDelayedTransition
/**
[REDACTED_AUTHOR]
@ -31,7 +30,7 @@ abstract class DescriptionWidget(
if (description.isEmpty()) return
tv.text = description
TransitionManager.beginDelayedTransition(view.parent as ViewGroup, AutoTransition())
(view.parent as ViewGroup).beginDelayedTransition()
descriptionContainer.visibility = state
}
}

View file

@ -12,7 +12,7 @@ import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
class PersonalizationItemBuilder : ItemWidgetBuilder {
override fun build(item: BaseItem, parent: ViewGroup): ViewWidget? {
return when (item) {
is TextItem -> GroupTitleWidget(parent, item)
is TextItem -> TextHeaderWidget(parent, item)
is EditTextItem -> EditTextWidget(parent, item)
is NumberItem -> NumberWidget(parent, item)
is BoolItem -> SwitchWidget(parent, item)

View file

@ -46,9 +46,8 @@ class SpinnerWidget(
}
spinner.onItemSelectedListener = onItemSelectedListener
item.viewModel.onDataUpdated = {
val selectedItem = it as? String
spinner.onItemSelectedListener = null
viewModel.itemList.firstOrNull { item -> item.value == selectedItem }?.let { keyValue ->
viewModel.itemList.firstOrNull { item -> item.value == it }?.let { keyValue ->
val position = viewModel.itemList.indexOf(keyValue)
spinner.setSelection(position)
}

View file

@ -8,8 +8,8 @@ import com.tangem.tangemtest._arch.structure.impl.TextItem
/**
[REDACTED_AUTHOR]
*/
class GroupTitleWidget(parent: ViewGroup, data: TextItem) : DescriptionWidget(parent, data) {
override fun getLayoutId(): Int = R.layout.w_personalize_item_text
class TextHeaderWidget(parent: ViewGroup, data: TextItem) : DescriptionWidget(parent, data) {
override fun getLayoutId(): Int = R.layout.w_personalize_item_header
private val tvName = view.findViewById<TextView>(R.id.tv_name)

View file

@ -29,6 +29,7 @@ enum class CardId : ResponseId {
paymentFlowVersion,
userCounter,
userProtectedCounter,
empty
}
enum class CardDataId : ResponseId {
@ -41,4 +42,15 @@ enum class CardDataId : ResponseId {
tokenSymbol,
tokenContractAddress,
tokenDecimal,
}
enum class SignId : ResponseId {
cid,
walletSignedHashes,
walletRemainingSignatures,
signature,
}
enum class DepersonalizeId: ResponseId {
isSuccess
}

View file

@ -0,0 +1,27 @@
package com.tangem.tangemtest.ucase.variants.responses.converter
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.*
import com.tangem.tangemtest.ucase.domain.responses.ResponseFieldConverter
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
abstract class BaseResponseConverter<M> : ModelToItems<M> {
protected val fieldConverter = ResponseFieldConverter()
protected open fun createGroup(id: Id, colorId: Int? = null, addHeaderItem: Boolean = true): ItemGroup {
val group = if (colorId == null) SimpleItemGroup(id)
else SimpleItemGroup(id, BaseItemViewModel(viewState = ViewState(bgColor = colorId)))
if (addHeaderItem) group.addItem(TextHeaderItem(id, ""))
return group
}
protected open fun valueToString(value: Any?): String? {
if (value == null) return null
return stringOf(value)
}
}

View file

@ -1,33 +1,27 @@
package com.tangem.tangemtest.ucase.variants.responses.converter
import com.tangem.commands.Card
import com.tangem.commands.CardData
import com.tangem.commands.Settings
import com.tangem.commands.SettingsMask
import com.tangem.common.extensions.toHexString
import com.tangem.commands.*
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.StringId
import com.tangem.tangemtest._arch.structure.abstraction.*
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ItemGroup
import com.tangem.tangemtest._arch.structure.abstraction.iterate
import com.tangem.tangemtest._arch.structure.impl.BoolItem
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest.ucase.variants.personalize.BlockId
import com.tangem.tangemtest.ucase.variants.responses.CardDataId
import com.tangem.tangemtest.ucase.variants.responses.CardId
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
/**
[REDACTED_AUTHOR]
*/
class CardConverter : ModelToItems<Card> {
class CardConverter : BaseResponseConverter<Card>() {
override fun convert(from: Card): List<Item> {
val itemList = mutableListOf<Item>()
// val holder = GsonInitializer()
// itemList.add(TextItem(Additional.JSON_INCOMING, holder.gson.toJson(from)))
itemList.add(simpleFields(from))
itemList.add(cardData(from.cardData))
itemList.add(settingsMask(from.settingsMask))
commonGroup(itemList, from)
cardDataGroup(itemList, from.cardData)
settingsMaskGroup(itemList, from.settingsMask)
hideEmptyNullFields(itemList)
return itemList
@ -35,71 +29,66 @@ class CardConverter : ModelToItems<Card> {
private fun hideEmptyNullFields(itemList: MutableList<Item>) {
itemList.iterate {
val data = it.getData<Any?>()
val isHidden = if (data == null) true
else when (data) {
is String -> data.isEmpty()
else -> false
}
if (it is ItemGroup) return@iterate
if (isHidden) it.viewModel.viewState.isVisibleState.value = false
if (it.getData<Any?>() == null) it.viewModel.viewState.isVisibleState.value = false
}
}
private fun simpleFields(from: Card): Item {
val group = createGroup(BlockId.Common)
private fun commonGroup(itemList: MutableList<Item>, from: Card) {
val group = createGroup(CardId.empty, addHeaderItem = false)
itemList.add(group)
group.addItem(TextItem(CardId.cardId, from.cardId))
group.addItem(TextItem(CardId.manufacturerName, from.manufacturerName))
group.addItem(TextItem(CardId.status, stringOf(from.status)))
group.addItem(TextItem(CardId.status, valueToString(from.status)))
group.addItem(TextItem(CardId.firmwareVersion, from.firmwareVersion))
group.addItem(TextItem(CardId.cardPublicKey, from.cardPublicKey?.toHexString()))
group.addItem(TextItem(CardId.issuerPublicKey, from.issuerPublicKey?.toHexString()))
group.addItem(TextItem(CardId.curve, stringOf(from.curve)))
group.addItem(TextItem(CardId.maxSignatures, stringOf(from.maxSignatures)))
group.addItem(TextItem(CardId.signingMethod, stringOf(from.signingMethod?.rawValue)))
group.addItem(TextItem(CardId.pauseBeforePin2, stringOf(from.pauseBeforePin2)))
group.addItem(TextItem(CardId.walletPublicKey, stringOf(from.walletPublicKey)))
group.addItem(TextItem(CardId.walletRemainingSignatures, stringOf(from.walletRemainingSignatures)))
group.addItem(TextItem(CardId.walletSignedHashes, stringOf(from.walletSignedHashes)))
group.addItem(TextItem(CardId.health, stringOf(from.health)))
group.addItem(TextItem(CardId.isActivated, stringOf(from.isActivated)))
group.addItem(TextItem(CardId.activationSeed, stringOf(from.activationSeed)))
group.addItem(TextItem(CardId.paymentFlowVersion, stringOf(from.paymentFlowVersion)))
group.addItem(TextItem(CardId.userCounter, stringOf(from.userCounter)))
// block.addItem(TextItem(CardId.UserProtectedCounter, stringOf(from.userProtectedCounter)))
return group
group.addItem(TextItem(CardId.cardPublicKey, fieldConverter.byteArray(from.cardPublicKey)))
group.addItem(TextItem(CardId.issuerPublicKey, fieldConverter.byteArray(from.issuerPublicKey)))
group.addItem(TextItem(CardId.curve, valueToString(from.curve)))
group.addItem(TextItem(CardId.maxSignatures, valueToString(from.maxSignatures)))
group.addItem(TextItem(CardId.pauseBeforePin2, valueToString(from.pauseBeforePin2)))
group.addItem(TextItem(CardId.walletPublicKey, fieldConverter.byteArray(from.walletPublicKey)))
group.addItem(TextItem(CardId.walletRemainingSignatures, valueToString(from.walletRemainingSignatures)))
group.addItem(TextItem(CardId.walletSignedHashes, valueToString(from.walletSignedHashes)))
group.addItem(TextItem(CardId.health, valueToString(from.health)))
group.addItem(TextItem(CardId.isActivated, valueToString(from.isActivated)))
group.addItem(TextItem(CardId.activationSeed, valueToString(from.activationSeed)))
group.addItem(TextItem(CardId.paymentFlowVersion, valueToString(from.paymentFlowVersion)))
group.addItem(TextItem(CardId.userCounter, valueToString(from.userCounter)))
val signingMethodMask = from.signingMethods ?: return
group.addItem(TextHeaderItem(CardId.signingMethod, ""))
SigningMethod.values().forEach { group.addItem(BoolItem(StringId(it.name), signingMethodMask.contains(it))) }
}
private fun cardData(from: CardData?): Item {
val group = createGroup(BlockId.Common, R.color.group_card_data)
val data = from ?: return group
private fun cardDataGroup(itemList: MutableList<Item>, cardData: CardData?) {
val data = cardData ?: return
// group.addItem(TextItem(StringResId(R.string.response_card_card_data)))
val group = createGroup(CardId.cardData, R.color.group_card_data)
itemList.add(group)
group.addItem(TextItem(CardDataId.batchId, data.batchId))
// Format: Year (2 bytes) | Month (1 byte) | Day (1 byte)
group.addItem(TextItem(CardDataId.manufactureDateTime, stringOf(data.manufactureDateTime)))
group.addItem(TextItem(CardDataId.manufactureDateTime, valueToString(data.manufactureDateTime)))
group.addItem(TextItem(CardDataId.issuerName, data.issuerName))
group.addItem(TextItem(CardDataId.blockchainName, data.blockchainName))
group.addItem(TextItem(CardDataId.manufacturerSignature, data.manufacturerSignature?.toHexString()))
group.addItem(TextItem(CardDataId.productMask, stringOf(data.productMask?.rawValue)))
group.addItem(TextItem(CardDataId.manufacturerSignature, fieldConverter.byteArray(data.manufacturerSignature)))
group.addItem(TextItem(CardDataId.tokenSymbol, data.tokenSymbol))
group.addItem(TextItem(CardDataId.tokenContractAddress, data.tokenContractAddress))
group.addItem(TextItem(CardDataId.tokenDecimal, data.tokenSymbol))
return group
val productMask = data.productMask ?: return
group.addItem(TextHeaderItem(CardDataId.productMask, ""))
Product.values().forEach { group.addItem(BoolItem(StringId(it.name), productMask.contains(it))) }
}
private fun settingsMask(from: SettingsMask?): Item {
val group = createGroup(CardId.settingsMask, R.color.group_signing_method)
val data = from ?: return group
private fun settingsMaskGroup(itemList: MutableList<Item>, from: SettingsMask?) {
val data = from ?: return
val group = createGroup(CardId.settingsMask, R.color.group_settings_mask)
itemList.add(group)
// group.addItem(TextItem(StringResId(R.string.response_card_settings_mask)))
Settings.values().forEach { group.addItem(BoolItem(StringId(it.name), data.contains(it))) }
return group
}
private fun createGroup(id: Id, colorId: Int? = null): ItemGroup {
return if (colorId == null) SimpleItemGroup(id)
else SimpleItemGroup(id, BaseItemViewModel(viewState = ViewState(bgColor = colorId)))
}
}

View file

@ -1,36 +1,30 @@
package com.tangem.tangemtest.ucase.variants.responses.converter
import com.tangem.commands.Card
import com.tangem.commands.SignResponse
import com.tangem.commands.personalization.DepersonalizeResponse
import com.tangem.common.CompletionResult
import com.tangem.tangemtest._arch.structure.StringId
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ModelToItems
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest.ucase.variants.responses.DepersonalizeId
import com.tangem.tangemtest.ucase.variants.responses.SignId
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
class ReadEventConverter : ModelToItems<CompletionResult.Success<Card>> {
override fun convert(from: CompletionResult.Success<Card>): List<Item> = CardConverter().convert(from.data)
}
class SignResponseConverter : ModelToItems<SignResponse> {
class SignResponseConverter : BaseResponseConverter<SignResponse>() {
override fun convert(from: SignResponse): List<Item> {
return listOf(
TextItem(StringId("CID"), from.cardId),
TextItem(StringId("Wallet signed hashes"), stringOf(from.walletSignedHashes)),
TextItem(StringId("Wallet remaining signatures"), stringOf(from.walletRemainingSignatures)),
TextItem(StringId("Signature"), stringOf(from.signature))
TextItem(SignId.cid, from.cardId),
TextItem(SignId.walletSignedHashes, valueToString(from.walletSignedHashes)),
TextItem(SignId.walletRemainingSignatures, valueToString(from.walletRemainingSignatures)),
TextItem(SignId.signature, fieldConverter.byteArray(from.signature))
)
}
}
class DepersonalizeResponseConverter : ModelToItems<DepersonalizeResponse> {
class DepersonalizeResponseConverter : BaseResponseConverter<DepersonalizeResponse>() {
override fun convert(from: DepersonalizeResponse): List<Item> {
return listOf(TextItem(StringId("Is success"), stringOf(from.success)))
return listOf(TextItem(DepersonalizeId.isSuccess, stringOf(from.success)))
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.tangemtest.ucase.variants.responses.item
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel
import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel
import com.tangem.tangemtest._arch.structure.abstraction.ViewState
import com.tangem.tangemtest._arch.structure.impl.TypedItem
open class TextHeaderItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
: this(id, BaseItemViewModel(value, viewState))
}

View file

@ -3,6 +3,8 @@ package com.tangem.tangemtest.ucase.variants.responses.ui
import android.os.Bundle
import android.view.*
import android.widget.LinearLayout
import androidx.annotation.StringRes
import androidx.core.os.bundleOf
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
@ -23,24 +25,25 @@ open class ResponseFragment : BaseFragment() {
private val mainActivityVM: MainViewModel by activityViewModels()
private val selfVM: ResponseViewModel by viewModels()
private val itemContainer: ViewGroup by lazy { mainView.findViewById<LinearLayout>(R.id.ll_container) }
private val itemContainer: ViewGroup by lazy { mainView.findViewById<LinearLayout>(R.id.ll_content_container) }
override fun getLayoutId(): Int = R.layout.fg_card_response
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
setTittle()
}
private fun setTittle() {
val titleId = selfVM.determineTitleId(mainActivityVM.commandResponse)
val titleId = getTittleId(arguments) ?: selfVM.determineTitleId(mainActivityVM.commandResponse)
activity?.setTitle(titleId)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
buildWidgets()
listenDescriptionSwitchChanges()
}
@ -58,10 +61,8 @@ open class ResponseFragment : BaseFragment() {
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_fg_response, menu)
super.onCreateOptionsMenu(menu, inflater)
val menuItem = menu.findItem(R.id.action_share)
menuItem.isVisible = true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -72,4 +73,12 @@ open class ResponseFragment : BaseFragment() {
}
return super.onOptionsItemSelected(item)
}
companion object {
private val argTittle = "tittle"
fun setTittle(@StringRes id: Int): Bundle = bundleOf(Pair(argTittle, id))
private fun getTittleId(args: Bundle?): Int? = args?.getInt(argTittle)
}
}

View file

@ -6,6 +6,7 @@ import com.tangem.tangemtest._arch.structure.impl.BoolItem
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest._arch.widget.ItemWidgetBuilder
import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
/**
[REDACTED_AUTHOR]
@ -13,6 +14,7 @@ import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
class ResponseItemBuilder : ItemWidgetBuilder {
override fun build(item: BaseItem, parent: ViewGroup): ViewWidget? {
return when (item) {
is TextHeaderItem -> ResponseHeaderWidget(parent, item)
is TextItem -> ResponseTextWidget(parent, item)
is BoolItem -> CheckBoxWidget(parent, item)
else -> null

View file

@ -4,6 +4,7 @@ import android.view.ViewGroup
import android.widget.TextView
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
/**
[REDACTED_AUTHOR]
@ -27,4 +28,18 @@ class ResponseTextWidget(
tvName.text = getName()
tvValue.text = data
}
}
class ResponseHeaderWidget(
parent: ViewGroup,
private val typedItem: TextHeaderItem
) : ResponseWidget(parent, typedItem) {
override fun getLayoutId(): Int = R.layout.w_response_item_header
private val tvName: TextView = view.findViewById(R.id.tv_name)
init {
tvName.text = getName()
}
}

View file

@ -14,7 +14,7 @@ import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
abstract class ResponseWidget(parent: ViewGroup, item: Item): DescriptionWidget(parent, item) {
abstract class ResponseWidget(parent: ViewGroup, item: Item) : DescriptionWidget(parent, item) {
init {
view.setOnClickListener {

View file

@ -1,10 +1,18 @@
package com.tangem.tangemtest.ucase.variants.scan.ui
import android.os.Bundle
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import com.tangem.commands.Card
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.ScanItemsManager
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
import com.tangem.tangemtest.ucase.variants.responses.ui.ResponseFragment
import ru.dev.gbixahue.eu4d.lib.android._android.views.dpToPx
/**
[REDACTED_AUTHOR]
@ -13,14 +21,36 @@ class ScanActionFragment : BaseCardActionFragment() {
override val itemsManager: ItemsManager by lazy { ScanItemsManager() }
override fun getLayoutId(): Int = R.layout.fg_action_card_scan
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun initFab() {
val howToUseView = createHowToUse()
contentContainer.layoutParams = FrameLayout.LayoutParams(-1, -1, Gravity.CENTER)
contentContainer.addView(howToUseView)
}
private fun createHowToUse(): View {
val fl = FrameLayout(requireContext())
fl.layoutParams = FrameLayout.LayoutParams(-1, -1, Gravity.CENTER)
val tv = TextView(requireContext()).apply {
val padding = dpToPx(16f).toInt()
setPadding(padding, 0, padding, 0)
gravity = Gravity.CENTER
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
setText(R.string.htu_scan_action)
}
fl.addView(tv)
return fl
}
override fun initViews() {
swrLayout.isEnabled = false
actionFab.setOnClickListener { actionVM.invokeMainAction() }
}
override fun responseCardDataHandled(card: Card?) {
super.responseCardDataHandled(card)
navigateTo(R.id.action_nav_card_action_to_response_screen)
override fun handleResponseCardData(card: Card) {
super.handleResponseCardData(card)
val bundle = ResponseFragment.setTittle(R.string.fg_name_response_scan)
navigateTo(R.id.action_nav_card_action_to_response_screen, bundle, null)
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.tangemtest.ucase.variants.sign.ui
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.SignItemsManager
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
@ -11,6 +10,4 @@ import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
class SignActionFragment : BaseCardActionFragment() {
override val itemsManager: ItemsManager by lazy { SignItemsManager() }
override fun getLayoutId(): Int = R.layout.fg_action_card_sign
}

View file

@ -10,7 +10,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"/>
<fragment
android:id="@+id/nav_host_fragment"

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="@dimen/def_indent"
android:paddingBottom="@dimen/def_half_indent">
<include
layout="@layout/m_divider_h"
android:layout_width="match_parent"
android:layout_height="2dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/def_indent"
android:layout_marginEnd="@dimen/def_indent" />
</LinearLayout>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_item"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
app:boxBackgroundColor="@android:color/transparent"
android:hint="Enter a preset name"
tools:hint="Field name">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Some text field" />
</com.google.android.material.textfield.TextInputLayout>
</FrameLayout>

View file

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
<include layout="@layout/w_action_response_json" />
</LinearLayout>
</ScrollView>
<TextView
android:id="@+id/tv_screen_stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:textSize="18sp"
android:layout_margin="@dimen/def_double_indent"
android:text="@string/empty_screen_stub" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/def_indent"
android:layout_marginBottom="@dimen/def_double_indent"
android:src="@drawable/ic_nfc" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
<include layout="@layout/w_action_response_json" />
</LinearLayout>
</ScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/def_indent"
android:layout_marginBottom="@dimen/def_double_indent"
android:src="@drawable/ic_nfc" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -5,29 +5,17 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
<include layout="@layout/v_content_container" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/def_indent"
android:layout_marginBottom="@dimen/def_double_indent"
android:src="@drawable/ic_nfc"
app:layout_anchor="@id/scroll_view"
app:layout_anchor="@id/swr_layout"
app:layout_anchorGravity="bottom|end" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -10,7 +10,7 @@
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_container"
android:id="@+id/ll_content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
<include layout="@layout/w_action_response_json" />
</LinearLayout>
</ScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/def_indent"
android:layout_marginBottom="@dimen/def_double_indent"
android:src="@drawable/ic_nfc" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swr_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_items"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/item_header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<LinearLayout
android:id="@+id/item_list_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<LinearLayout
android:id="@+id/item_footer_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</LinearLayout>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_name"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_delete"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:hint="Field name" />
<ImageView
android:id="@+id/btn_delete"
android:layout_width="26dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@android:drawable/ic_menu_delete" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/delimiter"
android:background="@color/field_header_background"
android:minHeight="@dimen/iw_layout_frame_header_min_height"
android:paddingTop="6dp"
android:paddingBottom="6dp">

View file

@ -4,7 +4,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/w_personalize_item_text" />
<include layout="@layout/w_personalize_item_header" />
<include layout="@layout/w_personalize_item_edit_text" />

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/field_header_background">
<LinearLayout
android:id="@+id/container_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/def_indent"
android:paddingBottom="8dp">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/field_content"
android:textSize="16sp"
tools:text="Custom blockchain" />
<include layout="@layout/w_field_description" />
</LinearLayout>
<include
layout="@layout/m_divider_h"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom" />
</FrameLayout>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_toggle_description_visibility"
android:checkable="true"
android:title="@string/menu_main_description"
app:showAsAction="never" />
</menu>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/menu_group_personalization_preset">
<item
android:id="@+id/action_reset"
android:title="@string/menu_personalization_preset_reset" />
<item
android:id="@+id/action_save"
android:title="@string/menu_personalization_preset_save" />
<item
android:id="@+id/action_load"
android:title="@string/menu_personalization_preset_load" />
</group>
</menu>

View file

@ -5,14 +5,7 @@
<item
android:id="@+id/action_share"
android:icon="@drawable/ic_share_white_18dp"
android:title="Share"
android:visible="false"
app:showAsAction="always" />
<item
android:id="@+id/action_favorite"
android:title=""
app:actionLayout="@layout/menu_item_switch"
app:showAsAction="always" />
android:title="@string/menu_response_share"
app:showAsAction="ifRoom" />
</menu>

View file

@ -6,7 +6,13 @@
<action
android:id="@+id/action_nav_card_action_to_response_screen"
app:destination="@+id/nav_card_response" />
app:destination="@+id/nav_card_response"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:popUpTo="@id/nav_entry_point"
app:popUpToInclusive="false" />
<fragment
android:id="@+id/nav_entry_point"
@ -57,25 +63,25 @@
android:id="@+id/nav_scan"
android:name="com.tangem.tangemtest.ucase.variants.scan.ui.ScanActionFragment"
android:label="@string/action_card_scan"
tools:layout="@layout/fg_action_card_scan" />
tools:layout="@layout/fg_base_action_layout" />
<fragment
android:id="@+id/nav_sign"
android:name="com.tangem.tangemtest.ucase.variants.sign.ui.SignActionFragment"
android:label="@string/action_card_sign"
tools:layout="@layout/fg_action_card_sign" />
tools:layout="@layout/fg_base_action_layout" />
<fragment
android:id="@+id/nav_personalize"
android:name="com.tangem.tangemtest.ucase.variants.personalize.ui.PersonalizationFragment"
android:label="@string/action_personalize"
tools:layout="@layout/fg_personalization" />
tools:layout="@layout/fg_base_action_layout" />
<fragment
android:id="@+id/nav_depersonalize"
android:name="com.tangem.tangemtest.ucase.variants.depersonalize.ui.DepersonalizeActionFragment"
android:label="@string/action_depersonalize"
tools:layout="@layout/fg_depersonalize" />
tools:layout="@layout/fg_base_action_layout" />
<fragment
android:id="@+id/nav_issuer_read_data"

View file

@ -9,12 +9,13 @@
<color name="action_name">#2B2B2B</color>
<color name="field_header_background">#C3C3C3</color>
<color name="field_info">#888888</color>
<color name="field_content">#303030</color>
<color name="field_description">@color/field_info</color>
<color name="group_card_data">#D7E4F3</color>
<color name="group_signing_method">#D7F3E6</color>
<color name="group_settings_mask">#D7F3E6</color>
<color name="switchTrack">#C6C6C6</color>

View file

@ -1,14 +1,25 @@
<resources>
<string name="app_name">Tangem Development Kit</string>
<string name="menu_main_description">Description</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="btn_delete">Delete</string>
<string name="btn_ok">OK</string>
<string name="btn_cancel">Cancel</string>
<string name="btn_save">Save</string>
<string name="btn_load">Load</string>
<string name="unknown">unknown</string>
<string name="error_nothing_to_load">Nothing to load</string>
<string name="fg_name_entry_point">@string/app_name</string>
<string name="fg_name_response_scan">Scan response</string>
<string name="fg_name_response_scan">Read response</string>
<string name="fg_name_response_sign">Sign response</string>
<string name="fg_name_response_personalization">Personalization response</string>
<string name="fg_name_response_depersonalization">Depersonalization response</string>
<string name="unknown">unknown</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="switch_description">Docs</string>
<string name="card_error_not_personalized">Your card hasn\'t been personalized yet. You need to run personalize command first.</string>
@ -18,7 +29,7 @@
<string name="stub">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit</string>
<!-- <string name="stub">Sed ut perspiciatis unde omnis iste natus</string>-->
<string name="show_rare_fields">Show rarely used fields</string>
<string name="hide_rare_fields">Hide</string>
<string name="show_rare_fields">Show all fields</string>
<string name="hide_rare_fields">Hide fields</string>
</resources>

View file

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="menu_personalization_preset_reset">Reset to default</string>
<string name="menu_personalization_preset_save">Save configuration</string>
<string name="menu_personalization_preset_load">Load configuration</string>
<string name="personalize">Personalize</string>
<string name="depersonalize">Depersonalize</string>
<string name="pers_block_card_number">Card number</string>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="menu_response_share">Share</string>
<!-- Field names - Response: Card -->
<string name="response_card_cid">CID</string>
@ -56,7 +57,7 @@
<string name="info_response_card_status">Current status of the card</string>
<string name="info_response_card_firmware_version">Version of Tangem COS</string>
<string name="info_response_card_public_key">Public key that is used to authenticate the card against manufacturers database. It is generated one time during card manufacturing. See Security section for more details.</string>
<string name="info_response_card_settings_mask">Card settings defined by personalization (bit mask: 0 Enabled, 1 Disabled):</string>
<string name="info_response_card_settings_mask">Card settings defined by personalization.</string>
<string name="info_response_card_is_reusable">Defines what happens when user calls PURGE_WALLET command:
\n0 - Card will switch to Purged state
\n1 - Card will switch to Empty state and let create a new wallet again</string>
@ -71,29 +72,29 @@
<string name="info_response_card_one_apdu_at_time">Card will execute only one command during one communication session, thus requiring user to physically take the card away from the host after each action (all commands except for READ_CARD).</string>
<string name="info_response_card_use_ndef">Whether the card should emulate NDEF. In default configuration, two NDEF records are loaded during personalization: (1) Tangem web site address, (2) name of Android App package in Google Play Store.</string>
<string name="info_response_card_use_dynamic_ndef">0 Disable dynamic generation of NDEF for iOS. See Dynamic NDEF section for more details.
/n1 Enable dynamic NDEF for iOS.</string>
\n1 Enable dynamic NDEF for iOS.</string>
<string name="info_response_card_smart_security_delay">Security delay Pause_Before_PIN2 will not be applied if PIN2 is not default.</string>
<string name="info_response_card_allow_unencrypted">Whether the card supports unencrypted NFC communication. See NFC communication section for more details.</string>
<string name="info_response_card_allow_fast_encryption">Whether the card supports fast encrypted NFC communication. See NFC communication section for more details.</string>
<string name="info_response_card_protect_issuer_data_against_replay">0 No replay protection on write issuer data
/n1 Enable replay protection on write issuer data (card will require additional Issuer_Data_Counter incremented on each write)</string>
\n1 Enable replay protection on write issuer data (card will require additional Issuer_Data_Counter incremented on each write)</string>
<string name="info_response_card_allow_select_blockchain">0 Wallet elliptic curve and blockchain information stored during PERSONALIZE command and never change
/n1 Wallet elliptic curve and blockchain information can be changed on CREATE_WALLET command</string>
\n1 Wallet elliptic curve and blockchain information can be changed on CREATE_WALLET command</string>
<string name="info_response_card_disable_precomputed_ndef">0 Enable precomputed dynamic NDEF to work around iPhone 7+ NFC bug.
/n1 Disable precomputed dynamic NDEF. See Dynamic NDEF section for more details.</string>
\n1 Disable precomputed dynamic NDEF. See Dynamic NDEF section for more details.</string>
<string name="info_response_card_security_delay_if_validated">0 Enforce security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).
/n1 Skip security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).</string>
\n1 Skip security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).</string>
<string name="info_response_card_skip_pin2_cvc_if_validated_by_issuer">0 Require and check PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).
/n1 Skip checking PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).</string>
\n1 Skip checking PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).</string>
<string name="info_response_card_skip_security_delay_if_validated_by_linked_terminal">1 - Store Terminal_PublicKey public key of linked terminal no each SIGN command, skip security delay if valid signature of transaction is made with Terminal_PrivateKey is provided in SIGN command</string>
<string name="info_response_card_restrict_overwrite_issuer_ex_data"></string>
<string name="info_info_response_card_prohibit_overwriting_issuer_ex_data"></string>
<string name="info_response_card_require_terminal_tx_sig">0 Skip checking terminals signature when signing POS transaction
/n1 Check terminals signature when signing POS transaction</string>
\n1 Check terminals signature when signing POS transaction</string>
<string name="info_response_card_require_terminal_cert_sig">0 Skip checking acquirers signature of terminal certificate when signing POS transaction
/n1 Check acquirers signature of terminal certificate when signing POS transaction</string>
\n1 Check acquirers signature of terminal certificate when signing POS transaction</string>
<string name="info_response_card_check_pin3">0 Additionally encrypt POS transaction signature with key derived from PIN3 when the transaction amount exceeds PIN3 floor limit
/n1 Require terminal to send PIN3 to card when the POS transaction amount exceeds PIN3_Floor_Limit</string>
\n1 Require terminal to send PIN3 to card when the POS transaction amount exceeds PIN3_Floor_Limit</string>
<string name="info_response_card_card_data">Detailed information about card contents. Format is defined by the card issuer. Cards complaint with Tangem Wallet application should have TLV format described in Personalization section.</string>
<string name="info_response_card_issuer_data_public_key">Public key that is used by the card issuer to sign Issuer_Data field. See Security section for more details.</string>
<string name="info_response_card_curve">Explicit text name of the elliptic curve used for all wallet key operations.</string>
@ -105,12 +106,12 @@
<string name="info_response_card_wallet_signed_hashes">Total number of signed single hashes returned by the card in SIGN command responses since card personalization. Sums up array elements within all SIGN commands.</string>
<string name="info_response_card_health">Any non-zero value indicates that the card experiences some hardware problems. User should withdraw the value to other blockchain wallet as soon as possible. Non-zero Health tag will also appear in responses of all other commands.</string>
<string name="info_response_card_is_activated">Whether the card requires issuers confirmation of activation.
/n0 card will require issuers confirmation of activation,
/notherwise this field will not be returned (card is activated and operational).</string>
\n0 card will require issuers confirmation of activation,
\notherwise this field will not be returned (card is activated and operational).</string>
<string name="info_response_card_activation_seed">A random challenge generated by PERSONALIZE command that should be signed and returned to COS by the issuer to confirm the card has been activated. See ACTIVATE_CARD command for more details.
/nThis field will not be returned if the card is activated.</string>
\nThis field will not be returned if the card is activated.</string>
<string name="info_response_card_payment_flow_version">Version of POS payment scheme supported by COS ([0x02,0x01] for version 2.30)
/nReturned only if SigningMethod 6 enabling POS transactions is supported by card.</string>
\nReturned only if SigningMethod 6 enabling POS transactions is supported by card.</string>
<string name="info_response_card_user_counter">This value can be initialized by App and will be increased by COS with the execution of each SIGN command. For example, this field can store blockchain “nonce” for a quick one-touch transaction on POS terminals. Returned only if SigningMethod =6.</string>
<string name="info_response_card_user_protected_counter">This value can be initialized by App (with PIN2 confirmation) and will be increased by COS with the execution of each SIGN command. For example, this field can store blockchain “nonce” for a quick one-touch transaction on POS terminals. Returned only if SigningMethod =6.</string>
@ -135,4 +136,23 @@
<string name="info_response_card_card_data_token_symbol"></string>
<string name="info_response_card_card_data_token_contract_address"></string>
<string name="info_response_card_card_data_token_decimal"></string>
<!-- Field names - Response: Sign -->
<string name="response_sign_cid">CID</string>
<string name="response_sign_wallet_signed_hashes">Wallet signed hashes</string>
<string name="response_sign_wallet_remaining_signatures">Wallet remaining signatures</string>
<string name="response_sign_signature">Signature</string>
<string name="info_response_sign_cid">@string/info_response_card_cid</string>
<string name="info_response_sign_wallet_signed_hashes">Total number of signed single hashes returned by the card in SIGN command responses since card personalization. Sums up array elements within all SIGN commands.</string>
<string name="info_response_sign_wallet_remaining_signatures">Remaining number of SIGN operations before the wallet will stop signing transactions.</string>
<string name="info_response_sign_signature">Array of resulting signatures that App should embed into a raw transaction according to a transaction format of an appropriate blockchain.</string>
<!-- Field names - Response: Depersonalize -->
<string name="response_depersonalize_is_success">Is success</string>
<string name="info_response_depersonalize_is_success">Is success</string>
</resources>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="empty_screen_stub">To read the card, press the button at the right bottom corner of the screen</string>
<!-- How to use -->
<string name="htu_scan_action">To read the card, press the button at the right bottom corner of the screen</string>
</resources>

View file

@ -0,0 +1,73 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- You can change the parent around to whatever you normally use -->
<style name="DebugColors" parent="Theme.AppCompat.Light.NoActionBar">
<!-- System colors -->
<item name="android:windowBackground">@color/__debugWindowBackground</item>
<item name="android:colorPressedHighlight">#FF4400</item>
<item name="android:colorLongPressedHighlight">#FF0044</item>
<item name="android:colorFocusedHighlight">#44FF00</item>
<item name="android:colorActivatedHighlight">#00FF44</item>
<item name="android:colorMultiSelectHighlight">#4400FF</item>
<item name="android:statusBarColor">#FFF000</item>
<item name="android:navigationBarColor">#000FFF</item>
<item name="android:colorForeground">#440000</item>
<item name="android:colorForegroundInverse">#004400</item>
<item name="android:colorBackground">#444400</item>
<item name="android:colorBackgroundCacheHint">#440044</item>
//Only for >21
<item name="android:textColorPrimary">#FFFF00</item>
<item name="android:textColorSecondary">#FF00FF</item>
<item name="android:textColorTertiary">#00FFFF</item> <!-- Overrides a TextView textColor-->
<item name="android:textColorPrimaryInverse">#CCCC00</item>
<item name="android:textColorSecondaryInverse">#CC00CC</item>
<item name="android:textColorTertiaryInverse">#00CCCC</item>
<item name="android:textColorPrimaryDisableOnly">#FFCC00</item>
<item name="android:textColorPrimaryInverseDisableOnly">#FF00CC</item>
<item name="android:textColorPrimaryNoDisable">#CCFF00</item>
<item name="android:textColorSecondaryNoDisable">#00FFCC</item>
<item name="android:textColorPrimaryInverseNoDisable">#CC00FF</item>
<item name="android:textColorSecondaryInverseNoDisable">#00CCFF</item>
<item name="android:textColorHint">#FF8800</item>
<item name="android:textColorHintInverse">#FF0088</item>
<item name="android:textColorHighlight">#88FF00</item>
<item name="android:textColorHighlightInverse">#00FF88</item>
<item name="android:textColorLink">#8800FF</item>
<item name="android:textColorLinkInverse">#0088FF</item>
<item name="android:textColorAlertDialogListItem">#444444</item>
<!-- Color palette (via app-compat) -->
<item name="colorPrimary">#FF0000</item>
<item name="colorPrimaryDark">#00FF00</item>
<item name="colorAccent">#0000FF</item>
<item name="colorControlNormal">#CC0000</item>
<item name="colorControlActivated">#00CC00</item>
<item name="colorControlHighlight">#0000CC</item>
<item name="colorButtonNormal">#880000</item>
<item name="colorSwitchThumbNormal">#008800</item>
<!-- Random other things found in app-compat -->
<item name="actionMenuTextColor">#440000</item>
<item name="editTextColor">#FF4400</item> <!-- Overrides textColorPrimary-->
<item name="textColorSearchUrl">#000044</item>
</style>
<!-- Also needed, since windowBackground is a reference, not a color -->
<color name="__debugWindowBackground">#888888</color>
</resources>