Updated on 2026-08-14

This commit is contained in:
Tangem 2020-04-06 09:05:33 +00:00
commit 94a78fb08c
142 changed files with 5224 additions and 1101 deletions

541
.idea/navEditor.xml generated
View file

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

View file

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

View file

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

View file

@ -481,7 +481,6 @@ public class EthEngine extends CoinEngine {
BigInteger l = new BigInteger(balanceCap, 16);
coinData.setBalanceReceived(true);
coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, "wei"));
// Log.i("$TAG eth_get_balance", balanceCap)
}
break;
@ -491,8 +490,9 @@ public class EthEngine extends CoinEngine {
nonce = nonce.substring(2);
BigInteger count = new BigInteger(nonce, 16);
coinData.setConfirmedTXCount(count);
if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
}
// Log.i("$TAG eth_getTransCount", nonce)
}
break;
@ -502,7 +502,9 @@ public class EthEngine extends CoinEngine {
pending = pending.substring(2);
BigInteger count = new BigInteger(pending, 16);
coinData.setUnconfirmedTXCount(count);
if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
}
// Log.i("$TAG eth_getPendingTxCount", pending)
}
break;
@ -582,7 +584,6 @@ public class EthEngine extends CoinEngine {
};
serverApiBlockcypher.setResponseListener(blockcypherListener);
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", "");
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");

View file

@ -109,7 +109,6 @@ public class MaticTokenEngine extends TokenEngine {
BigInteger l = new BigInteger(balanceCap, 16);
coinData.setBalanceReceived(true);
coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei"));
// Log.i("$TAG eth_get_balance", balanceCap)
}
break;
@ -119,8 +118,10 @@ public class MaticTokenEngine extends TokenEngine {
nonce = nonce.substring(2);
BigInteger count = new BigInteger(nonce, 16);
coinData.setConfirmedTXCount(count);
if (serverApiMatic.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), "");
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
}
// Log.i("$TAG eth_getTransCount", nonce)
}
break;
@ -130,7 +131,10 @@ public class MaticTokenEngine extends TokenEngine {
pending = pending.substring(2);
BigInteger count = new BigInteger(pending, 16);
coinData.setUnconfirmedTXCount(count);
if (serverApiMatic.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), "");
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
}
// Log.i("$TAG eth_getPendingTxCount", pending)
}
break;
@ -143,16 +147,6 @@ public class MaticTokenEngine extends TokenEngine {
BigInteger l = new BigInteger(balanceCap, 16);
coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol));
// Log.i("$TAG eth_call", balanceCap)
if (blockchainRequestsCallbacks.allowAdvance()) {
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", "");
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");
} else {
ctx.setError("Terminated by user");
}
} catch (Exception e) {
e.printStackTrace();
}
@ -181,7 +175,8 @@ public class MaticTokenEngine extends TokenEngine {
serverApiMatic.setResponseListener(responseListener);
if (validateAddress(getContractAddress(ctx.getCard()))) {
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), "");
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", "");
serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");
} else {
ctx.setError("Smart contract address not defined");
blockchainRequestsCallbacks.onComplete(false);

View file

@ -85,8 +85,9 @@ public class RskEngine extends EthEngine {
nonce = nonce.substring(2);
BigInteger count = new BigInteger(nonce, 16);
coinData.setConfirmedTXCount(count);
if (serverApiRootstock.isRequestsSequenceCompleted()) {
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
}
// Log.i("$TAG eth_getTransCount", nonce)
}
break;
@ -96,7 +97,9 @@ public class RskEngine extends EthEngine {
pending = pending.substring(2);
BigInteger count = new BigInteger(pending, 16);
coinData.setUnconfirmedTXCount(count);
if (serverApiRootstock.isRequestsSequenceCompleted()) {
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
}
// Log.i("$TAG eth_getPendingTxCount", pending)
}
break;
@ -122,7 +125,6 @@ public class RskEngine extends EthEngine {
};
serverApiRootstock.setResponseListener(responseListener);
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", "");
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");
}

View file

@ -82,7 +82,6 @@ public class RskTokenEngine extends TokenEngine {
BigInteger l = new BigInteger(balanceCap, 16);
coinData.setBalanceReceived(true);
coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei"));
// Log.i("$TAG eth_get_balance", balanceCap)
}
break;
@ -92,8 +91,10 @@ public class RskTokenEngine extends TokenEngine {
nonce = nonce.substring(2);
BigInteger count = new BigInteger(nonce, 16);
coinData.setConfirmedTXCount(count);
if (serverApiRootstock.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), "");
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
}
// Log.i("$TAG eth_getTransCount", nonce)
}
break;
@ -103,7 +104,10 @@ public class RskTokenEngine extends TokenEngine {
pending = pending.substring(2);
BigInteger count = new BigInteger(pending, 16);
coinData.setUnconfirmedTXCount(count);
if (serverApiRootstock.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), "");
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
}
// Log.i("$TAG eth_getPendingTxCount", pending)
}
break;
@ -115,15 +119,6 @@ public class RskTokenEngine extends TokenEngine {
BigInteger l = new BigInteger(balanceCap, 16);
coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol));
// Log.i("$TAG eth_call", balanceCap)
if (blockchainRequestsCallbacks.allowAdvance()) {
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", "");
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");
} else {
ctx.setError("Terminated by user");
}
} catch (Exception e) {
e.printStackTrace();
}
@ -152,7 +147,8 @@ public class RskTokenEngine extends TokenEngine {
serverApiRootstock.setResponseListener(responseListener);
if (validateAddress(getContractAddress(ctx.getCard()))) {
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), "");
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", "");
serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");
} else {
ctx.setError("Smart contract address not defined");
blockchainRequestsCallbacks.onComplete(false);

View file

@ -655,7 +655,6 @@ public class TokenEngine extends CoinEngine {
BigInteger l = new BigInteger(balanceCap, 16);
coinData.setBalanceReceived(true);
coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei"));
// Log.i("$TAG eth_get_balance", balanceCap)
}
break;
@ -665,8 +664,10 @@ public class TokenEngine extends CoinEngine {
nonce = nonce.substring(2);
BigInteger count = new BigInteger(nonce, 16);
coinData.setConfirmedTXCount(count);
if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), "");
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
}
// Log.i("$TAG eth_getTransCount", nonce)
}
break;
@ -676,29 +677,21 @@ public class TokenEngine extends CoinEngine {
pending = pending.substring(2);
BigInteger count = new BigInteger(pending, 16);
coinData.setUnconfirmedTXCount(count);
if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), "");
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
}
// Log.i("$TAG eth_getPendingTxCount", pending)
}
break;
//
case ServerApiInfura.INFURA_ETH_CALL: {
try {
String balanceCap = infuraResponse.getResult();
balanceCap = balanceCap.substring(2);
BigInteger l = new BigInteger(balanceCap, 16);
coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol));
// Log.i("$TAG eth_call", balanceCap)
if (blockchainRequestsCallbacks.allowAdvance()) {
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", "");
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", "");
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");
} else {
ctx.setError("Terminated by user");
}
} catch (Exception e) {
e.printStackTrace();
}
@ -706,7 +699,7 @@ public class TokenEngine extends CoinEngine {
break;
}
if (serverApiInfura.isRequestsSequenceCompleted()&& serverApiBlockcypher.isRequestsSequenceCompleted()) {
if (serverApiInfura.isRequestsSequenceCompleted() && serverApiBlockcypher.isRequestsSequenceCompleted()) {
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
} else {
blockchainRequestsCallbacks.onProgress();
@ -771,13 +764,13 @@ public class TokenEngine extends CoinEngine {
serverApiBlockcypher.setResponseListener(blockcypherListener);
if (validateAddress(getContractAddress(ctx.getCard()))) {
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), "");
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", "");
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", "");
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), "");
} else {
ctx.setError("Smart contract address not defined");
blockchainRequestsCallbacks.onComplete(false);
}
serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), "");
}
@Override

View file

@ -109,14 +109,14 @@ public class XlmAssetEngine extends CoinEngine {
@Override
public boolean isBalanceNotZero() {
if (coinData == null) return false;
if (coinData.getXlmBalance() == null) return false;
return coinData.getXlmBalance().notZero();
return (coinData.getXlmBalance() != null && coinData.getXlmBalance().notZero() &&
coinData.getAssetBalance() != null && coinData.getAssetBalance().notZero());
}
@Override
public boolean hasBalanceInfo() {
if (coinData == null) return false;
return coinData.getXlmBalance() != null;
return (coinData.getXlmBalance() != null && coinData.getAssetBalance() != null) || (coinData.isError404());
}

View file

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

View file

@ -12,6 +12,12 @@ fun String.calculateSha256(): ByteArray {
return sha256.digest(data)
}
fun String.calculateSha512(): ByteArray {
val sha = MessageDigest.getInstance("SHA-512")
val data = this.toByteArray(Charset.forName("UTF-8"))
return sha.digest(data)
}
fun String.hexToBytes(): ByteArray {
return ByteArray(this.length / 2)
{ i ->

View file

@ -3,8 +3,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.tangem.tangemtest"
@ -34,13 +33,23 @@ android {
dependencies {
implementation project(':tangem-core')
implementation project(':tangem-sdk')
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4"
implementation "androidx.navigation:navigation-fragment-ktx:2.2.1"
implementation "androidx.navigation:navigation-ui-ktx:2.2.1"
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha01"
implementation "com.google.android.material:material:1.2.0-alpha05"
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.github.gbIxaHue:eu4d:0.3.7'
}

View file

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

View file

@ -0,0 +1,29 @@
package com.tangem.tangemtest
import android.app.Application
import com.tangem.tangemtest._arch.structure.ILog
import com.tangem.tangemtest._arch.structure.ItemLogger
import com.tangem.tangemtest.commons.TangemLogger
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
/**
[REDACTED_AUTHOR]
*/
class AppTangemDemo : Application() {
override fun onCreate() {
super.onCreate()
AppTangemDemo.appInstance = this
setupLoggers()
}
private fun setupLoggers() {
Log.setLogger(TangemLogger())
ILog.setLogger(ItemLogger())
}
companion object {
lateinit var appInstance: AppTangemDemo
}
}

View file

@ -5,12 +5,13 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.tangem.CardManager
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tangemtest.extensions.create
import com.tangem.tasks.ScanEvent
import com.tangem.tasks.TaskError
import com.tangem.tasks.TaskEvent
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.old_activity_main.*
class MainActivity : AppCompatActivity() {
class Old_MainActivity : AppCompatActivity() {
private lateinit var cardManager: CardManager
private lateinit var cardId: String
@ -20,7 +21,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setContentView(R.layout.old_activity_main)
cardManager = CardManager.init(this)

View file

@ -19,162 +19,162 @@ import java.nio.charset.StandardCharsets
/**
[REDACTED_AUTHOR]
*/
class TestUserDataActivity: AppCompatActivity() {
class TestUserDataActivity : AppCompatActivity() {
private lateinit var cardManager: CardManager
private lateinit var writeOptions: WriteOptions
private lateinit var cardManager: CardManager
private lateinit var writeOptions: WriteOptions
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_user_data)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_user_data)
init()
initWriteOptions()
}
private fun init() {
cardManager = CardManager.init(this)
btn_scan?.setOnClickListener { _ ->
cardManager.scanCard { taskEvent ->
when (taskEvent) {
is TaskEvent.Event -> {
when (taskEvent.data) {
is ScanEvent.OnReadEvent -> {
// Handle returned card data
writeOptions.cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId
runOnUiThread { showReadWriteSection(true) }
}
is ScanEvent.OnVerifyEvent -> {
//Handle card verification
}
}
}
is TaskEvent.Completion -> {
if (taskEvent.error != null) {
if (taskEvent.error is TaskError.UserCancelled) {
// Handle case when user cancelled manually
}
// Handle other errors
}
// Handle completion
}
}
}
init()
initWriteOptions()
}
btn_write.setOnClickListener {
if (writeOptions.cardId == null) return@setOnClickListener
private fun init() {
cardManager = CardManager.init(this)
cardManager.writeUserData(
writeOptions.cardId !!,
writeOptions.userData,
writeOptions.userProtectedData,
writeOptions.userCounter,
writeOptions.userProtectedCounter
) {
when (it) {
is TaskEvent.Completion -> handleError(tv_write_result, it.error)
is TaskEvent.Event -> {
runOnUiThread {
val data = it.data as? WriteUserDataResponse
if (data == null) {
tv_write_result.text = "Response doesn't match"
return@runOnUiThread
}
tv_write_result?.text = "Success"
btn_scan?.setOnClickListener { _ ->
cardManager.scanCard { taskEvent ->
when (taskEvent) {
is TaskEvent.Event -> {
when (taskEvent.data) {
is ScanEvent.OnReadEvent -> {
// Handle returned card data
writeOptions.cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId
runOnUiThread { showReadWriteSection(true) }
}
is ScanEvent.OnVerifyEvent -> {
//Handle card verification
}
}
}
is TaskEvent.Completion -> {
if (taskEvent.error != null) {
if (taskEvent.error is TaskError.UserCancelled) {
// Handle case when user cancelled manually
}
// Handle other errors
}
// Handle completion
}
}
}
}
btn_write.setOnClickListener {
if (writeOptions.cardId == null) return@setOnClickListener
cardManager.writeUserData(
writeOptions.cardId!!,
writeOptions.userData,
writeOptions.userProtectedData,
writeOptions.userCounter,
writeOptions.userProtectedCounter
) {
when (it) {
is TaskEvent.Completion -> handleError(tv_write_result, it.error)
is TaskEvent.Event -> {
runOnUiThread {
val data = it.data as? WriteUserDataResponse
if (data == null) {
tv_write_result.text = "Response doesn't match"
return@runOnUiThread
}
tv_write_result?.text = "Success"
}
}
}
}
}
btn_read.setOnClickListener {
if (writeOptions.cardId == null) return@setOnClickListener
cardManager.readUserData(writeOptions.cardId!!) {
when (it) {
is TaskEvent.Completion -> handleError(tv_read_result, it.error)
is TaskEvent.Event -> {
runOnUiThread {
val data = it.data as? ReadUserDataResponse
if (data == null) {
tv_read_result.text = "Response doesn't match"
return@runOnUiThread
}
tv_read_result?.text = "Success"
writeOptions.userData = data.userData
writeOptions.userProtectedData = data.userProtectedData
writeOptions.userCounter = data.userCounter
writeOptions.userProtectedCounter = data.userProtectedCounter
tv_card_cid.text = data.cardId
tv_data.text = String(data.userData, StandardCharsets.US_ASCII)
tv_protected_data.text = String(data.userProtectedData, StandardCharsets.US_ASCII)
tv_counter.text = data.userCounter.toString()
tv_protected_counter.text = data.userProtectedCounter.toString()
}
}
}
}
}
}
}
}
btn_read.setOnClickListener {
if (writeOptions.cardId == null) return@setOnClickListener
private fun handleError(tv: TextView, error: TaskError?) {
val er = error ?: return
if (er is TaskError.UserCancelled) return
cardManager.readUserData(writeOptions.cardId !!) {
when (it) {
is TaskEvent.Completion -> handleError(tv_read_result, it.error)
is TaskEvent.Event -> {
runOnUiThread {
val data = it.data as? ReadUserDataResponse
if (data == null) {
tv_read_result.text = "Response doesn't match"
return@runOnUiThread
}
tv_read_result?.text = "Success"
writeOptions.userData = data.userData
writeOptions.userProtectedData = data.userProtectedData
writeOptions.userCounter = data.userCounter
writeOptions.userProtectedCounter = data.userProtectedCounter
tv_card_cid.text = data.cardId
tv_data.text = String(data.userData, StandardCharsets.US_ASCII)
tv_protected_data.text = String(data.userProtectedData, StandardCharsets.US_ASCII)
tv_counter.text = data.userCounter.toString()
tv_protected_counter.text = data.userProtectedCounter.toString()
}
}
}
}
runOnUiThread { tv.text = er::class.simpleName }
}
}
private fun handleError(tv: TextView, error: TaskError?) {
val er = error ?: return
if (er is TaskError.UserCancelled) return
private fun initWriteOptions() {
writeOptions = WriteOptions()
runOnUiThread { tv.text = er::class.simpleName }
}
chb_with_ud.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateData(buttonView) }
chb_with_ud_protected.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedData(buttonView) }
chb_with_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateCounter(buttonView) }
chb_with_protected_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedCounter(buttonView) }
chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) }
}
private fun initWriteOptions() {
writeOptions = WriteOptions()
chb_with_ud.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateData(buttonView) }
chb_with_ud_protected.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedData(buttonView) }
chb_with_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateCounter(buttonView) }
chb_with_protected_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedCounter(buttonView) }
chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) }
}
private fun showReadWriteSection(show: Boolean) {
val state = if (show) View.VISIBLE else View.GONE
cl_read_write.visibility = state
}
private fun showReadWriteSection(show: Boolean) {
val state = if (show) View.VISIBLE else View.GONE
cl_read_write.visibility = state
}
}
class WriteOptions {
var cardId: String? = null
var userData: ByteArray? = null
var userProtectedData: ByteArray? = null
var userCounter: Int? = null
var userProtectedCounter: Int? = null
var pin2: String? = null
var cardId: String? = null
var userData: ByteArray? = null
var userProtectedData: ByteArray? = null
var userCounter: Int? = null
var userProtectedCounter: Int? = null
var pin2: String? = null
fun updateData(chbx: CompoundButton) {
val value = "simple user data".toByteArray()
userData = if (chbx.isChecked) value else null
}
fun updateData(chbx: CompoundButton) {
val value = "simple user data".toByteArray()
userData = if (chbx.isChecked) value else null
}
fun updateProtectedData(chbx: CompoundButton) {
val value = "protected user data".toByteArray()
userProtectedData = if (chbx.isChecked) value else null
}
fun updateProtectedData(chbx: CompoundButton) {
val value = "protected user data".toByteArray()
userProtectedData = if (chbx.isChecked) value else null
}
fun updateCounter(chbx: CompoundButton) {
val value = if (userCounter == null) 0 else userCounter !! + 1
userCounter = if (chbx.isChecked) value else null
}
fun updateCounter(chbx: CompoundButton) {
val value = if (userCounter == null) 0 else userCounter!! + 1
userCounter = if (chbx.isChecked) value else null
}
fun updateProtectedCounter(chbx: CompoundButton) {
val value = if (userProtectedCounter == null) 0 else userProtectedCounter !! + 1
userProtectedCounter = if (chbx.isChecked) value else null
}
fun updateProtectedCounter(chbx: CompoundButton) {
val value = if (userProtectedCounter == null) 0 else userProtectedCounter!! + 1
userProtectedCounter = if (chbx.isChecked) value else null
}
fun updatePin2(chbx: CompoundButton) {
val value = CardEnvironment.DEFAULT_PIN2
pin2 = if (chbx.isChecked) value else null
}
fun updatePin2(chbx: CompoundButton) {
val value = CardEnvironment.DEFAULT_PIN2
pin2 = if (chbx.isChecked) value else null
}
}

View file

@ -0,0 +1,44 @@
package com.tangem.tangemtest._arch
import android.util.Log
import androidx.annotation.MainThread
import androidx.annotation.Nullable
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean
/**
[REDACTED_AUTHOR]
*/
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending: AtomicBoolean = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(it)
}
})
}
@MainThread
override fun setValue(@Nullable t: T?) {
mPending.set(true)
super.setValue(t)
}
@MainThread
fun call() {
setValue(null)
}
companion object {
private const val TAG = "SingleLiveEvent"
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.tangemtest._arch.structure
/**
[REDACTED_AUTHOR]
*/
interface DataHolder<D> {
var viewModel: D
}
typealias Payload = MutableMap<String, Any?>
interface PayloadHolder {
val payload: Payload
fun get(key: String): Any? = payload[key]
fun remove(key: String): Any? = payload.remove(key)
fun set(key: String, value: Any?) {
payload[key] = value
}
}
interface ItemListHolder<I> {
fun setItems(list: MutableList<I>)
fun getItems(): MutableList<I>
fun addItem(item: I)
fun removeItem(item: I)
fun clear()
}

View file

@ -0,0 +1,13 @@
package com.tangem.tangemtest._arch.structure
/**
[REDACTED_AUTHOR]
*/
interface Id
enum class Additional : Id {
UNDEFINED,
JSON_INCOMING,
JSON_OUTGOING,
JSON_TAILS,
}

View file

@ -0,0 +1,36 @@
package com.tangem.tangemtest._arch.structure
import ru.dev.gbixahue.eu4d.lib.android.global.log.Logger
import ru.dev.gbixahue.eu4d.lib.android.global.log.TagLogger
import ru.dev.gbixahue.eu4d.lib.android.global.log.profiling.SimpleLogProfiler
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
object ILog {
private var logger: Logger? = null
fun setLogger(logger: Logger) {
ILog.logger = logger
}
fun d(from: Any, msg: Any?, value: Any? = null) {
logger?.d(from, stringOf(msg), value)
}
fun w(from: Any, msg: Any?, value: Any? = null) {
logger?.w(from, stringOf(msg), value)
}
fun e(from: Any, msg: Any?, value: Any? = null) {
logger?.e(from, stringOf(msg), value)
}
}
class ItemLogger : TagLogger("ITEM") {
init {
msProfiler = SimpleLogProfiler()
}
}

View file

@ -0,0 +1,50 @@
package com.tangem.tangemtest._arch.structure.abstraction
import com.tangem.tangemtest._arch.structure.ILog
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.ItemListHolder
/**
[REDACTED_AUTHOR]
*/
interface Block : Item {
val itemList: MutableList<Item>
}
abstract class BaseBlock : Block {
override var parent: Item? = null
override val itemList: MutableList<Item> = mutableListOf()
override val payload: MutableMap<String, Any?> = mutableMapOf()
}
open class ListItemBlock(
override val id: Id
) : BaseBlock(), ItemListHolder<Item> {
override fun setItems(list: MutableList<Item>) {
ILog.d(this, "setItems into: $id, count: ${list.size}")
itemList.clear()
itemList.addAll(list)
itemList.forEach { it.parent = this }
}
override fun getItems(): MutableList<Item> {
return itemList
}
override fun addItem(item: Item) {
ILog.d(this, "addItem into: $id, who: ${item.id}")
item.parent = this
itemList.add(item)
}
override fun removeItem(item: Item) {
ILog.d(this, "removeItem from: $id, which: ${item.id}")
itemList.remove(item)
}
override fun clear() {
ILog.d(this, "clear $id")
itemList.clear()
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.tangemtest._arch.structure.abstraction
import com.tangem.tangemtest._arch.structure.Id
/**
[REDACTED_AUTHOR]
*/
fun List<Item>.findDataItem(id: Id): BaseItem<Any?>? {
var foundItem: BaseItem<Any?>? = null
iterate {
if (it.id == id) {
foundItem = it as? BaseItem<Any?>
return@iterate
}
}
return foundItem
}
fun List<Item>.iterate(func: (Item) -> Unit) {
forEach {
when (it) {
is BaseItem<*> -> func(it)
is Block -> it.itemList.iterate(func)
}
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.tangemtest._arch.structure.abstraction
import com.tangem.tangemtest._arch.structure.DataHolder
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.PayloadHolder
/**
[REDACTED_AUTHOR]
*/
interface Item : PayloadHolder {
val id: Id
var parent: Item?
}
abstract class BaseItem<D>(
override var viewModel: ItemViewModel<D>
) : Item, DataHolder<ItemViewModel<D>> {
override var parent: Item? = null
override val payload: Payload = mutableMapOf()
fun getData(): D? = viewModel.data
fun setData(value: D?) {
viewModel.data = value
}
fun restoreDefaultData(){
setData(viewModel.defaultData)
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.tangemtest._arch.structure.abstraction
import ru.dev.gbixahue.eu4d.lib.kotlin.common.Converter
/**
[REDACTED_AUTHOR]
*/
interface DefaultConverter<A, B> {
fun convert(from: A, default: B): B
}
interface ItemsToModel<M> : DefaultConverter<List<Item>, M>
interface ModelToItems<M> : Converter<M, List<Item>>
interface ModelConverter<M> : DefaultConverter<List<Item>, M>, Converter<M, List<Item>>

View file

@ -0,0 +1,10 @@
package com.tangem.tangemtest._arch.structure.abstraction
/**
[REDACTED_AUTHOR]
*/
typealias PluginCallBackResult = (Any?) -> Item
interface Plugin : Item {
fun invoke(data: Any?, asyncCallback: PluginCallBackResult? = null): Any?
}

View file

@ -0,0 +1,73 @@
package com.tangem.tangemtest._arch.structure.abstraction
import com.tangem.tangemtest._arch.structure.ILog
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.PayloadHolder
/**
[REDACTED_AUTHOR]
*/
typealias ValueChange<V> = (V?) -> Unit
typealias SafeValueChange<V> = (V) -> Unit
interface ItemViewModel<D : Any?> : PayloadHolder {
val viewState: ViewState
var data: D?
var defaultData: D?
var onDataUpdated: ValueChange<D>?
fun updateDataByView(data: D?)
}
open class BaseItemViewModel<D> : ItemViewModel<D> {
override val viewState: ViewState = ViewState()
override val payload: Payload = mutableMapOf()
// Don't update it directly from a View. Use for it updateDataByView()
override var data: D? = null
set(value) {
if (handleDataUpdates(value)) field = value
}
// Data for restoring initial value
override var defaultData: D? = null
set(value) {
field = value
data = value
}
// Use it for handling data updates in View
override var onDataUpdated: ValueChange<D>? = null
// When data updates directly it invokes onDataUpdated
// return true = data will update
// return false = data won't update
protected open fun handleDataUpdates(value: D?): Boolean {
ILog.d(this, "handleDateUpdates: $value")
onDataUpdated?.invoke(value)
return true
}
// Use it to update the data from a View. It disables onDataUpdated to prevent a callback loop
override fun updateDataByView(data: D?) {
ILog.d(this, "data changed: $data")
val callback = onDataUpdated
onDataUpdated = null
this.data = data
onDataUpdated = callback
}
}
class ViewState {
var isHiddenField = false
var descriptionVisibility: Int = 0x00000008
set(value) {
field = value
onDescriptionVisibilityChanged?.invoke(value)
}
var onDescriptionVisibilityChanged: SafeValueChange<Int>? = null
}

View file

@ -0,0 +1,19 @@
package com.tangem.tangemtest._arch.structure.impl
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
/**
[REDACTED_AUTHOR]
*/
class AnyItem(override val id: Id, value: Any? = null) : BaseItem<Any>(AnyViewModel(value))
class TextItem(override val id: Id, value: String? = null) : BaseItem<String>(StringViewModel(value))
class EditTextItem(override val id: Id, value: String? = null) : BaseItem<String>(StringViewModel(value))
class NumberItem(override val id: Id, value: Number? = null) : BaseItem<Number>(NumberViewModel(value))
class BoolItem(override val id: Id, value: Boolean? = null) : BaseItem<Boolean>(BoolViewModel(value))
class ListItem(override val id: Id, value: List<KeyValue>, selectedValue: Any?)
: BaseItem<ListValueWrapper>(ListViewModel(ListValueWrapper(selectedValue, value))
)
inline fun<reified T> BaseItem<T>.getData(): T? = viewModel.data

View file

@ -0,0 +1,29 @@
package com.tangem.tangemtest._arch.structure.impl
import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel
/**
[REDACTED_AUTHOR]
*/
open class TransitiveViewModel<D>(value: D?) : BaseItemViewModel<D>() {
init {
defaultData = value
}
}
class AnyViewModel(value: Any? = null) : TransitiveViewModel<Any>(value)
class StringViewModel(value: String? = null) : TransitiveViewModel<String>(value)
class NumberViewModel(value: Number? = null) : TransitiveViewModel<Number>(value)
class BoolViewModel(value: Boolean? = null) : TransitiveViewModel<Boolean>(value)
class ListViewModel(value: ListValueWrapper? = null) : TransitiveViewModel<ListValueWrapper>(value) {
override fun handleDataUpdates(value: ListValueWrapper?): Boolean {
val newValue = value ?: return false
if (newValue.selectedItem == data?.selectedItem) return false
return super.handleDataUpdates(value)
}
}
class KeyValue(val key: String, val value: Any)
class ListValueWrapper(var selectedItem: Any?, val itemList: List<KeyValue>)

View file

@ -0,0 +1,62 @@
package com.tangem.tangemtest._main
import android.content.res.Resources
import android.os.Bundle
import android.util.Log
import android.view.Menu
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat
import androidx.appcompat.widget.Toolbar
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import com.tangem.tangemtest.R
/**
[REDACTED_AUTHOR]
*/
class MainActivity : AppCompatActivity() {
private val vm: MainViewModel by viewModels<MainViewModel>()
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.a_main)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
val host: NavHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment? ?: return
val navController = host.navController
appBarConfiguration = AppBarConfiguration(setOf(R.id.nav_entry_point))
setupActionBarWithNavController(navController, appBarConfiguration)
navController.addOnDestinationChangedListener { _, destination, _ ->
val dest: String = try {
resources.getResourceName(destination.id)
} catch (e: Resources.NotFoundException) {
destination.id.toString()
}
Log.d("NavigationActivity", "Navigated to $dest")
}
}
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.nav_host_fragment).navigateUp(appBarConfiguration)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_menu, menu)
val switchMenu = menu.findItem(R.id.action_favorite)
(switchMenu.actionView as? SwitchCompat)?.let {
it.setOnCheckedChangeListener { buttonView, isChecked -> vm.switchToggled(isChecked) }
}
return super.onCreateOptionsMenu(menu)
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.tangemtest._main
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
/**
[REDACTED_AUTHOR]
*/
class MainViewModel : ViewModel() {
val ldDescriptionSwitch = MutableLiveData<Boolean>(false)
fun switchToggled(state: Boolean) {
ldDescriptionSwitch.postValue(state)
}
}

View file

@ -0,0 +1,87 @@
package com.tangem.tangemtest._main.entryPoint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.tangem.tangemtest.R
import com.tangem.tangemtest._main.MainViewModel
import com.tangem.tangemtest.ucase.getDefaultNavigationOptions
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
import kotlinx.android.synthetic.main.fg_entry_point.*
/**
[REDACTED_AUTHOR]
*/
class ActionListFragment : Fragment() {
private val navController: NavController by lazy { findNavController() }
private lateinit var rvActions: RecyclerView
private val mainActivityVM: MainViewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fg_entry_point, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initRecyclerView()
}
private fun initRecyclerView() {
val layoutManager = LinearLayoutManager(activity)
rvActions = rv_actions
rvActions.layoutManager = layoutManager
rvActions.addItemDecoration(DividerItemDecoration(activity, layoutManager.orientation))
val vhDataWrapper = VhExDataWrapper(MainResourceHolder, false)
rvActions.adapter = RvActionsAdapter(vhDataWrapper) { type, position, data -> navigate(data) }
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
vhDataWrapper.descriptionIsVisible = it
TransitionManager.beginDelayedTransition(rvActions as ViewGroup, AutoTransition())
rvActions.adapter?.notifyDataSetChanged()
})
}
override fun onResume() {
super.onResume()
val adapter: RvActionsAdapter = rvActions.adapter as? RvActionsAdapter ?: return
adapter.setItemList(getNavigateOptions())
adapter.notifyDataSetChanged()
}
private fun navigate(destinationId: Int) {
navController.navigate(destinationId, null, getDefaultNavigationOptions())
}
private fun getNavigateOptions(): MutableList<ActionType> {
return mutableListOf(
ActionType.Scan,
ActionType.Sign,
ActionType.Personalize,
ActionType.Depersonalize
// ActionType.CreateWallet,
// ActionType.PurgeWallet,
// ActionType.ReadIssuerData,
// ActionType.WriteIssuerData,
// ActionType.ReadIssuerExData,
// ActionType.WriteIssuerExData,
// ActionType.ReadUserData,
// ActionType.WriteUserData
)
}
}

View file

@ -0,0 +1,69 @@
package com.tangem.tangemtest._main.entryPoint
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.resources.ActionRes
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate
import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseAdapter
import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseVH
import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvCallback
/**
[REDACTED_AUTHOR]
*/
class RvActionsAdapter(
private val wrapper: VhExDataWrapper,
private val callback: RvCallback<Int>
) : RvBaseAdapter<RvActionsVH, ActionType>() {
override fun onBindViewHolder(holder: RvActionsVH, position: Int) {
holder.bindData(itemList[position])
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RvActionsVH {
val view = parent.inflate<View>(R.layout.vh_actions, false)
return RvActionsVH(view, wrapper, callback)
}
}
class VhExDataWrapper(
val resHolder: MainResourceHolder,
var descriptionIsVisible: Boolean
)
class RvActionsVH(
itemView: View,
private val wrapper: VhExDataWrapper,
private val callback: RvCallback<Int>?
) : RvBaseVH<ActionType>(itemView) {
private val tvAction = itemView.findViewById<TextView>(R.id.tv_title)
private val containerDescription = itemView.findViewById<ViewGroup>(R.id.container_description)
private val tvDescription = itemView.findViewById<TextView>(R.id.tv_description)
override fun bindData(data: ActionType) {
val res: ActionRes = wrapper.resHolder.safeGet(data)
tvAction.text = getString(res.resName)
tvDescription.text = getString(res.resDescription)
itemView.setOnClickListener {
res.resNavigation?.let { callback?.invoke(itemViewType, adapterPosition, it) }
}
containerDescription.visibility = if (wrapper.descriptionIsVisible) View.VISIBLE else View.GONE
}
override fun onDataBound(data: ActionType) {}
}
fun RecyclerView.ViewHolder.getString(@StringRes id: Int?, ifNull: String = ""): String {
val reqId = id ?: return ifNull
return itemView.context.getString(reqId)
}

View file

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

View file

@ -0,0 +1,8 @@
package com.tangem.tangemtest.commons
import ru.dev.gbixahue.eu4d.lib.android.global.log.TagLogger
/**
[REDACTED_AUTHOR]
*/
class TangemLogger : TagLogger("TangemDemo")

View file

@ -7,7 +7,7 @@ import com.tangem.commands.personalization.entities.CardConfig
import com.tangem.commands.personalization.entities.NdefRecord
import java.util.*
fun CardConfig.Companion.init(application: Application): CardConfig {
fun CardConfig.Companion.create(application: Application): CardConfig {
val preferences = application.getSharedPreferences("prefs", Context.MODE_PRIVATE)

View file

@ -0,0 +1,16 @@
package com.tangem.tangemtest.extensions
/**
[REDACTED_AUTHOR]
*/
fun <T> List<T>.print(delimiter: String = ", ", wrap: Boolean = true): String {
val builder = StringBuilder()
forEach { builder.append(it).append(delimiter) }
val length = builder.length
if (length > delimiter.length) {
builder.delete(length - delimiter.length, length)
}
val result = builder.toString()
return if (wrap) "[$result]" else result
}

View file

@ -0,0 +1,19 @@
package com.tangem.tangemtest.ucase
import androidx.navigation.NavOptions
import androidx.navigation.navOptions
import com.tangem.tangemtest.R
/**
[REDACTED_AUTHOR]
*/
fun getDefaultNavigationOptions(): NavOptions {
return navOptions {
anim {
enter = R.anim.slide_in_right
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_left
popExit = R.anim.slide_out_right
}
}
}

View file

@ -0,0 +1,46 @@
package com.tangem.tangemtest.ucase.domain.actions
import com.tangem.CardManager
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.AfterActionModification
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence
import com.tangem.tasks.TaskEvent
/**
[REDACTED_AUTHOR]
*
* The Card Action class family is designed for calling Card Manager functions
* and then processing the response. It also allows you to extract the main action as a lambda expression
*/
data class AttrForAction(
val cardManager: CardManager,
val itemList: List<Item>,
val payload: Payload,
val consequence: ItemsChangeConsequence?
)
interface Action {
fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback)
fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? = null
}
abstract class BaseAction : Action {
protected open fun handleResult(
payload: PayloadHolder,
taskEvent: TaskEvent<*>,
modifier: AfterActionModification?,
attrs: AttrForAction,
callback: ActionCallback
) {
val modifiedItems = mutableListOf<Item>()
modifier?.modify(payload, taskEvent, attrs.itemList)?.forEach { item ->
modifiedItems.add(item)
attrs.consequence?.affectChanges(payload, item, attrs.itemList)?.let { modifiedItems.addAll(it) }
}
callback(taskEvent, modifiedItems)
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.tangemtest.ucase.domain.actions
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.findDataItem
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.variants.TlvId
/**
[REDACTED_AUTHOR]
*/
class DepersonalizeAction : BaseAction() {
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
val item = attrs.itemList.findDataItem(TlvId.CardId) ?: return
val cardId = item.viewModel.data as? String ?: return
attrs.cardManager.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) }
}
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {
return when (id) {
TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) }
else -> null
}
}
}

View file

@ -0,0 +1,65 @@
package com.tangem.tangemtest.ucase.domain.actions
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.findDataItem
import com.tangem.tangemtest._arch.structure.impl.EditTextItem
import com.tangem.tangemtest._arch.structure.impl.NumberItem
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
import com.tangem.tangemtest.ucase.tunnel.ActionView
import com.tangem.tangemtest.ucase.tunnel.ItemError
import com.tangem.tangemtest.ucase.variants.personalize.CardNumber
import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizeConfigConverter
import com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo.PersonalizeConfigToCardConfig
import com.tangem.tangemtest.ucase.variants.personalize.dto.DefaultPersonalizeParams
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
class PersonalizeAction : BaseAction() {
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
val itemList = attrs.payload[PayloadKey.itemList] as? List<Item> ?: return
val actionView = attrs.payload[PayloadKey.actionView] as? ActionView ?: return
if (!checkSeries(itemList)) {
actionView.showSnackbar(ItemError.BadSeries)
return
}
if (!checkNumber(itemList)) {
actionView.showSnackbar(ItemError.BadCardNumber)
return
}
val issuer = DefaultPersonalizeParams.issuer()
val acquirer = DefaultPersonalizeParams.acquirer()
val manufacturer = DefaultPersonalizeParams.manufacturer()
val personalizeConfig = PersonalizeConfigConverter().convert(itemList, PersonalizeConfig())
val cardConfig = PersonalizeConfigToCardConfig().convert(personalizeConfig)
attrs.cardManager.personalize(cardConfig, issuer, manufacturer, acquirer) {
handleResult(payload, it, null, attrs, callback)
}
}
private fun checkSeries(itemList: List<Item>): Boolean {
val seriesItem = itemList.findDataItem(CardNumber.Series) as? EditTextItem ?: return false
val data = seriesItem.getData() ?: return false
seriesItem.setData(data.toUpperCase())
return data.length == 2 || data.length == 4
}
private fun checkNumber(itemList: List<Item>): Boolean {
val seriesItem = itemList.findDataItem(CardNumber.Series) as? EditTextItem ?: return false
val numberItem = itemList.findDataItem(CardNumber.Number) as? NumberItem ?: return false
val seriesData = seriesItem.getData() ?: return false
val numberData = stringOf(numberItem.getData())
return if (seriesData.length == 2 && numberData.length > 13) false
else !(seriesData.length == 4 && numberData.length > 11)
}
}

View file

@ -0,0 +1,14 @@
package com.tangem.tangemtest.ucase.domain.actions
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.AfterScanModifier
/**
[REDACTED_AUTHOR]
*/
class ScanAction : BaseAction() {
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
attrs.cardManager.scanCard { handleResult(payload, it, AfterScanModifier(), attrs, callback) }
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.tangemtest.ucase.domain.actions
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.findDataItem
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.variants.TlvId
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
class SignAction : BaseAction() {
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
val dataForHashing = attrs.itemList.findDataItem(TlvId.TransactionOutHash) ?: return
val hash = dataForHashing.getData() as? ByteArray ?: return
val cardId = attrs.itemList.findDataItem(TlvId.CardId)?.viewModel?.data ?: return
attrs.cardManager.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
}
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {
return when (id) {
TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) }
else -> null
}
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.tangemtest.ucase.domain.paramsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.DepersonalizeItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.PersonalizeItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.ScanItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.SignItemsManager
import com.tangem.tangemtest.ucase.resources.ActionType
import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder
/**
[REDACTED_AUTHOR]
*/
class ParamsManagerFactory : BaseTypedHolder<ActionType, ItemsManager>() {
companion object {
fun createFactory(): ParamsManagerFactory {
return ParamsManagerFactory().apply {
register(ActionType.Scan, ScanItemsManager())
register(ActionType.Sign, SignItemsManager())
register(ActionType.Personalize, PersonalizeItemsManager())
register(ActionType.Depersonalize, DepersonalizeItemsManager())
}
}
}
}

View file

@ -0,0 +1,38 @@
package com.tangem.tangemtest.ucase.domain.paramsManager
import com.tangem.CardManager
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence
import com.tangem.tasks.TaskEvent
typealias ActionResponse = TaskEvent<*>
typealias AffectedList = List<Item>
typealias AffectedItemsCallback = (AffectedList) -> Unit
typealias ActionCallback = (ActionResponse, AffectedList) -> Unit
/**
[REDACTED_AUTHOR]
*/
interface ItemsManager : PayloadHolder {
fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback? = null)
fun getItems(): List<Item>
fun invokeMainAction(cardManager: CardManager, callback: ActionCallback)
fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)?
fun attachPayload(payload: Payload)
val consequence: ItemsChangeConsequence?
}
interface PayloadKey {
companion object {
val cardConfig: String = "cardConfig"
val incomingJson: String = "incomingJson"
val card = "card"
val actionView = "actionView"
val itemList = "itemList"
}
}

View file

@ -0,0 +1,51 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import com.tangem.CardManager
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.findDataItem
import com.tangem.tangemtest.ucase.domain.actions.Action
import com.tangem.tangemtest.ucase.domain.actions.AttrForAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.domain.paramsManager.AffectedItemsCallback
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence
/**
[REDACTED_AUTHOR]
*/
abstract class BaseItemsManager(protected val action: Action) : ItemsManager {
protected val itemList: List<Item> by lazy { createItemsList() }
override val consequence: ItemsChangeConsequence? = null
override val payload: MutableMap<String, Any?> = mutableMapOf()
override fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback?) {
if (itemList.isEmpty()) return
val foundItem = itemList.findDataItem(id) ?: return
foundItem.setData(value)
applyChangesByAffectedItems(foundItem, callback)
}
override fun getItems(): MutableList<Item> = itemList.toMutableList()
override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? = null
override fun attachPayload(payload: Payload) {
payload.forEach { this.payload[it.key] = it.value }
}
// Use it if current item needs to be affect any other items
protected open fun applyChangesByAffectedItems(param: Item, callback: AffectedItemsCallback?) {
consequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) }
}
protected fun getAttrsForAction(cardManager: CardManager)
: AttrForAction = AttrForAction(cardManager, itemList, payload, consequence)
abstract fun createItemsList(): List<Item>
}

View file

@ -0,0 +1,26 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import com.tangem.CardManager
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.impl.EditTextItem
import com.tangem.tangemtest.ucase.domain.actions.DepersonalizeAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.variants.TlvId
/**
[REDACTED_AUTHOR]
*/
class DepersonalizeItemsManager : BaseItemsManager(DepersonalizeAction()) {
override fun createItemsList(): List<Item> {
return listOf(EditTextItem(TlvId.CardId, null))
}
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
}
override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? {
return action.getActionByTag(this, id, getAttrsForAction(cardManager))
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import com.tangem.CardManager
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest.ucase.domain.actions.PersonalizeAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
/**
[REDACTED_AUTHOR]
*/
class PersonalizeItemsManager : BaseItemsManager(PersonalizeAction()) {
override fun createItemsList(): List<Item> = listOf()
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import com.tangem.CardManager
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest.ucase.domain.actions.ScanAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
/**
[REDACTED_AUTHOR]
*/
class ScanItemsManager : BaseItemsManager(ScanAction()) {
override fun createItemsList(): List<Item> = listOf()
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
}
}

View file

@ -0,0 +1,34 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import com.tangem.CardManager
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.impl.EditTextItem
import com.tangem.tangemtest.ucase.domain.actions.SignAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.SignScanConsequence
import com.tangem.tangemtest.ucase.variants.TlvId
/**
[REDACTED_AUTHOR]
*/
class SignItemsManager : BaseItemsManager(SignAction()) {
override val consequence: ItemsChangeConsequence? by lazy { SignScanConsequence() }
override fun createItemsList(): List<Item> {
return listOf(
EditTextItem(TlvId.CardId, null),
EditTextItem(TlvId.TransactionOutHash, "Data used for hashing")
)
}
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
}
override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? {
return action.getActionByTag(this, id, getAttrsForAction(cardManager))
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tasks.TaskEvent
/**
[REDACTED_AUTHOR]
*
* The After Action Modification class family is intended for modifying items (if necessary)
* after calling CardManager.anyAction.
* Returns a list of items that have been modified
*/
interface AfterActionModification {
fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List<Item>): List<Item>
}

View file

@ -0,0 +1,42 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction
import com.tangem.commands.Card
import com.tangem.commands.CardStatus
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.findDataItem
import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
import com.tangem.tangemtest.ucase.tunnel.ActionView
import com.tangem.tangemtest.ucase.tunnel.CardError
import com.tangem.tangemtest.ucase.variants.TlvId
import com.tangem.tasks.ScanEvent
import com.tangem.tasks.TaskEvent
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI
/**
[REDACTED_AUTHOR]
*/
class AfterScanModifier : AfterActionModification {
override fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List<Item>): List<Item> {
val foundItem = itemList.findDataItem(TlvId.CardId) ?: return listOf()
val card = smartCast(taskEvent)?.card ?: return listOf()
val actionView = payload.get(PayloadKey.actionView) as? ActionView ?: return listOf()
return if (isNotPersonalized(card)) {
actionView.showSnackbar(CardError.NotPersonalized)
postUI { actionView.showActionFab(false) }
listOf()
} else {
payload.set(PayloadKey.card, card)
foundItem.setData(card.cardId)
postUI { actionView.showActionFab(true) }
listOf(foundItem)
}
}
private fun smartCast(taskEvent: TaskEvent<*>): ScanEvent.OnReadEvent? {
return (taskEvent as? TaskEvent.Event)?.data as? ScanEvent.OnReadEvent
}
private fun isNotPersonalized(card: Card): Boolean = card.status == CardStatus.NotPersonalized
}

View file

@ -0,0 +1,47 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence
import com.tangem.commands.Card
import com.tangem.commands.EllipticCurve
import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.calculateSha512
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.findDataItem
import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
import com.tangem.tangemtest.ucase.tunnel.ActionView
import com.tangem.tangemtest.ucase.variants.TlvId
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI
/**
[REDACTED_AUTHOR]
*
* The ParamsChangeConsequence class family modifies parameters depending on the state
* of the incoming parameter
*/
interface ItemsChangeConsequence {
fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List<Item>): List<Item>?
}
class SignScanConsequence: ItemsChangeConsequence {
override fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List<Item>): List<Item>? {
if (changedItem.id != TlvId.CardId) return null
val hashItem = itemList.findDataItem(TlvId.TransactionOutHash) ?: return null
val affectedItems = mutableListOf(hashItem)
val card = payload.remove(PayloadKey.card) as Card?
if (card == null) {
hashItem.restoreDefaultData()
postUI { (payload.get(PayloadKey.actionView) as? ActionView)?.showActionFab(false) }
} else {
val dataForHashing = hashItem.getData() as? String ?: "Any data mother...s"
val hashedData = when(card.curve) {
EllipticCurve.Secp256k1 -> dataForHashing.calculateSha256()
EllipticCurve.Ed25519 -> dataForHashing.calculateSha512()
else -> return null
}
hashItem.setData(hashedData)
}
return affectedItems.toList()
}
}

View file

@ -0,0 +1,65 @@
package com.tangem.tangemtest.ucase.domain.responses
import com.google.gson.*
import com.tangem.commands.ProductMask
import com.tangem.commands.Settings
import com.tangem.commands.SettingsMask
import com.tangem.commands.SigningMethod
import com.tangem.common.extensions.toHexString
import java.lang.reflect.Type
import java.text.DateFormat
import java.util.*
/**
[REDACTED_AUTHOR]
*/
class GsonInitializer {
val gson: Gson by lazy { init() }
private fun init(): Gson {
val builder = GsonBuilder().apply {
registerTypeAdapter(ByteArray::class.java, ByteTypeAdapter())
registerTypeAdapter(SigningMethod::class.java, SigningMethodTypeAdapter())
registerTypeAdapter(SettingsMask::class.java, SettingsMaskTypeAdapter())
registerTypeAdapter(ProductMask::class.java, ProductMaskTypeAdapter())
registerTypeAdapter(Date::class.java, DateTypeAdapter())
}
builder.setPrettyPrinting()
return builder.create()
}
}
class ByteTypeAdapter : JsonSerializer<ByteArray> {
override fun serialize(src: ByteArray, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.toHexString())
}
}
class SettingsMaskTypeAdapter : 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
}
}
class ProductMaskTypeAdapter : JsonSerializer<ProductMask> {
override fun serialize(src: ProductMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.rawValue.toString())
}
}
class SigningMethodTypeAdapter : JsonSerializer<SigningMethod> {
override fun serialize(src: SigningMethod, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.rawValue.toString())
}
}
class DateTypeAdapter: JsonSerializer<Date> {
override fun serialize(src: Date, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
val formatter = DateFormat.getDateInstance(DateFormat.DEFAULT, Locale("en_US"))
return JsonPrimitive(formatter.format(src).toString())
}
}

View file

@ -0,0 +1,22 @@
package com.tangem.tangemtest.ucase.resources
import com.tangem.tangemtest._arch.structure.Id
/**
[REDACTED_AUTHOR]
*/
enum class ActionType : Id {
Scan,
Sign,
CreateWallet,
PurgeWallet,
ReadIssuerData,
WriteIssuerData,
ReadIssuerExData,
WriteIssuerExData,
ReadUserData,
WriteUserData,
Personalize,
Depersonalize,
Unknown,
}

View file

@ -0,0 +1,26 @@
package com.tangem.tangemtest.ucase.resources
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest.ucase.resources.initializers.ActionResources
import com.tangem.tangemtest.ucase.resources.initializers.PersonalizeResources
import com.tangem.tangemtest.ucase.resources.initializers.TlvResources
import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder
/**
[REDACTED_AUTHOR]
*/
open class ResourceHolder<T> : BaseTypedHolder<T, Resources>()
object MainResourceHolder : ResourceHolder<Id>() {
init {
PersonalizeResources().init(this)
ActionResources().init(this)
TlvResources().init(this)
}
inline fun <reified Res : Resources> safeGet(id: Id): Res {
val result = super.get(id) ?: Resources(R.string.unknown, R.string.unknown)
return result as Res
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.tangemtest.ucase.resources
/**
[REDACTED_AUTHOR]
*/
open class Resources(
val resName: Int,
val resDescription: Int? = null
)
class ActionRes(
resName: Int,
resDescription: Int? = null,
val resNavigation: Int? = null
) : Resources(resName, resDescription)

View file

@ -0,0 +1,29 @@
package com.tangem.tangemtest.ucase.resources.initializers
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest.ucase.resources.ActionRes
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.resources.ResourceHolder
/**
[REDACTED_AUTHOR]
*/
class ActionResources {
fun init(holder: ResourceHolder<Id>) {
holder.register(ActionType.Scan, ActionRes(R.string.action_card_scan, R.string.info_action_card_scan, R.id.action_nav_entry_point_to_nav_scan))
holder.register(ActionType.Sign, ActionRes(R.string.action_card_sign, R.string.info_action_card_sign, R.id.action_nav_entry_point_to_nav_sign))
holder.register(ActionType.Personalize, ActionRes(R.string.action_personalize, R.string.info_action_personalize, R.id.action_nav_entry_point_to_nav_personalize))
holder.register(ActionType.Depersonalize, ActionRes(R.string.action_depersonalize, R.string.info_action_depersonalize, R.id.action_nav_entry_point_to_nav_depersonalize))
holder.register(ActionType.CreateWallet, ActionRes(R.string.action_wallet_create, R.string.info_action_wallet_create, R.id.action_nav_entry_point_to_nav_wallet_create))
holder.register(ActionType.PurgeWallet, ActionRes(R.string.action_wallet_purge, R.string.info_action_wallet_purge, R.id.action_nav_entry_point_to_nav_wallet_purge))
holder.register(ActionType.ReadIssuerData, ActionRes(R.string.action_issuer_read_data, R.string.info_action_issuer_read_data, R.id.action_nav_entry_point_to_nav_issuer_read_data))
holder.register(ActionType.WriteIssuerData, ActionRes(R.string.action_issuer_write_data, R.string.info_action_issuer_write_data, R.id.action_nav_entry_point_to_nav_issuer_write_data))
holder.register(ActionType.ReadIssuerExData, ActionRes(R.string.action_issuer_read_ex_data, R.string.info_action_issuer_read_ex_data, R.id.action_nav_entry_point_to_nav_issuer_read_ex_data))
holder.register(ActionType.WriteIssuerExData, ActionRes(R.string.action_issuer_write_ex_data, R.string.info_action_issuer_write_ex_data, R.id.action_nav_entry_point_to_nav_issuer_write_ex_data))
holder.register(ActionType.ReadUserData, ActionRes(R.string.action_user_read_data, R.string.info_action_user_read_data, R.id.action_nav_entry_point_to_nav_user_read_data))
holder.register(ActionType.WriteUserData, ActionRes(R.string.action_user_write_data, R.string.info_action_user_write_data, R.id.action_nav_entry_point_to_nav_user_write_data))
// holder.register(ActionType.Unknown, getIfNotContains())
}
}

View file

@ -0,0 +1,131 @@
package com.tangem.tangemtest.ucase.resources.initializers
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest.ucase.resources.ResourceHolder
import com.tangem.tangemtest.ucase.resources.Resources
import com.tangem.tangemtest.ucase.variants.personalize.*
import ru.dev.gbixahue.eu4d.lib.kotlin.common.TypedHolder
/**
[REDACTED_AUTHOR]
*/
class PersonalizeResources() {
fun init(holder: ResourceHolder<Id>) {
initBlock(holder)
initCardNumber(holder)
initCommon(holder)
initSigningMethod(holder)
initSignHashExProp(holder)
initDenomination(holder)
initToken(holder)
initProductMask(holder)
initSettingsMask(holder)
initSettingsMaskProtocolEnc(holder)
initSettingsMaskNde(holder)
initPins(holder)
}
private fun initBlock(holder: TypedHolder<Id, Resources>) {
holder.register(BlockId.CardNumber, Resources(R.string.pers_block_card_number, R.string.info_pers_block_card_number))
holder.register(BlockId.Common, Resources(R.string.pers_block_common, R.string.info_pers_block_common))
holder.register(BlockId.SigningMethod, Resources(R.string.pers_block_signing_method, R.string.info_pers_block_signing_method))
holder.register(BlockId.SignHashExProp, Resources(R.string.pers_block_sign_hash_ex_prop, R.string.info_pers_block_sign_hash_ex_prop))
holder.register(BlockId.Denomination, Resources(R.string.pers_block_denomination, R.string.info_pers_block_denomination))
holder.register(BlockId.Token, Resources(R.string.pers_block_token, R.string.info_pers_block_token))
holder.register(BlockId.ProdMask, Resources(R.string.pers_block_product_mask, R.string.info_pers_block_product_mask))
holder.register(BlockId.SettingsMask, Resources(R.string.pers_block_settings_mask, R.string.info_pers_block_settings_mask))
holder.register(BlockId.SettingsMaskProtocolEnc, Resources(R.string.pers_block_settings_mask_protocol_enc, R.string.info_pers_block_settings_mask_protocol_enc))
holder.register(BlockId.SettingsMaskNdef, Resources(R.string.pers_block_settings_mask_ndef, R.string.info_pers_block_settings_mask_ndef))
holder.register(BlockId.Pins, Resources(R.string.pers_block_pins, R.string.info_pers_block_pins))
}
private fun initCardNumber(holder: TypedHolder<Id, Resources>) {
holder.register(CardNumber.Series, Resources(R.string.pers_item_series, R.string.info_pers_item_series))
holder.register(CardNumber.Number, Resources(R.string.pers_item_number, R.string.info_pers_item_number))
holder.register(CardNumber.BatchId, Resources(R.string.pers_item_batch_id, R.string.info_pers_item_batch_id))
}
private fun initCommon(holder: TypedHolder<Id, Resources>) {
holder.register(Common.Curve, Resources(R.string.pers_item_curve, R.string.info_pers_item_curve))
holder.register(Common.Blockchain, Resources(R.string.pers_item_blockchain, R.string.info_pers_item_blockchain))
holder.register(Common.BlockchainCustom, Resources(R.string.pers_item_custom_blockchain, R.string.info_pers_item_custom_blockchain))
holder.register(Common.MaxSignatures, Resources(R.string.pers_item_max_signatures, R.string.info_pers_item_max_signatures))
holder.register(Common.CreateWallet, Resources(R.string.pers_item_create_wallet, R.string.info_pers_item_create_wallet))
}
private fun initSigningMethod(holder: TypedHolder<Id, Resources>) {
holder.register(SigningMethod.SignTx, Resources(R.string.pers_item_sign_tx_hashes, R.string.info_pers_item_sign_tx_hashes))
holder.register(SigningMethod.SignTxRaw, Resources(R.string.pers_item_sign_raw_tx, R.string.info_pers_item_sign_raw_tx))
holder.register(SigningMethod.SignValidatedTx, Resources(R.string.pers_item_sign_validated_tx_hashes, R.string.info_pers_item_sign_validated_tx_hashes))
holder.register(SigningMethod.SignValidatedTxRaw, Resources(R.string.pers_item_sign_validated_raw_tx, R.string.info_pers_item_sign_validated_raw_tx))
holder.register(SigningMethod.SignValidatedTxIssuer, Resources(R.string.pers_item_sign_validated_tx_hashes_with_iss_data, R.string.info_pers_item_sign_validated_tx_hashes_with_iss_data))
holder.register(SigningMethod.SignValidatedTxRawIssuer, Resources(R.string.pers_item_sign_validated_raw_tx_with_iss_data, R.string.info_pers_item_sign_validated_raw_tx_with_iss_data))
holder.register(SigningMethod.SignExternal, Resources(R.string.pers_item_sign_hash_ex, R.string.info_pers_item_sign_hash_ex))
}
private fun initSignHashExProp(holder: TypedHolder<Id, Resources>) {
holder.register(SignHashExProp.PinLessFloorLimit, Resources(R.string.pers_item_pin_less_floor_limit, R.string.info_pers_item_pin_less_floor_limit))
holder.register(SignHashExProp.CryptoExKey, Resources(R.string.pers_item_cr_ex_key, R.string.info_pers_item_cr_ex_key))
holder.register(SignHashExProp.RequireTerminalCertSig, Resources(R.string.pers_item_require_terminal_cert_sig, R.string.info_pers_item_require_terminal_cert_sig))
holder.register(SignHashExProp.RequireTerminalTxSig, Resources(R.string.pers_item_require_terminal_tx_sig, R.string.info_pers_item_require_terminal_tx_sig))
holder.register(SignHashExProp.CheckPin3, Resources(R.string.pers_item_check_pin3_on_card, R.string.info_pers_item_check_pin3_on_card))
}
private fun initDenomination(holder: TypedHolder<Id, Resources>) {
holder.register(Denomination.WriteOnPersonalize, Resources(R.string.pers_item_write_on_personalize, R.string.info_pers_item_write_on_personalize))
holder.register(Denomination.Denomination, Resources(R.string.pers_item_denomination, R.string.info_pers_item_denomination))
}
private fun initToken(holder: TypedHolder<Id, Resources>) {
holder.register(Token.ItsToken, Resources(R.string.pers_item_its_token, R.string.info_pers_item_its_token))
holder.register(Token.Symbol, Resources(R.string.pers_item_symbol, R.string.info_pers_item_symbol))
holder.register(Token.ContractAddress, Resources(R.string.pers_item_contract_address, R.string.info_pers_item_contract_address))
holder.register(Token.Decimal, Resources(R.string.pers_item_decimal, R.string.info_pers_item_decimal))
}
private fun initProductMask(holder: TypedHolder<Id, Resources>) {
holder.register(ProductMask.Note, Resources(R.string.pers_item_note, R.string.info_pers_item_note))
holder.register(ProductMask.Tag, Resources(R.string.pers_item_tag, R.string.info_pers_item_tag))
holder.register(ProductMask.CardId, Resources(R.string.pers_item_id_card, R.string.info_pers_item_id_card))
}
private fun initSettingsMask(holder: TypedHolder<Id, Resources>) {
holder.register(SettingsMask.IsReusable, Resources(R.string.pers_item_is_reusable, R.string.info_pers_item_is_reusable))
holder.register(SettingsMask.NeedActivation, Resources(R.string.pers_item_need_activation, R.string.info_pers_item_need_activation))
holder.register(SettingsMask.ForbidPurge, Resources(R.string.pers_item_forbid_purge, R.string.info_pers_item_forbid_purge))
holder.register(SettingsMask.AllowSelectBlockchain, Resources(R.string.pers_item_allow_select_blockchain, R.string.info_pers_item_allow_select_blockchain))
holder.register(SettingsMask.UseBlock, Resources(R.string.pers_item_use_block, R.string.info_pers_item_use_block))
holder.register(SettingsMask.OneApdu, Resources(R.string.pers_item_one_apdu_at_once, R.string.info_pers_item_one_apdu_at_once))
holder.register(SettingsMask.UseCvc, Resources(R.string.pers_item_use_cvc, R.string.info_pers_item_use_cvc))
holder.register(SettingsMask.AllowSwapPin, Resources(R.string.pers_item_allow_swap_pin, R.string.info_pers_item_allow_swap_pin))
holder.register(SettingsMask.AllowSwapPin2, Resources(R.string.pers_item_allow_swap_pin2, R.string.info_pers_item_allow_swap_pin2))
holder.register(SettingsMask.ForbidDefaultPin, Resources(R.string.pers_item_forbid_default_pin, R.string.info_pers_item_forbid_default_pin))
holder.register(SettingsMask.SmartSecurityDelay, Resources(R.string.pers_item_smart_security_delay, R.string.info_pers_item_smart_security_delay))
holder.register(SettingsMask.ProtectIssuerDataAgainstReplay, Resources(R.string.pers_item_protect_issuer_data_against_replay, R.string.info_pers_item_protect_issuer_data_against_replay))
holder.register(SettingsMask.SkipSecurityDelayIfValidated, Resources(R.string.pers_item_skip_security_delay_if_validated, R.string.info_pers_item_skip_security_delay_if_validated))
holder.register(SettingsMask.SkipPin2CvcIfValidated, Resources(R.string.pers_item_skip_pin2_and_cvc_if_validated, R.string.info_pers_item_skip_pin2_and_cvc_if_validated))
holder.register(SettingsMask.SkipSecurityDelayOnLinkedTerminal, Resources(R.string.pers_item_skip_security_delay_on_linked_terminal, R.string.info_pers_item_skip_security_delay_on_linked_terminal))
holder.register(SettingsMask.RestrictOverwriteExtraIssuerData, Resources(R.string.pers_item_restrict_overwrite_ex_issuer_data, R.string.info_pers_item_restrict_overwrite_ex_issuer_data))
}
private fun initSettingsMaskProtocolEnc(holder: TypedHolder<Id, Resources>) {
holder.register(SettingsMaskProtocolEnc.AllowUnencrypted, Resources(R.string.pers_item_allow_unencrypted, R.string.info_pers_item_allow_unencrypted))
holder.register(SettingsMaskProtocolEnc.AllowStaticEncryption, Resources(R.string.pers_item_allow_fast_encryption, R.string.info_pers_item_allow_fast_encryption))
}
private fun initSettingsMaskNde(holder: TypedHolder<Id, Resources>) {
holder.register(SettingsMaskNdef.UseNdef, Resources(R.string.pers_item_use_ndef, R.string.info_pers_item_use_ndef))
holder.register(SettingsMaskNdef.DynamicNdef, Resources(R.string.pers_item_dynamic_ndef, R.string.info_pers_item_dynamic_ndef))
holder.register(SettingsMaskNdef.DisablePrecomputedNdef, Resources(R.string.pers_item_disable_precomputed_ndef, R.string.info_pers_item_disable_precomputed_ndef))
holder.register(SettingsMaskNdef.Aar, Resources(R.string.pers_item_aar, R.string.info_pers_item_aar))
}
private fun initPins(holder: TypedHolder<Id, Resources>) {
holder.register(Pins.Pin, Resources(R.string.pers_item_pin, R.string.info_pers_item_pin))
holder.register(Pins.Pin2, Resources(R.string.pers_item_pin2, R.string.info_pers_item_pin2))
holder.register(Pins.Pin3, Resources(R.string.pers_item_pin3, R.string.info_pers_item_pin3))
holder.register(Pins.Cvc, Resources(R.string.pers_item_cvc, R.string.info_pers_item_cvc))
holder.register(Pins.PauseBeforePin2, Resources(R.string.pers_item_pause_before_pin2, R.string.info_pers_item_pause_before_pin2))
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.tangemtest.ucase.resources.initializers
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
import com.tangem.tangemtest.ucase.resources.Resources
import com.tangem.tangemtest.ucase.variants.TlvId
/**
[REDACTED_AUTHOR]
*/
class TlvResources {
fun init(holder: MainResourceHolder) {
initScan(holder)
}
private fun initScan(holder: MainResourceHolder) {
holder.register(TlvId.CardId, Resources(R.string.tlv_card_id, R.string.info_tlv_card_id))
holder.register(TlvId.TransactionOutHash, Resources(R.string.tlv_transaction_out_hash, R.string.info_tlv_transaction_out_hash))
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.tangemtest.ucase.tunnel
import com.tangem.tangemtest._arch.structure.Id
/**
[REDACTED_AUTHOR]
*/
interface Errors: Id
enum class CardError : Errors {
NotPersonalized,
}
enum class ItemError: Errors {
BadSeries,
BadCardNumber,
}

View file

@ -0,0 +1,15 @@
package com.tangem.tangemtest.ucase.tunnel
import com.tangem.tangemtest._arch.structure.Id
interface ViewScreen
interface SnackbarHolder {
fun showSnackbar(message: String)
fun showSnackbar(id: Int)
fun showSnackbar(id: Id)
}
interface ActionView : ViewScreen, SnackbarHolder {
fun showActionFab(show: Boolean)
}

View file

@ -0,0 +1,150 @@
package com.tangem.tangemtest.ucase.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.tangem.CardManager
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
import com.tangem.tangemtest._main.MainViewModel
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.ParamsManagerFactory
import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.tunnel.ActionView
import com.tangem.tangemtest.ucase.tunnel.CardError
import com.tangem.tangemtest.ucase.ui.widgets.ParameterWidget
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder
/**
[REDACTED_AUTHOR]
*/
abstract class BaseCardActionFragment : Fragment(), LayoutHolder, ActionView {
protected val incomingParamsContainer: ViewGroup by lazy {
mainView.findViewById<LinearLayout>(R.id.ll_incoming_params_container)
}
protected lateinit var mainView: View
protected val actionFab: FloatingActionButton by lazy { mainView.findViewById<FloatingActionButton>(R.id.fab_action) }
protected val paramsVM: ParamsViewModel by viewModels { ActionViewModelFactory(itemsManager) }
protected val widgetList = mutableListOf<ParameterWidget>()
private val itemsManager: ItemsManager by lazy { ParamsManagerFactory.createFactory().get(getAction())!! }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Log.d(this, "onCreateView")
mainView = inflater.inflate(getLayoutId(), container, false)
return mainView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d(this, "onViewCreated")
viewLifecycleOwner.lifecycle.addObserver(paramsVM)
paramsVM.setCardManager(CardManager.init(requireActivity()))
paramsVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView))
initFab()
showActionFab(false)
createWidgets { subscribeToViewModelChanges() }
}
private fun initFab() {
Log.d(this, "initFab")
actionFab.setOnClickListener { paramsVM.invokeMainAction() }
}
private fun createWidgets(widgetCreatedCallback: () -> Unit) {
Log.d(this, "createWidgets")
paramsVM.ldParams.observe(viewLifecycleOwner, Observer { itemList ->
itemList.forEach { param ->
val widget = ParameterWidget(inflateParamView(incomingParamsContainer), param)
widget.onValueChanged = { id, value -> paramsVM.userChangedItem(id, value) }
widget.onActionBtnClickListener = paramsVM.getItemAction(param.id)
widgetList.add(widget)
}
widgetCreatedCallback()
})
}
private fun subscribeToViewModelChanges() {
Log.d(this, "subscribeToViewModelChanges")
listenResponse()
listenError()
listenChangedItems()
listenDescriptionSwitchChanges()
}
protected open fun listenResponse() {
val tvResponse by lazy { mainView.findViewById<TextView>(R.id.tv_action_response_json) }
paramsVM.ldResponse.observe(viewLifecycleOwner, Observer {
Log.d(this, "action response: ${if (it.length > 50) it.substring(0..50) else it}")
tvResponse.text = it
})
}
protected open fun listenError() {
paramsVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) })
}
protected open fun listenChangedItems() {
paramsVM.seChangedItems.observe(viewLifecycleOwner, Observer { itemList ->
itemList.forEach { item ->
Log.d(this, "item changed from VM - name: ${item.id}")
val dataItem = item as? BaseItem<Any?> ?: return@Observer
Log.d(this, "item changed from VM - name: ${dataItem.id}, value:${dataItem.viewModel.data}")
widgetList.firstOrNull { it.id == item.id }?.changeParamValue(item.viewModel.data)
}
})
}
protected open fun listenDescriptionSwitchChanges() {
val mainActViewModel by activityViewModels<MainViewModel>()
mainActViewModel.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer { isEnabled ->
widgetList.forEach { it.toggleDescriptionVisibility(isEnabled) }
})
}
private fun inflateParamView(where: ViewGroup): ViewGroup {
val inflater = LayoutInflater.from(where.context)
val view = inflater.inflate(R.layout.w_card_incoming_param, where, false)
where.addView(view)
return view as ViewGroup
}
override fun showActionFab(show: Boolean) {
if (show) actionFab.show() else actionFab.hide()
}
override fun showSnackbar(id: Id) {
// MainResourceHolder.safeGet<>()
when (id) {
CardError.NotPersonalized -> showSnackbar(R.string.card_error_not_personalized)
else -> showSnackbar(requireContext().getString(R.string.unknown))
}
}
override fun showSnackbar(id: Int) {
showSnackbar(requireContext().getString(id))
}
override fun showSnackbar(message: String) {
Snackbar.make(mainView, message, BaseTransientBottomBar.LENGTH_SHORT).show()
}
abstract fun getAction(): ActionType
}

View file

@ -0,0 +1,144 @@
package com.tangem.tangemtest.ucase.ui
import androidx.annotation.UiThread
import androidx.lifecycle.*
import com.google.gson.Gson
import com.tangem.CardManager
import com.tangem.commands.Card
import com.tangem.tangemtest._arch.SingleLiveEvent
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest.commons.performAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.responses.GsonInitializer
import com.tangem.tangemtest.ucase.tunnel.ViewScreen
import com.tangem.tasks.ScanEvent
import com.tangem.tasks.TaskError
import com.tangem.tasks.TaskEvent
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
/**
[REDACTED_AUTHOR]
*/
class ActionViewModelFactory(private val manager: ItemsManager) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = ParamsViewModel(manager) as T
}
class ParamsViewModel(private val itemsManager: ItemsManager) : ViewModel(), LifecycleObserver {
val ldCard = MutableLiveData<Card>()
val ldIsVerified = MutableLiveData<Boolean>()
val ldResponse = MutableLiveData<String>()
val ldReadResponse = MutableLiveData<String>()
val ldParams = MutableLiveData(itemsManager.getItems())
val seError: MutableLiveData<String> = SingleLiveEvent()
val seChangedItems: MutableLiveData<List<Item>> = SingleLiveEvent()
private val notifier: Notifier = Notifier(this)
private lateinit var cardManager: CardManager
fun setCardManager(cardManager: CardManager) {
this.cardManager = cardManager
}
fun userChangedItem(id: Id, value: Any?) {
itemChanged(id, value)
}
private fun itemChanged(id: Id, value: Any?) {
itemsManager.itemChanged(id, value) { notifier.notifyItemsChanged(it) }
}
//invokes Scan, Sign etc...
fun invokeMainAction() {
performAction(itemsManager, cardManager) { paramsManager, cardManager ->
paramsManager.invokeMainAction(cardManager) { response, listOfChangedParams ->
notifier.handleActionResult(response, listOfChangedParams)
}
}
}
fun getItemAction(id: Id): (() -> Unit)? {
val itemFunction = itemsManager.getActionByTag(id, cardManager) ?: return null
return {
itemFunction { response, listOfChangedParams ->
notifier.handleActionResult(response, listOfChangedParams)
}
}
}
fun attachToPayload(payload: Payload) {
itemsManager.attachPayload(payload)
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun viewOnDestroy() {
val keyList = mutableListOf<String>()
itemsManager.payload.filterValues { it is ViewScreen }.forEach { keyList.add(it.key) }
keyList.forEach { itemsManager.payload.remove(it) }
}
}
internal class Notifier(private val vm: ParamsViewModel) {
private var notShowedError: TaskError? = null
private val gson: Gson = GsonInitializer().gson
fun handleActionResult(response: TaskEvent<*>, list: List<Item>) {
if (list.isNotEmpty()) notifyItemsChanged(list)
handleResponse(response)
}
@UiThread
fun notifyItemsChanged(list: List<Item>) {
vm.seChangedItems.postValue(list)
}
fun handleResponse(response: TaskEvent<*>) {
val taskEvent = response as? TaskEvent<*> ?: return
when (taskEvent) {
is TaskEvent.Completion -> handleCompletionEvent(taskEvent)
is TaskEvent.Event -> handleDataEvent(taskEvent.data)
}
}
private fun handleDataEvent(event: Any?) {
when (event) {
is ScanEvent.OnReadEvent -> {
vm.ldCard.postValue(event.card)
vm.ldReadResponse.postValue(gson.toJson(event))
}
is ScanEvent.OnVerifyEvent -> {
vm.ldIsVerified.postValue(true)
}
else -> vm.ldResponse.postValue(gson.toJson(event))
}
}
private fun handleCompletionEvent(taskEvent: TaskEvent.Completion<*>) {
if (taskEvent.error == null) {
Log.d(this, "error = null")
if (notShowedError != null) {
vm.seError.postValue("${notShowedError!!::class.simpleName}")
notShowedError = null
}
} else {
Log.d(this, "error = ${taskEvent.error}")
when (taskEvent.error) {
is TaskError.UserCancelled -> {
if (notShowedError == null) {
vm.seError.postValue("User canceled the action")
} else {
vm.seError.postValue("${notShowedError!!::class.simpleName}")
notShowedError = null
}
}
else -> notShowedError = taskEvent.error
}
}
}
}

View file

@ -0,0 +1,119 @@
package com.tangem.tangemtest.ucase.ui.widgets
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
import com.tangem.tangemtest.ucase.resources.Resources
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
class ParameterWidget(
private val parent: ViewGroup,
item: Item
) {
val id: Id = item.id
val dataItem: BaseItem<Any> = item as BaseItem<Any>
var onValueChanged: ((Id, Any?) -> Unit)? = null
var onActionBtnClickListener: (() -> Unit)? = null
set(value) {
field = value
toggleActionBtnVisibility()
}
private val tilValue: TextInputLayout = parent.findViewById(R.id.til_param)
private val etValue: TextInputEditText = parent.findViewById(R.id.et_param)
private val btnAction: Button = parent.findViewById(R.id.btn_action)
private val valueWatcher: TextWatcher by lazy { getWatcher() }
private val descriptionContainer: ViewGroup by lazy { parent.findViewById<ViewGroup>(R.id.container_description) }
private val tvDescription: TextView? by lazy { descriptionContainer.findViewById<TextView>(R.id.tv_description) }
private var actionBtnVisibilityState: Int = btnAction.visibility
private var value: Any? = dataItem.viewModel.data
init {
tilValue.hint = tilValue.context.getString(getResNameId())
etValue.setText(stringOf(dataItem.viewModel.data))
etValue.addTextChangedListener(valueWatcher)
btnAction.setOnClickListener { onActionBtnClickListener?.invoke() }
btnAction.setText(getResNameId(ActionType.Scan))
toggleActionBtnVisibility()
initDescriptionWidget()
}
fun changeParamValue(data: Any?, silent: Boolean = true) {
Log.d(this, "changeParamValue: tag: $id, value: $data")
value = data
toggleActionBtnVisibility()
if (silent) {
etValue.removeTextChangedListener(valueWatcher)
etValue.setText(stringOf(value))
etValue.addTextChangedListener(valueWatcher)
} else {
etValue.setText(stringOf(value))
}
}
fun toggleDescriptionVisibility(state: Boolean) {
TransitionManager.beginDelayedTransition(parent.parent as ViewGroup, AutoTransition())
descriptionContainer.visibility = if (state) View.VISIBLE else View.GONE
}
private fun getWatcher(): TextWatcher {
return object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
Log.d(this, "afterTextChanged $editable")
value = if (editable.isNullOrEmpty()) null else editable.toString()
toggleActionBtnVisibility()
onValueChanged?.invoke(id, value)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}
}
private fun toggleActionBtnVisibility() {
fun switchVisibilityState(newState: Int) {
actionBtnVisibilityState = newState
TransitionManager.beginDelayedTransition(parent, AutoTransition())
btnAction.visibility = actionBtnVisibilityState
}
when {
onActionBtnClickListener == null -> {
if (actionBtnVisibilityState == View.GONE) return
switchVisibilityState(View.GONE)
}
value == null && actionBtnVisibilityState != View.VISIBLE -> switchVisibilityState(View.VISIBLE)
value != null && actionBtnVisibilityState != View.GONE -> switchVisibilityState(View.GONE)
}
}
private fun initDescriptionWidget() {
getResDescription()?.let { tvDescription?.setText(it) }
}
}
fun ParameterWidget.getResNameId(id: Id? = null): Int = MainResourceHolder.safeGet<Resources>(id
?: this.id).resName
fun ParameterWidget.getResDescription(): Int? = MainResourceHolder.safeGet<Resources>(id).resDescription

View file

@ -0,0 +1,11 @@
package com.tangem.tangemtest.ucase.variants
import com.tangem.tangemtest._arch.structure.Id
/**
[REDACTED_AUTHOR]
*/
enum class TlvId : Id {
CardId,
TransactionOutHash,
}

View file

@ -0,0 +1,16 @@
package com.tangem.tangemtest.ucase.variants.depersonalize.ui
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
/**
[REDACTED_AUTHOR]
*/
class DepersonalizeActionFragment : BaseCardActionFragment() {
override fun getLayoutId(): Int = R.layout.fg_depersonalize
override fun getAction(): ActionType = ActionType.Depersonalize
}

View file

@ -0,0 +1,115 @@
package com.tangem.tangemtest.ucase.variants.personalize
import com.tangem.tangemtest._arch.structure.Id
/**
[REDACTED_AUTHOR]
*/
interface PersonalizeId : Id
interface BlockItem : PersonalizeId
enum class BlockId : PersonalizeId {
CardNumber,
Common,
SigningMethod,
SignHashExProp,
Denomination,
Token,
ProdMask,
SettingsMask,
SettingsMaskProtocolEnc,
SettingsMaskNdef,
Pins,
}
enum class CardNumber : BlockItem {
Series,
Number,
BatchId,
}
enum class Common : BlockItem {
Curve,
Blockchain,
BlockchainCustom,
MaxSignatures,
CreateWallet,
}
enum class SigningMethod : BlockItem {
SignTx,
SignTxRaw,
SignValidatedTx,
SignValidatedTxRaw,
SignValidatedTxIssuer,
SignValidatedTxRawIssuer,
SignExternal,
}
enum class SignHashExProp : BlockItem {
PinLessFloorLimit,
CryptoExKey,
RequireTerminalCertSig,
RequireTerminalTxSig,
CheckPin3,
}
enum class Denomination : BlockItem {
WriteOnPersonalize,
Denomination,
}
enum class Token : BlockItem {
ItsToken,
Symbol,
ContractAddress,
Decimal
}
enum class ProductMask : BlockItem {
Note,
Tag,
CardId
}
enum class SettingsMask : BlockItem {
IsReusable,
NeedActivation,
ForbidPurge,
AllowSelectBlockchain,
UseBlock,
OneApdu,
UseCvc,
AllowSwapPin,
AllowSwapPin2,
ForbidDefaultPin,
SmartSecurityDelay,
ProtectIssuerDataAgainstReplay,
SkipSecurityDelayIfValidated,
SkipPin2CvcIfValidated,
SkipSecurityDelayOnLinkedTerminal,
RestrictOverwriteExtraIssuerData,
}
enum class SettingsMaskProtocolEnc : BlockItem {
AllowUnencrypted,
AllowStaticEncryption
}
enum class SettingsMaskNdef : BlockItem {
UseNdef,
DynamicNdef,
DisablePrecomputedNdef,
Aar,
AarCustom
}
enum class Pins : BlockItem {
Pin,
Pin2,
Pin3,
Cvc,
PauseBeforePin2
}

View file

@ -0,0 +1,135 @@
package com.tangem.tangemtest.ucase.variants.personalize.converter
import com.tangem.commands.EllipticCurve
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.impl.KeyValue
import com.tangem.tangemtest.ucase.variants.personalize.*
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig
import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder
/**
[REDACTED_AUTHOR]
*/
class PersonalizeConfigValuesHolder : BaseTypedHolder<Id, Value>() {
// All registered association values can't be objects
fun init(default: PersonalizeConfig) {
register(CardNumber.Series, Value(default.series))
register(CardNumber.Number, Value(default.startNumber))
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.MaxSignatures, Value(default.MaxSignatures))
register(Common.CreateWallet, Value(default.createWallet))
register(SigningMethod.SignTx, Value(default.SigningMethod0))
register(SigningMethod.SignTxRaw, Value(default.SigningMethod1))
register(SigningMethod.SignValidatedTx, Value(default.SigningMethod2))
register(SigningMethod.SignValidatedTxRaw, Value(default.SigningMethod3))
register(SigningMethod.SignValidatedTxIssuer, Value(default.SigningMethod4))
register(SigningMethod.SignValidatedTxRawIssuer, Value(default.SigningMethod5))
register(SigningMethod.SignExternal, Value(default.SigningMethod6))
register(SignHashExProp.PinLessFloorLimit, Value(default.pinLessFloorLimit))
register(SignHashExProp.CryptoExKey, Value(default.hexCrExKey))
register(SignHashExProp.RequireTerminalCertSig, Value(default.requireTerminalCertSignature))
register(SignHashExProp.RequireTerminalTxSig, Value(default.requireTerminalTxSignature))
register(SignHashExProp.CheckPin3, Value(default.checkPIN3onCard))
register(Denomination.WriteOnPersonalize, Value(default.writeOnPersonalization))
register(Denomination.Denomination, Value(default.denomination))
register(Token.ItsToken, Value(default.itsToken))
register(Token.Symbol, Value(default.symbol))
register(Token.ContractAddress, Value(default.contractAddress))
register(Token.Decimal, Value(default.decimal))
register(ProductMask.Note, Value(default.cardData.product_note))
register(ProductMask.Tag, Value(default.cardData.product_tag))
register(ProductMask.CardId, Value(default.cardData.product_id_card))
register(SettingsMask.IsReusable, Value(default.isReusable))
register(SettingsMask.NeedActivation, Value(default.useActivation))
register(SettingsMask.ForbidPurge, Value(default.forbidPurgeWallet))
register(SettingsMask.AllowSelectBlockchain, Value(default.allowSelectBlockchain))
register(SettingsMask.UseBlock, Value(default.useBlock))
register(SettingsMask.OneApdu, Value(default.oneApdu))
register(SettingsMask.UseCvc, Value(default.useCVC))
register(SettingsMask.AllowSwapPin, Value(default.allowSwapPIN))
register(SettingsMask.AllowSwapPin2, Value(default.allowSwapPIN2))
register(SettingsMask.ForbidDefaultPin, Value(default.forbidDefaultPIN))
register(SettingsMask.SmartSecurityDelay, Value(default.smartSecurityDelay))
register(SettingsMask.ProtectIssuerDataAgainstReplay, Value(default.protectIssuerDataAgainstReplay))
register(SettingsMask.SkipSecurityDelayIfValidated, Value(default.skipSecurityDelayIfValidatedByIssuer))
register(SettingsMask.SkipPin2CvcIfValidated, Value(default.skipCheckPIN2andCVCIfValidatedByIssuer))
register(SettingsMask.SkipSecurityDelayOnLinkedTerminal, Value(default.skipSecurityDelayIfValidatedByLinkedTerminal))
register(SettingsMask.RestrictOverwriteExtraIssuerData, Value(default.restrictOverwriteIssuerDataEx))
register(SettingsMaskProtocolEnc.AllowUnencrypted, Value(default.protocolAllowUnencrypted))
register(SettingsMaskProtocolEnc.AllowStaticEncryption, Value(default.protocolAllowStaticEncryption))
register(SettingsMaskNdef.UseNdef, Value(default.useNDEF))
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(Pins.Pin, Value(default.PIN))
register(Pins.Pin2, Value(default.PIN2))
register(Pins.Pin3, Value(default.PIN3))
register(Pins.Cvc, Value(default.CVC))
register(Pins.PauseBeforePin2, Value(default.pauseBeforePIN2, Helper.pauseBeforePin()))
}
}
class Value(
private var value: Any? = null,
val list: List<KeyValue>? = mutableListOf()
) {
fun get(): Any? = value
fun set(newValue: Any?) {
value = newValue
}
}
internal class Helper {
companion object {
fun listOfCurves(): List<KeyValue> {
return EllipticCurve.values().map { KeyValue(it.name, it.curve) }
}
fun listOfBlockchain(): List<KeyValue> {
return mutableListOf(
KeyValue("--- CUSTOM ---", ""),
KeyValue("BTC", "BTC"),
KeyValue("BTC/test", "BTC/test"),
KeyValue("ETH", "ETH"),
KeyValue("ETH/test", "ETH/test"),
KeyValue("BCH", "BCH"),
KeyValue("BCH/test", "BCH/test"),
KeyValue("LTC", "LTC"),
KeyValue("XLM", "XLM"),
KeyValue("XLM/test", "XLM/test"),
KeyValue("RSK", "RSK"),
KeyValue("XPR", "XPR"),
KeyValue("CARDANO", "CARDANO"),
KeyValue("BNB", "BNB"),
KeyValue("XTZ", "XTZ"),
KeyValue("DUC", "DUC")
)
}
fun aarList(): List<KeyValue> {
return mutableListOf(
KeyValue("--- CUSTOM ---", ""),
KeyValue("Release APP", "com.tangem.wallet"),
KeyValue("Debug APP", "com.tangem.wallet.debug"),
KeyValue("None", "")
)
}
fun pauseBeforePin(): List<KeyValue> {
return mutableListOf(
KeyValue("immediately", 0L),
KeyValue("2 seconds", 2000L),
KeyValue("5 seconds", 5000L),
KeyValue("15 seconds", 15000L),
KeyValue("30 seconds", 30000L),
KeyValue("1 minute", 60000L),
KeyValue("2 minute", 120000L)
)
}
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.tangemtest.ucase.variants.personalize.converter
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ItemsToModel
import com.tangem.tangemtest._arch.structure.abstraction.ModelConverter
import com.tangem.tangemtest._arch.structure.abstraction.ModelToItems
import com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo.ItemsToPersonalizeConfig
import com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo.PersonalizeConfigToItem
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig
/**
[REDACTED_AUTHOR]
*/
class PersonalizeConfigConverter : ModelConverter<PersonalizeConfig> {
private val toModel: ItemsToModel<PersonalizeConfig> = ItemsToPersonalizeConfig()
private val toItems: ModelToItems<PersonalizeConfig> = PersonalizeConfigToItem()
override fun convert(from: List<Item>, default: PersonalizeConfig): PersonalizeConfig {
return toModel.convert(from, default)
}
override fun convert(from: PersonalizeConfig): List<Item> {
return toItems.convert(from)
}
}

View file

@ -0,0 +1,46 @@
package com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo
import com.tangem.tangemtest.ucase.variants.personalize.*
class ItemTypes {
val blockIdList = mutableListOf(
BlockId.CardNumber, BlockId.Common, BlockId.SigningMethod, BlockId.SignHashExProp, BlockId.Denomination,
BlockId.Token, BlockId.ProdMask, BlockId.SettingsMask, BlockId.SettingsMaskProtocolEnc,
BlockId.SettingsMaskNdef, BlockId.Pins
)
val listItemList = mutableListOf(Common.Curve, Common.Blockchain, SettingsMaskNdef.Aar, Pins.PauseBeforePin2)
val boolList = mutableListOf(
Common.CreateWallet, SigningMethod.SignTx, SigningMethod.SignTxRaw, SigningMethod.SignValidatedTx,
SigningMethod.SignValidatedTxRaw, SigningMethod.SignValidatedTxIssuer,
SigningMethod.SignValidatedTxRawIssuer, SigningMethod.SignExternal, SignHashExProp.RequireTerminalCertSig,
SignHashExProp.RequireTerminalTxSig, SignHashExProp.CheckPin3, Denomination.WriteOnPersonalize, Token.ItsToken,
ProductMask.Note, ProductMask.Tag, ProductMask.CardId, SettingsMask.IsReusable, SettingsMask.NeedActivation,
SettingsMask.ForbidPurge, SettingsMask.AllowSelectBlockchain, SettingsMask.UseBlock, SettingsMask.OneApdu,
SettingsMask.UseCvc, SettingsMask.AllowSwapPin, SettingsMask.AllowSwapPin2, SettingsMask.ForbidDefaultPin,
SettingsMask.SmartSecurityDelay, SettingsMask.ProtectIssuerDataAgainstReplay,
SettingsMask.SkipSecurityDelayIfValidated, SettingsMask.SkipPin2CvcIfValidated,
SettingsMask.SkipSecurityDelayOnLinkedTerminal, SettingsMask.RestrictOverwriteExtraIssuerData,
SettingsMaskProtocolEnc.AllowUnencrypted, SettingsMaskProtocolEnc.AllowStaticEncryption,
SettingsMaskNdef.UseNdef, SettingsMaskNdef.DynamicNdef, SettingsMaskNdef.DisablePrecomputedNdef
)
val editTextList = mutableListOf(
CardNumber.Series, CardNumber.BatchId, Common.BlockchainCustom, SignHashExProp.CryptoExKey, Token.Symbol,
Token.ContractAddress, Pins.Pin, Pins.Pin2, Pins.Pin3, Pins.Cvc, SettingsMaskNdef.AarCustom
)
val numberList = mutableListOf(
CardNumber.Number, Common.MaxSignatures, SignHashExProp.PinLessFloorLimit, Denomination.Denomination, Token.Decimal
)
val hiddenList = mutableListOf(
CardNumber.Series, CardNumber.BatchId, Pins.Pin3, SigningMethod.SignExternal,
SignHashExProp.CryptoExKey, SignHashExProp.CheckPin3, SettingsMask.OneApdu,
SettingsMask.UseBlock, SettingsMask.ProtectIssuerDataAgainstReplay,
SignHashExProp.RequireTerminalCertSig, SignHashExProp.RequireTerminalTxSig
)
}

View file

@ -0,0 +1,116 @@
package com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
import com.tangem.tangemtest._arch.structure.abstraction.Block
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ItemsToModel
import com.tangem.tangemtest._arch.structure.impl.KeyValue
import com.tangem.tangemtest._arch.structure.impl.ListValueWrapper
import com.tangem.tangemtest.ucase.variants.personalize.*
import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizeConfigValuesHolder
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
/**
[REDACTED_AUTHOR]
*/
class ItemsToPersonalizeConfig : ItemsToModel<PersonalizeConfig> {
protected val valuesHolder = PersonalizeConfigValuesHolder()
override fun convert(from: List<Item>, default: PersonalizeConfig): PersonalizeConfig {
valuesHolder.init(default)
mapListItems(from)
return createModel()
}
private fun mapListItems(itemList: List<Item>) {
itemList.forEach { item -> mapItemToHolder(item) }
}
private fun mapItemToHolder(item: Item) {
when (item) {
is Block -> mapListItems(item.itemList)
is BaseItem<*> -> {
val defValue = valuesHolder.get(item.id) ?: return
defValue.set(item.viewModel.data)
}
}
}
private fun createModel(): PersonalizeConfig {
val export = PersonalizeConfig()
export.series = getTyped(CardNumber.Series)
export.startNumber = getTyped(CardNumber.Number)
export.curveID = getTyped(Common.Curve)
export.blockchain = getTyped(Common.Blockchain)
export.blockchainCustom = getTyped(Common.BlockchainCustom)
export.MaxSignatures = getTyped(Common.MaxSignatures)
export.createWallet = getTyped(Common.CreateWallet)
export.SigningMethod0 = getTyped(SigningMethod.SignTx)
export.SigningMethod1 = getTyped(SigningMethod.SignTxRaw)
export.SigningMethod2 = getTyped(SigningMethod.SignValidatedTx)
export.SigningMethod3 = getTyped(SigningMethod.SignValidatedTxRaw)
export.SigningMethod4 = getTyped(SigningMethod.SignValidatedTxIssuer)
export.SigningMethod5 = getTyped(SigningMethod.SignValidatedTxRawIssuer)
export.SigningMethod6 = getTyped(SigningMethod.SignExternal)
export.pinLessFloorLimit = getTyped(SignHashExProp.PinLessFloorLimit)
export.hexCrExKey = getTyped(SignHashExProp.CryptoExKey)
export.requireTerminalCertSignature = getTyped(SignHashExProp.RequireTerminalCertSig)
export.requireTerminalTxSignature = getTyped(SignHashExProp.RequireTerminalTxSig)
export.checkPIN3onCard = getTyped(SignHashExProp.CheckPin3)
export.writeOnPersonalization = getTyped(Denomination.WriteOnPersonalize)
export.denomination = getTyped(Denomination.Denomination)
export.itsToken = getTyped(Token.ItsToken)
export.symbol = getTyped(Token.Symbol)
export.contractAddress = getTyped(Token.ContractAddress)
export.decimal = getTyped(Token.Decimal)
export.cardData = export.cardData.apply { this.product_note = getTyped(ProductMask.Note) }
export.cardData = export.cardData.apply { this.product_tag = getTyped(ProductMask.Tag) }
export.cardData = export.cardData.apply { this.product_id_card = getTyped(ProductMask.CardId) }
export.isReusable = getTyped(SettingsMask.IsReusable)
export.useActivation = getTyped(SettingsMask.NeedActivation)
export.forbidPurgeWallet = getTyped(SettingsMask.ForbidPurge)
export.allowSelectBlockchain = getTyped(SettingsMask.AllowSelectBlockchain)
export.useBlock = getTyped(SettingsMask.UseBlock)
export.oneApdu = getTyped(SettingsMask.OneApdu)
export.useCVC = getTyped(SettingsMask.UseCvc)
export.allowSwapPIN = getTyped(SettingsMask.AllowSwapPin)
export.allowSwapPIN2 = getTyped(SettingsMask.AllowSwapPin2)
export.forbidDefaultPIN = getTyped(SettingsMask.ForbidDefaultPin)
export.smartSecurityDelay = getTyped(SettingsMask.SmartSecurityDelay)
export.protectIssuerDataAgainstReplay = getTyped(SettingsMask.ProtectIssuerDataAgainstReplay)
export.skipSecurityDelayIfValidatedByIssuer = getTyped(SettingsMask.SkipSecurityDelayIfValidated)
export.skipCheckPIN2andCVCIfValidatedByIssuer = getTyped(SettingsMask.SkipPin2CvcIfValidated)
export.skipSecurityDelayIfValidatedByLinkedTerminal = getTyped(SettingsMask.SkipSecurityDelayOnLinkedTerminal)
export.restrictOverwriteIssuerDataEx = getTyped(SettingsMask.RestrictOverwriteExtraIssuerData)
export.protocolAllowUnencrypted = getTyped(SettingsMaskProtocolEnc.AllowUnencrypted)
export.protocolAllowStaticEncryption = getTyped(SettingsMaskProtocolEnc.AllowStaticEncryption)
export.useNDEF = getTyped(SettingsMaskNdef.UseNdef)
export.useDynamicNDEF = getTyped(SettingsMaskNdef.DynamicNdef)
export.disablePrecomputedNDEF = getTyped(SettingsMaskNdef.DisablePrecomputedNdef)
export.aar = getTyped(SettingsMaskNdef.Aar)
export.aarCustom = getTyped(SettingsMaskNdef.AarCustom)
export.PIN = getTyped(Pins.Pin)
export.PIN2 = getTyped(Pins.Pin2)
export.PIN3 = getTyped(Pins.Pin3)
export.CVC = getTyped(Pins.Cvc)
export.pauseBeforePIN2 = getTyped(Pins.PauseBeforePin2)
return export
}
private inline fun <reified Type> getTyped(id: Id): Type {
return getTypedBy<Type>(valuesHolder, id)!!
}
private inline fun <reified Type> getTypedBy(holder: PersonalizeConfigValuesHolder, id: Id): Type? {
Log.d(this, "getTyped for id: $id")
var typedValue = holder.get(id)?.get()
typedValue = when (typedValue) {
is ListValueWrapper -> (typedValue.selectedItem as KeyValue).value as Type
else -> typedValue as? Type ?: null
}
return typedValue
}
}

View file

@ -0,0 +1,298 @@
package com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo
import com.tangem.commands.CardData
import com.tangem.commands.EllipticCurve
import com.tangem.commands.ProductMaskBuilder
import com.tangem.commands.personalization.entities.CardConfig
import com.tangem.commands.personalization.entities.NdefRecord
import com.tangem.tangemtest._arch.structure.Additional
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.*
import com.tangem.tangemtest._arch.structure.impl.*
import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
import com.tangem.tangemtest.ucase.variants.personalize.*
import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizeConfigValuesHolder
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig
import ru.dev.gbixahue.eu4d.lib.kotlin.common.Converter
import java.util.*
/**
[REDACTED_AUTHOR]
*/
class PersonalizeConfigToItem : ModelToItems<PersonalizeConfig> {
private val valuesHolder = PersonalizeConfigValuesHolder()
private val itemTypes = ItemTypes()
override fun convert(from: PersonalizeConfig): List<Item> {
valuesHolder.init(from)
val blocList = mutableListOf<Item>()
blocList.add(cardNumber())
blocList.add(common())
blocList.add(signingMethod())
blocList.add(signHashExProperties())
blocList.add(denomination())
blocList.add(token())
blocList.add(productMask())
blocList.add(settingsMask())
blocList.add(settingsMaskProtocolEnc())
blocList.add(settingsMaskNdef())
blocList.add(pins())
val payloadBlock = ListItemBlock(Additional.JSON_TAILS)
addPayload(payloadBlock, from)
blocList.add(payloadBlock)
blocList.iterate {
if (itemTypes.hiddenList.contains(it.id)) {
(it as BaseItem<*>)?.viewModel.viewState.isHiddenField = true
}
}
return blocList
}
private fun cardNumber(): Block {
val block = createBlock(BlockId.CardNumber)
mutableListOf(
CardNumber.Series,
CardNumber.Number,
CardNumber.BatchId
).forEach { createItem(block, it as Id) }
return block
}
private fun common(): Block {
val block = createBlock(BlockId.Common)
mutableListOf(
Common.Curve,
Common.Blockchain,
Common.BlockchainCustom,
Common.MaxSignatures,
Common.CreateWallet
).forEach { createItem(block, it) }
return block
}
private fun signingMethod(): Block {
val block = createBlock(BlockId.SigningMethod)
mutableListOf(
SigningMethod.SignTx,
SigningMethod.SignTxRaw,
SigningMethod.SignValidatedTx,
SigningMethod.SignValidatedTxRaw,
SigningMethod.SignValidatedTxIssuer,
SigningMethod.SignValidatedTxRawIssuer,
SigningMethod.SignExternal
).forEach { createItem(block, it) }
return block
}
private fun signHashExProperties(): Block {
val block = createBlock(BlockId.SignHashExProp)
mutableListOf(
SignHashExProp.PinLessFloorLimit,
SignHashExProp.CryptoExKey,
SignHashExProp.RequireTerminalCertSig,
SignHashExProp.RequireTerminalTxSig,
SignHashExProp.CheckPin3
).forEach { createItem(block, it) }
return block
}
private fun denomination(): Block {
val block = createBlock(BlockId.Denomination)
mutableListOf(
Denomination.WriteOnPersonalize,
Denomination.Denomination
).forEach { createItem(block, it) }
return block
}
private fun token(): Block {
val block = createBlock(BlockId.Token)
mutableListOf(
Token.ItsToken,
Token.Symbol,
Token.ContractAddress,
Token.Decimal
).forEach { createItem(block, it) }
return block
}
private fun productMask(): Block {
val block = createBlock(BlockId.ProdMask)
mutableListOf(
ProductMask.Note,
ProductMask.Tag,
ProductMask.CardId
).forEach { createItem(block, it) }
return block
}
private fun settingsMask(): Block {
val block = createBlock(BlockId.SettingsMask)
mutableListOf(
SettingsMask.IsReusable,
SettingsMask.NeedActivation,
SettingsMask.ForbidPurge,
SettingsMask.AllowSelectBlockchain,
SettingsMask.UseBlock,
SettingsMask.OneApdu,
SettingsMask.UseCvc,
SettingsMask.AllowSwapPin,
SettingsMask.AllowSwapPin2,
SettingsMask.ForbidDefaultPin,
SettingsMask.SmartSecurityDelay,
SettingsMask.ProtectIssuerDataAgainstReplay,
SettingsMask.SkipSecurityDelayIfValidated,
SettingsMask.SkipPin2CvcIfValidated,
SettingsMask.SkipSecurityDelayOnLinkedTerminal,
SettingsMask.RestrictOverwriteExtraIssuerData
).forEach { createItem(block, it) }
return block
}
private fun settingsMaskProtocolEnc(): Block {
val block = createBlock(BlockId.SettingsMaskProtocolEnc)
mutableListOf(
SettingsMaskProtocolEnc.AllowUnencrypted,
SettingsMaskProtocolEnc.AllowStaticEncryption
).forEach { createItem(block, it) }
return block
}
private fun settingsMaskNdef(): Block {
val block = createBlock(BlockId.SettingsMaskNdef)
mutableListOf(
SettingsMaskNdef.UseNdef,
SettingsMaskNdef.DynamicNdef,
SettingsMaskNdef.DisablePrecomputedNdef,
SettingsMaskNdef.Aar,
SettingsMaskNdef.AarCustom
).forEach { createItem(block, it) }
return block
}
private fun pins(): Block {
val block = createBlock(BlockId.Pins)
mutableListOf(Pins.Pin, Pins.Pin2, Pins.Pin3, Pins.Cvc, Pins.PauseBeforePin2)
.forEach { createItem(block, it) }
return block
}
private fun createBlock(id: Id): ListItemBlock {
return ListItemBlock(id).apply { addItem(TextItem(id)) }
}
private fun addPayload(block: Block, from: PersonalizeConfig) {
block.payload[PayloadKey.incomingJson] = from
}
private fun createItem(block: ListItemBlock, id: Id) {
val holder = valuesHolder.get(id) ?: return
val item = when {
itemTypes.blockIdList.contains(id) -> TextItem(id, holder.get() as? String)
itemTypes.listItemList.contains(id) -> ListItem(id, holder.list as List<KeyValue>, holder.get())
itemTypes.boolList.contains(id) -> BoolItem(id, holder.get() as? Boolean)
itemTypes.editTextList.contains(id) -> EditTextItem(id, holder.get() as? String)
itemTypes.numberList.contains(id) -> NumberItem(id, holder.get() as? Number)
else -> ListItemBlock(Additional.UNDEFINED)
}
block.addItem(item)
}
}
class PersonalizeConfigToCardConfig : Converter<PersonalizeConfig, CardConfig> {
override fun convert(from: PersonalizeConfig): 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 isNote = from.cardData.product_note
val isTag = from.cardData.product_tag
val isIdCard = from.cardData.product_id_card
val productMaskBuilder = ProductMaskBuilder()
if (isNote) productMaskBuilder.add(com.tangem.commands.ProductMask.note)
if (isTag) productMaskBuilder.add(com.tangem.commands.ProductMask.tag)
if (isIdCard) productMaskBuilder.add(com.tangem.commands.ProductMask.idCard)
val productMask = productMaskBuilder.build()
var tokenSymbol: String? = null
var tokenContractAddress: String? = null
var tokenDecimal: Int? = null
if (from.itsToken) {
tokenSymbol = from.symbol
tokenContractAddress = from.contractAddress
tokenDecimal = from.decimal.toInt()
}
val blockchain = if (from.blockchain.isNotEmpty()) from.blockchain else from.blockchainCustom
val cardData = CardData(
blockchainName = blockchain,
batchId = from.batchId,
productMask = productMask,
tokenSymbol = tokenSymbol,
tokenContractAddress = tokenContractAddress,
tokenDecimal = tokenDecimal,
issuerName = null,
manufactureDateTime = Calendar.getInstance().time,
manufacturerSignature = null)
val ndefs = mutableListOf<NdefRecord>()
when (from.aar) {
"None" -> null
"--- CUSTOM ---" -> NdefRecord(NdefRecord.Type.AAR, from.aarCustom)
else -> NdefRecord(NdefRecord.Type.AAR, from.aar)
}?.let { ndefs.add(it) }
return CardConfig(
"Tangem",
"Tangem Test",
from.series,
from.startNumber,
1000,
from.PIN,
from.PIN2,
from.PIN3,
from.hexCrExKey,
from.CVC,
from.pauseBeforePIN2.toInt(),
from.smartSecurityDelay,
EllipticCurve.byName(from.curveID) ?: EllipticCurve.Secp256k1,
signingMethod,
from.MaxSignatures.toInt(),
from.isReusable,
from.allowSwapPIN,
from.allowSwapPIN2,
from.useActivation,
from.useCVC,
from.useNDEF,
from.useDynamicNDEF,
from.oneApdu,
from.useBlock,
from.allowSelectBlockchain,
from.forbidPurgeWallet,
from.protocolAllowUnencrypted,
from.protocolAllowStaticEncryption,
from.protectIssuerDataAgainstReplay,
from.forbidDefaultPIN,
from.disablePrecomputedNDEF,
from.skipSecurityDelayIfValidatedByIssuer,
from.skipCheckPIN2andCVCIfValidatedByIssuer,
from.skipSecurityDelayIfValidatedByLinkedTerminal,
from.restrictOverwriteIssuerDataEx,
from.requireTerminalTxSignature,
from.requireTerminalCertSignature,
from.checkPIN3onCard,
from.createWallet,
cardData,
ndefs
)
}
}

View file

@ -0,0 +1,51 @@
package com.tangem.tangemtest.ucase.variants.personalize.dto
import com.tangem.commands.personalization.entities.Acquirer
import com.tangem.commands.personalization.entities.Issuer
import com.tangem.commands.personalization.entities.Manufacturer
import com.tangem.common.KeyPair
/**
[REDACTED_AUTHOR]
*/
interface DefaultPersonalizeParams {
companion object {
fun issuer(): Issuer {
val name = "TANGEM SDK"
return Issuer(
name = name,
id = name + "\u0000",
dataKeyPair = KeyPair(
"".toByteArray(),
"11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92".toByteArray()
),
transactionKeyPair = KeyPair(
"".toByteArray(),
"11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312".toByteArray()
)
)
}
fun acquirer(): Acquirer {
val name = "Smart Cash"
return Acquirer(
name = name,
id = name + "\u0000",
keyPair = KeyPair(
"".toByteArray(),
"21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92".toByteArray()
)
)
}
fun manufacturer(): Manufacturer {
val keys = Manufacturer(com.tangem.tangemtest.ucase.variants.personalize.dto.Manufacturer.Mode.Developer)
return Manufacturer(
name = "Tangem",
keyPair = KeyPair(keys.publicKey, keys.privateKey)
)
}
}
}

View file

@ -0,0 +1,53 @@
package com.tangem.tangemtest.ucase.variants.personalize.dto
class Manufacturer(private val mode: Mode) {
private val devPrivateKeyArray = byteArrayOf(
0x1B.toByte(), 0x48.toByte(), 0xCF.toByte(), 0xD2.toByte(), 0x4B.toByte(), 0xBB.toByte(), 0x5B.toByte(), 0x39.toByte(),
0x47.toByte(), 0x71.toByte(), 0xED.toByte(), 0x81.toByte(), 0xF2.toByte(), 0xBA.toByte(), 0xCF.toByte(), 0x57.toByte(),
0x47.toByte(), 0x9E.toByte(), 0x47.toByte(), 0x35.toByte(), 0xEB.toByte(), 0x14.toByte(), 0x05.toByte(), 0x08.toByte(),
0x39.toByte(), 0x27.toByte(), 0x37.toByte(), 0x2D.toByte(), 0x40.toByte(), 0xDA.toByte(), 0x9E.toByte(), 0x92.toByte()
)
private val devPublicKeyArray = byteArrayOf(0x04,
0xBA.toByte(), 0xB8.toByte(), 0x6D.toByte(), 0x56.toByte(), 0x29.toByte(), 0x8C.toByte(), 0x99.toByte(), 0x6F.toByte(),
0x56.toByte(), 0x4A.toByte(), 0x84.toByte(), 0xFC.toByte(), 0x88.toByte(), 0xE2.toByte(), 0x8A.toByte(), 0xED.toByte(),
0x38.toByte(), 0x18.toByte(), 0x4B.toByte(), 0x12.toByte(), 0xF0.toByte(), 0x7E.toByte(), 0x51.toByte(), 0x91.toByte(),
0x13.toByte(), 0xBE.toByte(), 0xF4.toByte(), 0x8C.toByte(), 0x76.toByte(), 0xF3.toByte(), 0xDF.toByte(), 0x3A.toByte(),
0xDC.toByte(), 0x30.toByte(), 0x35.toByte(), 0x99.toByte(), 0xB0.toByte(), 0x8A.toByte(), 0xC0.toByte(), 0x5B.toByte(),
0x55.toByte(), 0xEC.toByte(), 0x3D.toByte(), 0xF9.toByte(), 0x8D.toByte(), 0x93.toByte(), 0x38.toByte(), 0x57.toByte(),
0x3A.toByte(), 0x62.toByte(), 0x42.toByte(), 0xF7.toByte(), 0x6F.toByte(), 0x5D.toByte(), 0x28.toByte(), 0xF4.toByte(),
0xF0.toByte(), 0xF3.toByte(), 0x64.toByte(), 0xE8.toByte(), 0x7E.toByte(), 0x8F.toByte(), 0xCA.toByte(), 0x2F.toByte()
)
private val releasePublicKeyArray = byteArrayOf(0x04.toByte(),
0x2E.toByte(), 0xDE.toByte(), 0x11.toByte(), 0x9B.toByte(), 0xF3.toByte(), 0x37.toByte(), 0xB2.toByte(), 0x64.toByte(),
0xFD.toByte(), 0xA1.toByte(), 0x32.toByte(), 0xCF.toByte(), 0xC7.toByte(), 0xC1.toByte(), 0x77.toByte(), 0x82.toByte(),
0x4D.toByte(), 0x36.toByte(), 0x17.toByte(), 0xDA.toByte(), 0xC8.toByte(), 0x0F.toByte(), 0x25.toByte(), 0xDB.toByte(),
0xB2.toByte(), 0xA4.toByte(), 0xA8.toByte(), 0xA1.toByte(), 0x18.toByte(), 0x3C.toByte(), 0x03.toByte(), 0xB9.toByte(),
0x15.toByte(), 0x23.toByte(), 0x05.toByte(), 0xF8.toByte(), 0xF1.toByte(), 0xDB.toByte(), 0x97.toByte(), 0x00.toByte(),
0x45.toByte(), 0x18.toByte(), 0x48.toByte(), 0x0D.toByte(), 0x50.toByte(), 0x91.toByte(), 0xAD.toByte(), 0xC1.toByte(),
0xCA.toByte(), 0xB9.toByte(), 0xEA.toByte(), 0xCC.toByte(), 0xC1.toByte(), 0x8E.toByte(), 0x1B.toByte(), 0x9E.toByte(),
0x9C.toByte(), 0x3B.toByte(), 0xEF.toByte(), 0xB2.toByte(), 0x93.toByte(), 0xDD.toByte(), 0x37.toByte(), 0xB2.toByte()
)
val publicKey: ByteArray
get() = if (mode == Mode.Developer) {
devPublicKeyArray
} else {
releasePublicKeyArray
}
@get:Throws(Exception::class)
val privateKey: ByteArray
get() = if (mode == Mode.Developer) {
devPrivateKeyArray
} else {
throw Exception("Real manufacturer key is very secret!")
}
val name: String
get() = if (mode == Mode.Release) "SMART CASH" else "SMART CASH SDK"
enum class Mode {
Developer, Release
}
}

View file

@ -0,0 +1,108 @@
package com.tangem.tangemtest.ucase.variants.personalize.dto
/**
[REDACTED_AUTHOR]
*/
class PersonalizeConfig {
// Card number
var series = "BB"
var startNumber: Long = 300000000000
var batchId = "ffff"
// Common
var curveID = "ed25519"
var blockchain = "BTC/test"
var blockchainCustom = ""
var MaxSignatures: Long = 100
var createWallet = false
// Signing method
var SigningMethod0 = true
var SigningMethod1 = false
var SigningMethod2 = false
var SigningMethod3 = false
var SigningMethod4 = false
var SigningMethod5 = false
var SigningMethod6 = false
// Sign hash external properties
var pinLessFloorLimit: Long = 100000
var hexCrExKey = "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"
var requireTerminalTxSignature = false
var requireTerminalCertSignature = false
var checkPIN3onCard = true
// Denomination
var writeOnPersonalization = false
var denomination: Long = 1000000
// Token
var itsToken = false
var symbol = ""
var contractAddress = ""
var decimal: Long = 0
var cardData = CardData()
// Settings mask
var isReusable = true
var useActivation = false
var forbidPurgeWallet = false
var allowSelectBlockchain = false
var useBlock = false
var oneApdu = false
var useCVC = false
var allowSwapPIN = true
var allowSwapPIN2 = true
var forbidDefaultPIN = false
var smartSecurityDelay = false
var protectIssuerDataAgainstReplay = true
var skipSecurityDelayIfValidatedByIssuer = true
var skipCheckPIN2andCVCIfValidatedByIssuer = true
var skipSecurityDelayIfValidatedByLinkedTerminal = true
var restrictOverwriteIssuerDataEx = false
// Settings mask - protocol encryption
var protocolAllowUnencrypted = true
var protocolAllowStaticEncryption = true
var useNDEF = true
var useDynamicNDEF = true
var disablePrecomputedNDEF = false
var aar = "com.tangem.wallet"
var aarCustom = ""
// Pins
var PIN = "000000"
var PIN2 = "000"
var PIN3 = ""
var CVC = "000"
var pauseBeforePIN2: Long = 15000
// not used
// var count: Long = 20
// var numberFormat = ""
// var issuerData = null
// var releaseVersion = false
// var issuerName = "TANGEM SDK"
}
class CardData {
var date = "2020-02-17"
var batch = "FF87"
var blockchain = "IROHA"
var product_note = true
var product_tag = false
var product_id_card = false
}

View file

@ -0,0 +1,116 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.tangem.CardManager
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._main.MainViewModel
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.ParamsManagerFactory
import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.tunnel.ActionView
import com.tangem.tangemtest.ucase.tunnel.CardError
import com.tangem.tangemtest.ucase.tunnel.ItemError
import com.tangem.tangemtest.ucase.ui.ActionViewModelFactory
import com.tangem.tangemtest.ucase.ui.ParamsViewModel
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.WidgetBuilder
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
/**
[REDACTED_AUTHOR]
*/
class PersonalizeFragment : Fragment(), ActionView {
private val mainActivityVM: MainViewModel by activityViewModels()
private val personalizeVM: PersonalizeViewModel by viewModels { PersonalizeViewModelFactory(PersonalizeConfig()) }
private val itemsManager: ItemsManager by lazy { ParamsManagerFactory.createFactory().get(ActionType.Personalize)!! }
private val paramsVM: ParamsViewModel by viewModels() { ActionViewModelFactory(itemsManager) }
private lateinit var mainView: View
private val blockContainer: ViewGroup by lazy { mainView.findViewById<LinearLayout>(R.id.ll_container) }
private val actionFab: FloatingActionButton by lazy { mainView.findViewById<FloatingActionButton>(R.id.fab_action) }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Log.d(this, "onCreateView")
mainView = inflater.inflate(R.layout.fg_personalize, container, false)
return mainView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val blockList = mutableListOf<Item>()
personalizeVM.ldBlockList.observe(viewLifecycleOwner, Observer { list ->
blockList.clear()
blockList.addAll(list)
blockList.forEach { WidgetBuilder().build(it, blockContainer) }
paramsVM.attachToPayload(mutableMapOf(
PayloadKey.actionView to this as ActionView,
PayloadKey.itemList to blockList
))
})
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
personalizeVM.toggleDescriptionVisibility(it)
})
val cardManager = CardManager.init(requireActivity())
paramsVM.setCardManager(cardManager)
paramsVM.ldResponse.observe(viewLifecycleOwner, Observer {
Log.d(this, "action response: ${if (it.length > 50) it.substring(0..50) else it}")
showSnackbarMessage(it)
})
paramsVM.seError.observe(viewLifecycleOwner, Observer { showSnackbarMessage(it) })
actionFab.setOnClickListener { paramsVM.invokeMainAction() }
}
protected fun showSnackbarMessage(message: String) {
Snackbar.make(mainView, message, BaseTransientBottomBar.LENGTH_SHORT).show()
}
override fun showActionFab(show: Boolean) {
if (show) actionFab.show() else actionFab.hide()
}
override fun showSnackbar(id: Id) {
// MainResourceHolder.safeGet<>()
when (id) {
CardError.NotPersonalized -> showSnackbar(R.string.card_error_not_personalized)
ItemError.BadSeries -> showSnackbar(R.string.card_error_bad_series)
ItemError.BadCardNumber -> showSnackbar(R.string.card_error_bad_series_number)
else -> showSnackbar(requireContext().getString(R.string.unknown))
}
}
override fun showSnackbar(id: Int) {
showSnackbar(requireContext().getString(id))
}
override fun showSnackbar(message: String) {
Snackbar.make(mainView, message, BaseTransientBottomBar.LENGTH_SHORT).show()
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui
import android.view.View
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.iterate
import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizeConfigConverter
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig
/**
[REDACTED_AUTHOR]
*/
class PersonalizeViewModelFactory(private val config: PersonalizeConfig) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = PersonalizeViewModel(config) as T
}
class PersonalizeViewModel(private val config: PersonalizeConfig) : ViewModel() {
val ldBlockList: MutableLiveData<List<Item>> by lazy { MutableLiveData(initBlockList()) }
private fun initBlockList(): List<Item> = PersonalizeConfigConverter().convert(config)
fun toggleDescriptionVisibility(state: Boolean) {
ldBlockList.value?.iterate {
val baseItem = it as? BaseItem<*> ?: return@iterate
baseItem.viewModel.viewState.descriptionVisibility = if (state) View.VISIBLE else View.GONE
}
}
}

View file

@ -0,0 +1,48 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
import android.view.ViewGroup
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
import com.tangem.tangemtest._arch.structure.abstraction.Block
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ListItemBlock
import com.tangem.tangemtest._arch.structure.impl.*
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.ViewWidget
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.StubWidget
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.block.LinearBlockWidget
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item.*
/**
[REDACTED_AUTHOR]
*/
class WidgetBuilder {
fun build(item: Item, parent: ViewGroup): ViewWidget? {
return when (item) {
is Block -> buildBlock(item, parent)
is BaseItem<*> -> buildUnitItem(item, parent)
else -> StubWidget(parent)
}
}
private fun buildBlock(block: Block, parent: ViewGroup): ViewWidget {
return when (block) {
is ListItemBlock -> {
val linearBlock = LinearBlockWidget(parent, block)
block.getItems().forEach { build(it, linearBlock.view as ViewGroup) }
linearBlock
}
else -> StubWidget(parent)
}
}
private fun buildUnitItem(item: BaseItem<*>, parent: ViewGroup): ViewWidget? {
return when (item) {
is TextItem -> TextWidget(parent, item)
is EditTextItem -> EditTextWidget(parent, item)
is NumberItem -> NumberWidget(parent, item)
is BoolItem -> SwitchWidget(parent, item)
is ListItem -> SpinnerWidget(parent, item)
else -> null
}
}
}

View file

@ -0,0 +1,54 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.DataHolder
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
import com.tangem.tangemtest.ucase.resources.Resources
import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder
/**
[REDACTED_AUTHOR]
*/
interface ViewWidget : LayoutHolder {
val view: View
}
interface DataWidget<D> : ViewWidget {
var dataItem: BaseItem<D>
}
interface BlockViewWidget : ViewWidget, DataHolder<List<ItemViewModel<*>>>
abstract class BaseWidget<D>(
parent: ViewGroup,
override var dataItem: BaseItem<D>
) : ViewWidget, DataWidget<D> {
override val view: View = inflate(getLayoutId(), parent)
init {
if (dataItem.viewModel.viewState.isHiddenField) {
view.visibility = View.GONE
}
}
}
abstract class BaseBlockWidget(parent: ViewGroup) : BlockViewWidget {
override val view: View = inflate(getLayoutId(), parent)
}
fun DataWidget<*>.getResNameId(): Int = MainResourceHolder.safeGet<Resources>(dataItem.id).resName
fun DataWidget<*>.getResDescription(): Int? = MainResourceHolder.safeGet<Resources>(dataItem.id).resDescription
internal fun inflate(id: Int, parent: ViewGroup): View {
val layoutId = if (id <= 0) R.layout.w_empty else id
val inflatedView = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
parent.addView(inflatedView)
return inflatedView
}

View file

@ -0,0 +1,14 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl
import android.view.ViewGroup
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseBlockWidget
/**
[REDACTED_AUTHOR]
*/
class StubWidget(parent: ViewGroup) : BaseBlockWidget(parent) {
override fun getLayoutId(): Int = R.layout.w_empty
override var viewModel: List<ItemViewModel<*>> = listOf()
}

View file

@ -0,0 +1,21 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.block
import android.view.ViewGroup
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel
import com.tangem.tangemtest._arch.structure.abstraction.ListItemBlock
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseBlockWidget
/**
[REDACTED_AUTHOR]
*/
class LinearBlockWidget(
parent: ViewGroup,
private val children: ListItemBlock
) : BaseBlockWidget(parent) {
override fun getLayoutId(): Int = R.layout.w_personilize_block
override var viewModel: List<ItemViewModel<*>> = listOf()
get() = children.itemList.map { it as BaseItem<*> }.map { it.viewModel }
}

View file

@ -0,0 +1,54 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
import android.view.ViewGroup
import android.widget.TextView
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseWidget
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResDescription
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
/**
[REDACTED_AUTHOR]
*/
abstract class DescriptionWidget<D>(
parent: ViewGroup,
data: BaseItem<D>
) : BaseWidget<D>(parent, data) {
private val descriptionContainer: ViewGroup by lazy { view.findViewById<ViewGroup>(R.id.container_description) }
private val tvDescription: TextView? by lazy { descriptionContainer.findViewById<TextView>(R.id.tv_description) }
init {
Log.d(this, "init id: ${dataItem.id}")
initDescriptionWidget()
}
private fun initDescriptionWidget() {
dataItem.viewModel.viewState.onDescriptionVisibilityChanged = { changeDescriptionVisibility(it) }
}
protected fun changeDescriptionVisibility(state: Int) {
val tv = tvDescription ?: return
val descriptionId = getResDescription() ?: return
val description = tv.context.getString(descriptionId)
if (description.isEmpty()) return
tv.text = description
TransitionManager.beginDelayedTransition(view.parent as ViewGroup, AutoTransition())
descriptionContainer.visibility = state
}
}

View file

@ -0,0 +1,45 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
import android.text.Editable
import android.text.TextWatcher
import android.view.ViewGroup
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.impl.EditTextItem
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId
import ru.dev.gbixahue.eu4d.lib.android._android.views.moveCursorToEnd
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
class EditTextWidget(parent: ViewGroup, data: EditTextItem) : DescriptionWidget<String>(parent, data) {
override fun getLayoutId(): Int = R.layout.w_personalize_item_edit_text
private val tilItem = view.findViewById<TextInputLayout>(R.id.til_item)
private val etItem = view.findViewById<TextInputEditText>(R.id.et_item)
private val watcher = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
dataItem.viewModel.updateDataByView(stringOf(s))
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}
init {
tilItem.hint = tilItem.context.getString(getResNameId())
etItem.setText(dataItem.viewModel.data)
etItem.addTextChangedListener(watcher)
dataItem.viewModel.onDataUpdated = { silentUpdate(it) }
}
private fun silentUpdate(value: String?) {
etItem.removeTextChangedListener(watcher)
etItem.setText(value)
etItem.moveCursorToEnd()
etItem.addTextChangedListener(watcher)
}
}

View file

@ -0,0 +1,57 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
import android.text.Editable
import android.text.InputFilter
import android.text.TextWatcher
import android.view.ViewGroup
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.impl.NumberItem
import com.tangem.tangemtest.ucase.variants.personalize.CardNumber
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId
import ru.dev.gbixahue.eu4d.lib.android._android.views.addInputFilter
import ru.dev.gbixahue.eu4d.lib.android._android.views.moveCursorToEnd
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
class NumberWidget(parent: ViewGroup, data: NumberItem) : DescriptionWidget<Number>(parent, data) {
override fun getLayoutId(): Int = R.layout.w_personalize_item_number
private val tilItem = view.findViewById<TextInputLayout>(R.id.til_item)
private val etItem = view.findViewById<TextInputEditText>(R.id.et_item)
private val watcher = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
dataItem.viewModel.updateDataByView(getValue(stringOf(s)))
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}
init {
tilItem.hint = tilItem.context.getString(getResNameId())
//TODO: remove from Widget
if (dataItem.id == CardNumber.Number) etItem.addInputFilter(InputFilter.LengthFilter(13))
etItem.setText(stringOf(dataItem.viewModel.data))
etItem.addTextChangedListener(watcher)
dataItem.viewModel.onDataUpdated = { silentUpdate(it) }
}
private fun silentUpdate(value: Number?) {
etItem.removeTextChangedListener(watcher)
etItem.setText(stringOf(value))
etItem.moveCursorToEnd()
etItem.addTextChangedListener(watcher)
}
private fun getValue(value: String): Long {
return if (value.isEmpty()) 0L else value.toLong()
}
}

View file

@ -0,0 +1,92 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.BaseAdapter
import android.widget.Spinner
import android.widget.TextView
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.impl.KeyValue
import com.tangem.tangemtest._arch.structure.impl.ListItem
import com.tangem.tangemtest._arch.structure.impl.ListValueWrapper
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId
import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
class SpinnerWidget(parent: ViewGroup, listItem: ListItem) : DescriptionWidget<ListValueWrapper>(parent, listItem) {
override fun getLayoutId(): Int = R.layout.w_personalize_item_spinner
private val data: ListValueWrapper = listItem.getData()!!
private val spItem = view.findViewById<Spinner>(R.id.sp_item)
private val spAdapter = SpItemAdapter(data.itemList)
private val onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
data.selectedItem = data.itemList[position]
dataItem.viewModel.updateDataByView(data)
}
}
init {
val name = view.findViewById<TextView>(R.id.tv_name)
name.setText(getResNameId())
spItem.adapter = spAdapter
spAdapter.getItemPosition(stringOf(data.selectedItem))?.let {
spItem.setSelection(it)
}
spItem.onItemSelectedListener = onItemSelectedListener
dataItem.viewModel.onDataUpdated = {
it?.apply {
spItem.onItemSelectedListener = null
this.itemList.firstOrNull { item -> item.value == selectedItem }?.let {
val position = itemList.indexOf(it)
spItem.setSelection(position)
}
spItem.onItemSelectedListener = onItemSelectedListener
}
}
}
}
class SpItemAdapter(list: List<KeyValue>?) : BaseAdapter() {
private val itemList: List<KeyValue> = list ?: listOf()
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: parent.inflate(R.layout.vh_sp_item)
val tv = view.findViewById<TextView>(R.id.tv_sp_item)
tv.text = extractData(itemList[position])
return view
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: parent.inflate(R.layout.vh_sp_dropdown_item)
val tv = view.findViewById<TextView>(R.id.tv_sp_item)
tv.text = extractData(itemList[position])
return view
}
private fun extractData(item: KeyValue): String? = item.key
override fun getItem(position: Int): String = stringOf(itemList[position])
override fun getItemId(position: Int): Long = position.toLong()
override fun getCount(): Int = itemList.size
fun getItemPosition(item: String): Int? {
val kv = itemList.firstOrNull { it.value == item } ?: return null
return itemList.indexOf(kv)
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
import android.view.ViewGroup
import android.widget.CompoundButton
import androidx.appcompat.widget.SwitchCompat
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.impl.BoolItem
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId
/**
[REDACTED_AUTHOR]
*/
class SwitchWidget(parent: ViewGroup, data: BoolItem) : DescriptionWidget<Boolean>(parent, data) {
override fun getLayoutId(): Int = R.layout.w_personalize_item_switch
private val switchItem = view.findViewById<SwitchCompat>(R.id.sw_item)
private val changeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
dataItem.viewModel.updateDataByView(isChecked)
}
init {
switchItem.setText(getResNameId())
switchItem.isChecked = dataItem.viewModel.data ?: false
switchItem.setOnCheckedChangeListener(changeListener)
dataItem.viewModel.onDataUpdated = {
switchItem.setOnCheckedChangeListener(null)
switchItem.isChecked = it ?: false
switchItem.setOnCheckedChangeListener(changeListener)
}
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
import android.view.ViewGroup
import android.widget.TextView
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId
/**
[REDACTED_AUTHOR]
*/
class TextWidget(parent: ViewGroup, data: TextItem) : DescriptionWidget<String>(parent, data) {
override fun getLayoutId(): Int = R.layout.w_personalize_item_text
private val tvName = view.findViewById<TextView>(R.id.tv_name)
init {
tvName?.setText(getResNameId())
}
}

View file

@ -0,0 +1,37 @@
package com.tangem.tangemtest.ucase.variants.scan.ui
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.lifecycle.Observer
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
import ru.dev.gbixahue.eu4d.lib.android._android.views.show
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
/**
[REDACTED_AUTHOR]
*/
class ScanActionFragment : BaseCardActionFragment() {
override fun getLayoutId(): Int = R.layout.fg_action_card_scan
override fun getAction(): ActionType = ActionType.Scan
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
showActionFab(true)
}
override fun listenResponse() {
val tvStub = mainView.findViewById<TextView>(R.id.tv_screen_stub)
val tvResponse by lazy { mainView.findViewById<TextView>(R.id.tv_action_response_json) }
paramsVM.ldReadResponse.observe(viewLifecycleOwner, Observer {
Log.d(this, "action response: ${if (it.length > 50) it.substring(0..50) else it}")
tvStub.show(false)
tvResponse.text = it
})
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.tangemtest.ucase.variants.sign.ui
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
/**
[REDACTED_AUTHOR]
*/
class SignActionFragment : BaseCardActionFragment() {
override fun getLayoutId(): Int = R.layout.fg_action_card_sign
override fun getAction(): ActionType = ActionType.Sign
}

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2018 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2018 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
</set>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="-100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="-100%"
android:toYDelta="0%" />
</set>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="100%"
android:toYDelta="0%" />
</set>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/switchThumbActive" android:state_checked="true" />
<item android:color="@color/switchThumb" />
</selector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/switchTrackActive" android:state_checked="true" />
<item android:color="@color/switchTrack" />
</selector>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorPrimary" android:state_enabled="true" />
<item android:alpha="0.12" android:color="?attr/colorOnSurface" />
</selector>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/primary_text_dark" android:state_enabled="true" />
<item android:alpha="0.38" android:color="?attr/colorOnSurface" />
</selector>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation" />
</LinearLayout>

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