diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml deleted file mode 100644 index 09d3bc259e..0000000000 --- a/.idea/navEditor.xml +++ /dev/null @@ -1,541 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java index 3a76e07b04..cab6f02b80 100644 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ b/app/src/main/java/com/tangem/data/Blockchain.java @@ -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; diff --git a/app/src/main/java/com/tangem/ui/activity/MainActivity.kt b/app/src/main/java/com/tangem/ui/activity/MainActivity.kt index b479ad8094..c5bdb69201 100644 --- a/app/src/main/java/com/tangem/ui/activity/MainActivity.kt +++ b/app/src/main/java/com/tangem/ui/activity/MainActivity.kt @@ -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) { diff --git a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java index 8c24e30480..4c31f74772 100644 --- a/app/src/main/java/com/tangem/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/wallet/eth/EthEngine.java @@ -481,7 +481,6 @@ public class EthEngine extends CoinEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceReceived(true); coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - // Log.i("$TAG eth_get_balance", balanceCap) } break; @@ -491,8 +490,9 @@ public class EthEngine extends CoinEngine { nonce = nonce.substring(2); BigInteger count = new BigInteger(nonce, 16); coinData.setConfirmedTXCount(count); - - + if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getTransCount", nonce) } break; @@ -502,7 +502,9 @@ public class EthEngine extends CoinEngine { pending = pending.substring(2); BigInteger count = new BigInteger(pending, 16); coinData.setUnconfirmedTXCount(count); - + if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getPendingTxCount", pending) } break; @@ -582,7 +584,6 @@ public class EthEngine extends CoinEngine { }; serverApiBlockcypher.setResponseListener(blockcypherListener); - serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); diff --git a/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java b/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java index 6568157b56..713f4b503c 100644 --- a/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/matic/MaticTokenEngine.java @@ -109,7 +109,6 @@ public class MaticTokenEngine extends TokenEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceReceived(true); coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - // Log.i("$TAG eth_get_balance", balanceCap) } break; @@ -119,8 +118,10 @@ public class MaticTokenEngine extends TokenEngine { nonce = nonce.substring(2); BigInteger count = new BigInteger(nonce, 16); coinData.setConfirmedTXCount(count); - - + if (serverApiMatic.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getTransCount", nonce) } break; @@ -130,7 +131,10 @@ public class MaticTokenEngine extends TokenEngine { pending = pending.substring(2); BigInteger count = new BigInteger(pending, 16); coinData.setUnconfirmedTXCount(count); - + if (serverApiMatic.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getPendingTxCount", pending) } break; @@ -143,16 +147,6 @@ public class MaticTokenEngine extends TokenEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol)); // Log.i("$TAG eth_call", balanceCap) - - - if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); - serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); - serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); - } else { - ctx.setError("Terminated by user"); - } - } catch (Exception e) { e.printStackTrace(); } @@ -181,7 +175,8 @@ public class MaticTokenEngine extends TokenEngine { serverApiMatic.setResponseListener(responseListener); if (validateAddress(getContractAddress(ctx.getCard()))) { - serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiMatic.requestData(ServerApiMatic.MATIC_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } else { ctx.setError("Smart contract address not defined"); blockchainRequestsCallbacks.onComplete(false); diff --git a/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java b/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java index d968f05e93..27614e94ea 100644 --- a/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java +++ b/app/src/main/java/com/tangem/wallet/rsk/RskEngine.java @@ -85,8 +85,9 @@ public class RskEngine extends EthEngine { nonce = nonce.substring(2); BigInteger count = new BigInteger(nonce, 16); coinData.setConfirmedTXCount(count); - - + if (serverApiRootstock.isRequestsSequenceCompleted()) { + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getTransCount", nonce) } break; @@ -96,7 +97,9 @@ public class RskEngine extends EthEngine { pending = pending.substring(2); BigInteger count = new BigInteger(pending, 16); coinData.setUnconfirmedTXCount(count); - + if (serverApiRootstock.isRequestsSequenceCompleted()) { + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getPendingTxCount", pending) } break; @@ -122,7 +125,6 @@ public class RskEngine extends EthEngine { }; serverApiRootstock.setResponseListener(responseListener); - serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } diff --git a/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java b/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java index 0f54555f16..e7ed9eed23 100644 --- a/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/rsk/RskTokenEngine.java @@ -82,7 +82,6 @@ public class RskTokenEngine extends TokenEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceReceived(true); coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - // Log.i("$TAG eth_get_balance", balanceCap) } break; @@ -92,8 +91,10 @@ public class RskTokenEngine extends TokenEngine { nonce = nonce.substring(2); BigInteger count = new BigInteger(nonce, 16); coinData.setConfirmedTXCount(count); - - + if (serverApiRootstock.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getTransCount", nonce) } break; @@ -103,7 +104,10 @@ public class RskTokenEngine extends TokenEngine { pending = pending.substring(2); BigInteger count = new BigInteger(pending, 16); coinData.setUnconfirmedTXCount(count); - + if (serverApiRootstock.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getPendingTxCount", pending) } break; @@ -115,15 +119,6 @@ public class RskTokenEngine extends TokenEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol)); // Log.i("$TAG eth_call", balanceCap) - - if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); - serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); - serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); - } else { - ctx.setError("Terminated by user"); - } - } catch (Exception e) { e.printStackTrace(); } @@ -152,7 +147,8 @@ public class RskTokenEngine extends TokenEngine { serverApiRootstock.setResponseListener(responseListener); if (validateAddress(getContractAddress(ctx.getCard()))) { - serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiRootstock.requestData(ServerApiRootstock.ROOTSTOCK_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); } else { ctx.setError("Smart contract address not defined"); blockchainRequestsCallbacks.onComplete(false); diff --git a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java index d36abf0480..7668f0fcae 100644 --- a/app/src/main/java/com/tangem/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/wallet/token/TokenEngine.java @@ -655,7 +655,6 @@ public class TokenEngine extends CoinEngine { BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceReceived(true); coinData.setBalanceAlterInInternalUnits(new CoinEngine.InternalAmount(l, "wei")); - // Log.i("$TAG eth_get_balance", balanceCap) } break; @@ -665,8 +664,10 @@ public class TokenEngine extends CoinEngine { nonce = nonce.substring(2); BigInteger count = new BigInteger(nonce, 16); coinData.setConfirmedTXCount(count); - - + if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getTransCount", nonce) } break; @@ -676,29 +677,21 @@ public class TokenEngine extends CoinEngine { pending = pending.substring(2); BigInteger count = new BigInteger(pending, 16); coinData.setUnconfirmedTXCount(count); - + if (serverApiInfura.isRequestsSequenceCompleted()) { //getting balances after checking for pending to avoid showing old balance as verified + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); + } // Log.i("$TAG eth_getPendingTxCount", pending) } break; // case ServerApiInfura.INFURA_ETH_CALL: { try { - String balanceCap = infuraResponse.getResult(); balanceCap = balanceCap.substring(2); BigInteger l = new BigInteger(balanceCap, 16); coinData.setBalanceInInternalUnits(new CoinEngine.InternalAmount(l, ctx.getCard().tokenSymbol)); // Log.i("$TAG eth_call", balanceCap) - - - if (blockchainRequestsCallbacks.allowAdvance()) { - serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_BALANCE, 67, coinData.getWallet(), "", ""); - serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); - serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); - } else { - ctx.setError("Terminated by user"); - } - } catch (Exception e) { e.printStackTrace(); } @@ -706,7 +699,7 @@ public class TokenEngine extends CoinEngine { break; } - if (serverApiInfura.isRequestsSequenceCompleted()&& serverApiBlockcypher.isRequestsSequenceCompleted()) { + if (serverApiInfura.isRequestsSequenceCompleted() && serverApiBlockcypher.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); } else { blockchainRequestsCallbacks.onProgress(); @@ -771,13 +764,13 @@ public class TokenEngine extends CoinEngine { serverApiBlockcypher.setResponseListener(blockcypherListener); if (validateAddress(getContractAddress(ctx.getCard()))) { - serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.getWallet(), getContractAddress(ctx.getCard()), ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_TRANSACTION_COUNT, 67, coinData.getWallet(), "", ""); + serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_GET_PENDING_COUNT, 67, coinData.getWallet(), "", ""); + serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), ""); } else { ctx.setError("Smart contract address not defined"); blockchainRequestsCallbacks.onComplete(false); } - - serverApiBlockcypher.requestData(ctx.getBlockchain().getID(), ServerApiBlockcypher.BLOCKCYPHER_ADDRESS, ctx.getCoinData().getWallet(), ""); } @Override diff --git a/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java index 1b5a8213cc..eee75cd22c 100644 --- a/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java +++ b/app/src/main/java/com/tangem/wallet/xlm/XlmAssetEngine.java @@ -109,14 +109,14 @@ public class XlmAssetEngine extends CoinEngine { @Override public boolean isBalanceNotZero() { if (coinData == null) return false; - if (coinData.getXlmBalance() == null) return false; - return coinData.getXlmBalance().notZero(); + return (coinData.getXlmBalance() != null && coinData.getXlmBalance().notZero() && + coinData.getAssetBalance() != null && coinData.getAssetBalance().notZero()); } @Override public boolean hasBalanceInfo() { if (coinData == null) return false; - return coinData.getXlmBalance() != null; + return (coinData.getXlmBalance() != null && coinData.getAssetBalance() != null) || (coinData.isError404()); } diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java index 1b10a221a3..ca79936381 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpData.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpData.java @@ -122,6 +122,6 @@ public class XrpData extends CoinData { } public boolean hasUnconfirmed() { - return !balanceConfirmed.equals(balanceUnconfirmed); + return hasBalanceInfo() && !balanceConfirmed.equals(balanceUnconfirmed); } } diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/String.kt b/tangem-core/src/main/java/com/tangem/common/extensions/String.kt index 2f9d73f416..fbddb5829c 100644 --- a/tangem-core/src/main/java/com/tangem/common/extensions/String.kt +++ b/tangem-core/src/main/java/com/tangem/common/extensions/String.kt @@ -12,6 +12,12 @@ fun String.calculateSha256(): ByteArray { return sha256.digest(data) } +fun String.calculateSha512(): ByteArray { + val sha = MessageDigest.getInstance("SHA-512") + val data = this.toByteArray(Charset.forName("UTF-8")) + return sha.digest(data) +} + fun String.hexToBytes(): ByteArray { return ByteArray(this.length / 2) { i -> diff --git a/tangem-demo/build.gradle b/tangem-demo/build.gradle index 614e6b9552..fa9e64d58f 100644 --- a/tangem-demo/build.gradle +++ b/tangem-demo/build.gradle @@ -3,8 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 29 - buildToolsVersion "29.0.2" - + buildToolsVersion "29.0.3" defaultConfig { applicationId "com.tangem.tangemtest" @@ -34,13 +33,23 @@ android { dependencies { implementation project(':tangem-core') implementation project(':tangem-sdk') - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.2.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" + + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.2.0' + implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4" + implementation "androidx.navigation:navigation-fragment-ktx:2.2.1" + implementation "androidx.navigation:navigation-ui-ktx:2.2.1" + implementation "androidx.recyclerview:recyclerview:1.2.0-alpha01" + implementation "com.google.android.material:material:1.2.0-alpha05" + implementation "androidx.viewpager2:viewpager2:1.0.0" + + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.github.gbIxaHue:eu4d:0.3.7' } diff --git a/tangem-demo/src/main/AndroidManifest.xml b/tangem-demo/src/main/AndroidManifest.xml index f90fa6c597..cf184dcfe2 100644 --- a/tangem-demo/src/main/AndroidManifest.xml +++ b/tangem-demo/src/main/AndroidManifest.xml @@ -8,8 +8,8 @@ - - + - + diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt new file mode 100644 index 0000000000..c9fc162b6a --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt @@ -0,0 +1,29 @@ +package com.tangem.tangemtest + +import android.app.Application +import com.tangem.tangemtest._arch.structure.ILog +import com.tangem.tangemtest._arch.structure.ItemLogger +import com.tangem.tangemtest.commons.TangemLogger +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +class AppTangemDemo : Application() { + + override fun onCreate() { + super.onCreate() + + AppTangemDemo.appInstance = this + setupLoggers() + } + + private fun setupLoggers() { + Log.setLogger(TangemLogger()) + ILog.setLogger(ItemLogger()) + } + + companion object { + lateinit var appInstance: AppTangemDemo + } +} diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt similarity index 97% rename from tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt rename to tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt index 01207d703d..b39081b6b0 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt @@ -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) diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt index 5b187df946..9ffcba3103 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt @@ -19,162 +19,162 @@ import java.nio.charset.StandardCharsets /** [REDACTED_AUTHOR] */ -class TestUserDataActivity: AppCompatActivity() { +class TestUserDataActivity : AppCompatActivity() { - private lateinit var cardManager: CardManager - private lateinit var writeOptions: WriteOptions + private lateinit var cardManager: CardManager + private lateinit var writeOptions: WriteOptions - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_test_user_data) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_test_user_data) - init() - initWriteOptions() - } - - private fun init() { - cardManager = CardManager.init(this) - - btn_scan?.setOnClickListener { _ -> - cardManager.scanCard { taskEvent -> - when (taskEvent) { - is TaskEvent.Event -> { - when (taskEvent.data) { - is ScanEvent.OnReadEvent -> { - // Handle returned card data - writeOptions.cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId - runOnUiThread { showReadWriteSection(true) } - } - is ScanEvent.OnVerifyEvent -> { - //Handle card verification - } - } - } - is TaskEvent.Completion -> { - if (taskEvent.error != null) { - if (taskEvent.error is TaskError.UserCancelled) { - // Handle case when user cancelled manually - } - // Handle other errors - } - // Handle completion - } - } - } + init() + initWriteOptions() } - btn_write.setOnClickListener { - if (writeOptions.cardId == null) return@setOnClickListener + private fun init() { + cardManager = CardManager.init(this) - cardManager.writeUserData( - writeOptions.cardId !!, - writeOptions.userData, - writeOptions.userProtectedData, - writeOptions.userCounter, - writeOptions.userProtectedCounter - ) { - when (it) { - is TaskEvent.Completion -> handleError(tv_write_result, it.error) - is TaskEvent.Event -> { - runOnUiThread { - val data = it.data as? WriteUserDataResponse - if (data == null) { - tv_write_result.text = "Response doesn't match" - return@runOnUiThread - } - tv_write_result?.text = "Success" + btn_scan?.setOnClickListener { _ -> + cardManager.scanCard { taskEvent -> + when (taskEvent) { + is TaskEvent.Event -> { + when (taskEvent.data) { + is ScanEvent.OnReadEvent -> { + // Handle returned card data + writeOptions.cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId + runOnUiThread { showReadWriteSection(true) } + } + is ScanEvent.OnVerifyEvent -> { + //Handle card verification + } + } + } + is TaskEvent.Completion -> { + if (taskEvent.error != null) { + if (taskEvent.error is TaskError.UserCancelled) { + // Handle case when user cancelled manually + } + // Handle other errors + } + // Handle completion + } + } + } + } + + btn_write.setOnClickListener { + if (writeOptions.cardId == null) return@setOnClickListener + + cardManager.writeUserData( + writeOptions.cardId!!, + writeOptions.userData, + writeOptions.userProtectedData, + writeOptions.userCounter, + writeOptions.userProtectedCounter + ) { + when (it) { + is TaskEvent.Completion -> handleError(tv_write_result, it.error) + is TaskEvent.Event -> { + runOnUiThread { + val data = it.data as? WriteUserDataResponse + if (data == null) { + tv_write_result.text = "Response doesn't match" + return@runOnUiThread + } + tv_write_result?.text = "Success" + } + } + } + } + } + + btn_read.setOnClickListener { + if (writeOptions.cardId == null) return@setOnClickListener + + cardManager.readUserData(writeOptions.cardId!!) { + when (it) { + is TaskEvent.Completion -> handleError(tv_read_result, it.error) + is TaskEvent.Event -> { + runOnUiThread { + val data = it.data as? ReadUserDataResponse + if (data == null) { + tv_read_result.text = "Response doesn't match" + return@runOnUiThread + } + tv_read_result?.text = "Success" + + writeOptions.userData = data.userData + writeOptions.userProtectedData = data.userProtectedData + writeOptions.userCounter = data.userCounter + writeOptions.userProtectedCounter = data.userProtectedCounter + + tv_card_cid.text = data.cardId + tv_data.text = String(data.userData, StandardCharsets.US_ASCII) + tv_protected_data.text = String(data.userProtectedData, StandardCharsets.US_ASCII) + tv_counter.text = data.userCounter.toString() + tv_protected_counter.text = data.userProtectedCounter.toString() + } + + } + } } - } } - } } - btn_read.setOnClickListener { - if (writeOptions.cardId == null) return@setOnClickListener + private fun handleError(tv: TextView, error: TaskError?) { + val er = error ?: return + if (er is TaskError.UserCancelled) return - cardManager.readUserData(writeOptions.cardId !!) { - when (it) { - is TaskEvent.Completion -> handleError(tv_read_result, it.error) - is TaskEvent.Event -> { - runOnUiThread { - val data = it.data as? ReadUserDataResponse - if (data == null) { - tv_read_result.text = "Response doesn't match" - return@runOnUiThread - } - tv_read_result?.text = "Success" - - writeOptions.userData = data.userData - writeOptions.userProtectedData = data.userProtectedData - writeOptions.userCounter = data.userCounter - writeOptions.userProtectedCounter = data.userProtectedCounter - - tv_card_cid.text = data.cardId - tv_data.text = String(data.userData, StandardCharsets.US_ASCII) - tv_protected_data.text = String(data.userProtectedData, StandardCharsets.US_ASCII) - tv_counter.text = data.userCounter.toString() - tv_protected_counter.text = data.userProtectedCounter.toString() - } - - } - } - } + runOnUiThread { tv.text = er::class.simpleName } } - } - private fun handleError(tv: TextView, error: TaskError?) { - val er = error ?: return - if (er is TaskError.UserCancelled) return + private fun initWriteOptions() { + writeOptions = WriteOptions() - runOnUiThread { tv.text = er::class.simpleName } - } + chb_with_ud.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateData(buttonView) } + chb_with_ud_protected.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedData(buttonView) } + chb_with_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateCounter(buttonView) } + chb_with_protected_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedCounter(buttonView) } + chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) } + } - private fun initWriteOptions() { - writeOptions = WriteOptions() - - chb_with_ud.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateData(buttonView) } - chb_with_ud_protected.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedData(buttonView) } - chb_with_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateCounter(buttonView) } - chb_with_protected_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedCounter(buttonView) } - chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) } - } - - private fun showReadWriteSection(show: Boolean) { - val state = if (show) View.VISIBLE else View.GONE - cl_read_write.visibility = state - } + private fun showReadWriteSection(show: Boolean) { + val state = if (show) View.VISIBLE else View.GONE + cl_read_write.visibility = state + } } class WriteOptions { - var cardId: String? = null - var userData: ByteArray? = null - var userProtectedData: ByteArray? = null - var userCounter: Int? = null - var userProtectedCounter: Int? = null - var pin2: String? = null + var cardId: String? = null + var userData: ByteArray? = null + var userProtectedData: ByteArray? = null + var userCounter: Int? = null + var userProtectedCounter: Int? = null + var pin2: String? = null - fun updateData(chbx: CompoundButton) { - val value = "simple user data".toByteArray() - userData = if (chbx.isChecked) value else null - } + fun updateData(chbx: CompoundButton) { + val value = "simple user data".toByteArray() + userData = if (chbx.isChecked) value else null + } - fun updateProtectedData(chbx: CompoundButton) { - val value = "protected user data".toByteArray() - userProtectedData = if (chbx.isChecked) value else null - } + fun updateProtectedData(chbx: CompoundButton) { + val value = "protected user data".toByteArray() + userProtectedData = if (chbx.isChecked) value else null + } - fun updateCounter(chbx: CompoundButton) { - val value = if (userCounter == null) 0 else userCounter !! + 1 - userCounter = if (chbx.isChecked) value else null - } + fun updateCounter(chbx: CompoundButton) { + val value = if (userCounter == null) 0 else userCounter!! + 1 + userCounter = if (chbx.isChecked) value else null + } - fun updateProtectedCounter(chbx: CompoundButton) { - val value = if (userProtectedCounter == null) 0 else userProtectedCounter !! + 1 - userProtectedCounter = if (chbx.isChecked) value else null - } + fun updateProtectedCounter(chbx: CompoundButton) { + val value = if (userProtectedCounter == null) 0 else userProtectedCounter!! + 1 + userProtectedCounter = if (chbx.isChecked) value else null + } - fun updatePin2(chbx: CompoundButton) { - val value = CardEnvironment.DEFAULT_PIN2 - pin2 = if (chbx.isChecked) value else null - } + fun updatePin2(chbx: CompoundButton) { + val value = CardEnvironment.DEFAULT_PIN2 + pin2 = if (chbx.isChecked) value else null + } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/SingleLiveEvent.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/SingleLiveEvent.kt new file mode 100644 index 0000000000..943023a1c1 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/SingleLiveEvent.kt @@ -0,0 +1,44 @@ +package com.tangem.tangemtest._arch + +import android.util.Log +import androidx.annotation.MainThread +import androidx.annotation.Nullable +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import java.util.concurrent.atomic.AtomicBoolean + +/** +[REDACTED_AUTHOR] + */ +class SingleLiveEvent : MutableLiveData() { + private val mPending: AtomicBoolean = AtomicBoolean(false) + + override fun observe(owner: LifecycleOwner, observer: Observer) { + if (hasActiveObservers()) { + Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") + } + + // Observe the internal MutableLiveData + super.observe(owner, Observer { + if (mPending.compareAndSet(true, false)) { + observer.onChanged(it) + } + }) + } + + @MainThread + override fun setValue(@Nullable t: T?) { + mPending.set(true) + super.setValue(t) + } + + @MainThread + fun call() { + setValue(null) + } + + companion object { + private const val TAG = "SingleLiveEvent" + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt new file mode 100644 index 0000000000..98704005f5 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt @@ -0,0 +1,28 @@ +package com.tangem.tangemtest._arch.structure + +/** +[REDACTED_AUTHOR] + */ +interface DataHolder { + var viewModel: D +} + +typealias Payload = MutableMap + +interface PayloadHolder { + val payload: Payload + + fun get(key: String): Any? = payload[key] + fun remove(key: String): Any? = payload.remove(key) + fun set(key: String, value: Any?) { + payload[key] = value + } +} + +interface ItemListHolder { + fun setItems(list: MutableList) + fun getItems(): MutableList + fun addItem(item: I) + fun removeItem(item: I) + fun clear() +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt new file mode 100644 index 0000000000..3b40edcc77 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt @@ -0,0 +1,13 @@ +package com.tangem.tangemtest._arch.structure + +/** +[REDACTED_AUTHOR] + */ +interface Id + +enum class Additional : Id { + UNDEFINED, + JSON_INCOMING, + JSON_OUTGOING, + JSON_TAILS, +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Logger.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Logger.kt new file mode 100644 index 0000000000..e71fc33cb6 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Logger.kt @@ -0,0 +1,36 @@ +package com.tangem.tangemtest._arch.structure + +import ru.dev.gbixahue.eu4d.lib.android.global.log.Logger +import ru.dev.gbixahue.eu4d.lib.android.global.log.TagLogger +import ru.dev.gbixahue.eu4d.lib.android.global.log.profiling.SimpleLogProfiler +import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf + +/** +[REDACTED_AUTHOR] + */ +object ILog { + + private var logger: Logger? = null + + fun setLogger(logger: Logger) { + ILog.logger = logger + } + + fun d(from: Any, msg: Any?, value: Any? = null) { + logger?.d(from, stringOf(msg), value) + } + + fun w(from: Any, msg: Any?, value: Any? = null) { + logger?.w(from, stringOf(msg), value) + } + + fun e(from: Any, msg: Any?, value: Any? = null) { + logger?.e(from, stringOf(msg), value) + } +} + +class ItemLogger : TagLogger("ITEM") { + init { + msProfiler = SimpleLogProfiler() + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt new file mode 100644 index 0000000000..11086e6f93 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt @@ -0,0 +1,50 @@ +package com.tangem.tangemtest._arch.structure.abstraction + +import com.tangem.tangemtest._arch.structure.ILog +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.ItemListHolder + +/** +[REDACTED_AUTHOR] + */ +interface Block : Item { + val itemList: MutableList +} + +abstract class BaseBlock : Block { + override var parent: Item? = null + override val itemList: MutableList = mutableListOf() + override val payload: MutableMap = mutableMapOf() +} + +open class ListItemBlock( + override val id: Id +) : BaseBlock(), ItemListHolder { + + override fun setItems(list: MutableList) { + ILog.d(this, "setItems into: $id, count: ${list.size}") + itemList.clear() + itemList.addAll(list) + itemList.forEach { it.parent = this } + } + + override fun getItems(): MutableList { + return itemList + } + + override fun addItem(item: Item) { + ILog.d(this, "addItem into: $id, who: ${item.id}") + item.parent = this + itemList.add(item) + } + + override fun removeItem(item: Item) { + ILog.d(this, "removeItem from: $id, which: ${item.id}") + itemList.remove(item) + } + + override fun clear() { + ILog.d(this, "clear $id") + itemList.clear() + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Extension.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Extension.kt new file mode 100644 index 0000000000..71924302a3 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Extension.kt @@ -0,0 +1,26 @@ +package com.tangem.tangemtest._arch.structure.abstraction + +import com.tangem.tangemtest._arch.structure.Id + +/** +[REDACTED_AUTHOR] + */ +fun List.findDataItem(id: Id): BaseItem? { + var foundItem: BaseItem? = null + iterate { + if (it.id == id) { + foundItem = it as? BaseItem + return@iterate + } + } + return foundItem +} + +fun List.iterate(func: (Item) -> Unit) { + forEach { + when (it) { + is BaseItem<*> -> func(it) + is Block -> it.itemList.iterate(func) + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt new file mode 100644 index 0000000000..2cfbc69173 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt @@ -0,0 +1,31 @@ +package com.tangem.tangemtest._arch.structure.abstraction + +import com.tangem.tangemtest._arch.structure.DataHolder +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.PayloadHolder + +/** +[REDACTED_AUTHOR] + */ +interface Item : PayloadHolder { + val id: Id + var parent: Item? +} + +abstract class BaseItem( + override var viewModel: ItemViewModel +) : Item, DataHolder> { + override var parent: Item? = null + override val payload: Payload = mutableMapOf() + + fun getData(): D? = viewModel.data + + fun setData(value: D?) { + viewModel.data = value + } + + fun restoreDefaultData(){ + setData(viewModel.defaultData) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ModelConverter.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ModelConverter.kt new file mode 100644 index 0000000000..09823a8495 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ModelConverter.kt @@ -0,0 +1,15 @@ +package com.tangem.tangemtest._arch.structure.abstraction + +import ru.dev.gbixahue.eu4d.lib.kotlin.common.Converter + +/** +[REDACTED_AUTHOR] + */ +interface DefaultConverter { + fun convert(from: A, default: B): B +} + +interface ItemsToModel : DefaultConverter, M> +interface ModelToItems : Converter> + +interface ModelConverter : DefaultConverter, M>, Converter> \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Plugin.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Plugin.kt new file mode 100644 index 0000000000..71bdc779c7 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Plugin.kt @@ -0,0 +1,10 @@ +package com.tangem.tangemtest._arch.structure.abstraction + +/** +[REDACTED_AUTHOR] + */ +typealias PluginCallBackResult = (Any?) -> Item + +interface Plugin : Item { + fun invoke(data: Any?, asyncCallback: PluginCallBackResult? = null): Any? +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt new file mode 100644 index 0000000000..86d1d1b345 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt @@ -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?) -> Unit +typealias SafeValueChange = (V) -> Unit + +interface ItemViewModel : PayloadHolder { + val viewState: ViewState + var data: D? + var defaultData: D? + var onDataUpdated: ValueChange? + + fun updateDataByView(data: D?) +} + +open class BaseItemViewModel : ItemViewModel { + + override val viewState: ViewState = ViewState() + override val payload: Payload = mutableMapOf() + + // Don't update it directly from a View. Use for it updateDataByView() + override var data: D? = null + set(value) { + if (handleDataUpdates(value)) field = value + } + + // Data for restoring initial value + override var defaultData: D? = null + set(value) { + field = value + data = value + } + + // Use it for handling data updates in View + override var onDataUpdated: ValueChange? = null + + // When data updates directly it invokes onDataUpdated + // return true = data will update + // return false = data won't update + protected open fun handleDataUpdates(value: D?): Boolean { + ILog.d(this, "handleDateUpdates: $value") + onDataUpdated?.invoke(value) + return true + } + + // Use it to update the data from a View. It disables onDataUpdated to prevent a callback loop + override fun updateDataByView(data: D?) { + ILog.d(this, "data changed: $data") + val callback = onDataUpdated + onDataUpdated = null + this.data = data + onDataUpdated = callback + } +} + +class ViewState { + var isHiddenField = false + + var descriptionVisibility: Int = 0x00000008 + set(value) { + field = value + onDescriptionVisibilityChanged?.invoke(value) + } + + var onDescriptionVisibilityChanged: SafeValueChange? = null +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt new file mode 100644 index 0000000000..11ea5510c0 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt @@ -0,0 +1,19 @@ +package com.tangem.tangemtest._arch.structure.impl + +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem + +/** +[REDACTED_AUTHOR] + */ +class AnyItem(override val id: Id, value: Any? = null) : BaseItem(AnyViewModel(value)) +class TextItem(override val id: Id, value: String? = null) : BaseItem(StringViewModel(value)) +class EditTextItem(override val id: Id, value: String? = null) : BaseItem(StringViewModel(value)) +class NumberItem(override val id: Id, value: Number? = null) : BaseItem(NumberViewModel(value)) +class BoolItem(override val id: Id, value: Boolean? = null) : BaseItem(BoolViewModel(value)) + +class ListItem(override val id: Id, value: List, selectedValue: Any?) + : BaseItem(ListViewModel(ListValueWrapper(selectedValue, value)) +) + +inline fun BaseItem.getData(): T? = viewModel.data \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt new file mode 100644 index 0000000000..f082577e70 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt @@ -0,0 +1,29 @@ +package com.tangem.tangemtest._arch.structure.impl + +import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel + +/** +[REDACTED_AUTHOR] + */ +open class TransitiveViewModel(value: D?) : BaseItemViewModel() { + init { + defaultData = value + } +} + +class AnyViewModel(value: Any? = null) : TransitiveViewModel(value) +class StringViewModel(value: String? = null) : TransitiveViewModel(value) +class NumberViewModel(value: Number? = null) : TransitiveViewModel(value) +class BoolViewModel(value: Boolean? = null) : TransitiveViewModel(value) +class ListViewModel(value: ListValueWrapper? = null) : TransitiveViewModel(value) { + + override fun handleDataUpdates(value: ListValueWrapper?): Boolean { + val newValue = value ?: return false + if (newValue.selectedItem == data?.selectedItem) return false + + return super.handleDataUpdates(value) + } +} + +class KeyValue(val key: String, val value: Any) +class ListValueWrapper(var selectedItem: Any?, val itemList: List) \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt new file mode 100644 index 0000000000..4e774f0be7 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt @@ -0,0 +1,62 @@ +package com.tangem.tangemtest._main + +import android.content.res.Resources +import android.os.Bundle +import android.util.Log +import android.view.Menu +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SwitchCompat +import androidx.appcompat.widget.Toolbar +import androidx.navigation.findNavController +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.navigateUp +import androidx.navigation.ui.setupActionBarWithNavController +import com.tangem.tangemtest.R + +/** +[REDACTED_AUTHOR] + */ +class MainActivity : AppCompatActivity() { + + private val vm: MainViewModel by viewModels() + private lateinit var appBarConfiguration: AppBarConfiguration + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.a_main) + + val toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + + val host: NavHostFragment = supportFragmentManager + .findFragmentById(R.id.nav_host_fragment) as NavHostFragment? ?: return + + val navController = host.navController + appBarConfiguration = AppBarConfiguration(setOf(R.id.nav_entry_point)) + setupActionBarWithNavController(navController, appBarConfiguration) + + navController.addOnDestinationChangedListener { _, destination, _ -> + val dest: String = try { + resources.getResourceName(destination.id) + } catch (e: Resources.NotFoundException) { + destination.id.toString() + } + Log.d("NavigationActivity", "Navigated to $dest") + } + } + + override fun onSupportNavigateUp(): Boolean { + return findNavController(R.id.nav_host_fragment).navigateUp(appBarConfiguration) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.main_menu, menu) + val switchMenu = menu.findItem(R.id.action_favorite) + (switchMenu.actionView as? SwitchCompat)?.let { + it.setOnCheckedChangeListener { buttonView, isChecked -> vm.switchToggled(isChecked) } + } + return super.onCreateOptionsMenu(menu) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt new file mode 100644 index 0000000000..17ce97d8f9 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt @@ -0,0 +1,15 @@ +package com.tangem.tangemtest._main + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +/** +[REDACTED_AUTHOR] + */ +class MainViewModel : ViewModel() { + val ldDescriptionSwitch = MutableLiveData(false) + + fun switchToggled(state: Boolean) { + ldDescriptionSwitch.postValue(state) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt new file mode 100644 index 0000000000..313c4aaffe --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt @@ -0,0 +1,87 @@ +package com.tangem.tangemtest._main.entryPoint + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.navigation.NavController +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.transition.AutoTransition +import androidx.transition.TransitionManager +import com.tangem.tangemtest.R +import com.tangem.tangemtest._main.MainViewModel +import com.tangem.tangemtest.ucase.getDefaultNavigationOptions +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.resources.MainResourceHolder +import kotlinx.android.synthetic.main.fg_entry_point.* + +/** +[REDACTED_AUTHOR] + */ +class ActionListFragment : Fragment() { + + private val navController: NavController by lazy { findNavController() } + private lateinit var rvActions: RecyclerView + + private val mainActivityVM: MainViewModel by activityViewModels() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fg_entry_point, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initRecyclerView() + } + + private fun initRecyclerView() { + val layoutManager = LinearLayoutManager(activity) + rvActions = rv_actions + rvActions.layoutManager = layoutManager + rvActions.addItemDecoration(DividerItemDecoration(activity, layoutManager.orientation)) + + val vhDataWrapper = VhExDataWrapper(MainResourceHolder, false) + + rvActions.adapter = RvActionsAdapter(vhDataWrapper) { type, position, data -> navigate(data) } + mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer { + vhDataWrapper.descriptionIsVisible = it + TransitionManager.beginDelayedTransition(rvActions as ViewGroup, AutoTransition()) + rvActions.adapter?.notifyDataSetChanged() + }) + } + + override fun onResume() { + super.onResume() + val adapter: RvActionsAdapter = rvActions.adapter as? RvActionsAdapter ?: return + + adapter.setItemList(getNavigateOptions()) + adapter.notifyDataSetChanged() + } + + private fun navigate(destinationId: Int) { + navController.navigate(destinationId, null, getDefaultNavigationOptions()) + } + + private fun getNavigateOptions(): MutableList { + return mutableListOf( + ActionType.Scan, + ActionType.Sign, + ActionType.Personalize, + ActionType.Depersonalize +// ActionType.CreateWallet, +// ActionType.PurgeWallet, +// ActionType.ReadIssuerData, +// ActionType.WriteIssuerData, +// ActionType.ReadIssuerExData, +// ActionType.WriteIssuerExData, +// ActionType.ReadUserData, +// ActionType.WriteUserData + ) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/RvActions.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/RvActions.kt new file mode 100644 index 0000000000..df045a572d --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/RvActions.kt @@ -0,0 +1,69 @@ +package com.tangem.tangemtest._main.entryPoint + +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.recyclerview.widget.RecyclerView +import com.tangem.tangemtest.R +import com.tangem.tangemtest.ucase.resources.ActionRes +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.resources.MainResourceHolder +import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate +import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseAdapter +import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseVH +import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvCallback + +/** +[REDACTED_AUTHOR] + */ +class RvActionsAdapter( + private val wrapper: VhExDataWrapper, + private val callback: RvCallback +) : RvBaseAdapter() { + + override fun onBindViewHolder(holder: RvActionsVH, position: Int) { + holder.bindData(itemList[position]) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RvActionsVH { + val view = parent.inflate(R.layout.vh_actions, false) + return RvActionsVH(view, wrapper, callback) + } + +} + +class VhExDataWrapper( + val resHolder: MainResourceHolder, + var descriptionIsVisible: Boolean +) + +class RvActionsVH( + itemView: View, + private val wrapper: VhExDataWrapper, + private val callback: RvCallback? +) : RvBaseVH(itemView) { + + private val tvAction = itemView.findViewById(R.id.tv_title) + private val containerDescription = itemView.findViewById(R.id.container_description) + private val tvDescription = itemView.findViewById(R.id.tv_description) + + override fun bindData(data: ActionType) { + val res: ActionRes = wrapper.resHolder.safeGet(data) + tvAction.text = getString(res.resName) + tvDescription.text = getString(res.resDescription) + + itemView.setOnClickListener { + res.resNavigation?.let { callback?.invoke(itemViewType, adapterPosition, it) } + } + + containerDescription.visibility = if (wrapper.descriptionIsVisible) View.VISIBLE else View.GONE + } + + override fun onDataBound(data: ActionType) {} +} + +fun RecyclerView.ViewHolder.getString(@StringRes id: Int?, ifNull: String = ""): String { + val reqId = id ?: return ifNull + return itemView.context.getString(reqId) +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt new file mode 100644 index 0000000000..7a19cf981f --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt @@ -0,0 +1,8 @@ +package com.tangem.tangemtest.commons + +/** +[REDACTED_AUTHOR] + */ +fun performAction(a: A?, b: B?, action: (A, B) -> Unit) { + if (a != null && b != null) action(a, b) +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Logger.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Logger.kt new file mode 100644 index 0000000000..795c23c726 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Logger.kt @@ -0,0 +1,8 @@ +package com.tangem.tangemtest.commons + +import ru.dev.gbixahue.eu4d.lib.android.global.log.TagLogger + +/** +[REDACTED_AUTHOR] + */ +class TangemLogger : TagLogger("TangemDemo") \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt index 0ef2666e1d..c8f68bec9c 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/CardConfig.kt @@ -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) diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/List.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/List.kt new file mode 100644 index 0000000000..d637d6f89a --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/extensions/List.kt @@ -0,0 +1,16 @@ +package com.tangem.tangemtest.extensions + +/** +[REDACTED_AUTHOR] + */ +fun List.print(delimiter: String = ", ", wrap: Boolean = true): String { + val builder = StringBuilder() + forEach { builder.append(it).append(delimiter) } + val length = builder.length + if (length > delimiter.length) { + builder.delete(length - delimiter.length, length) + } + val result = builder.toString() + + return if (wrap) "[$result]" else result +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/NavigationOp.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/NavigationOp.kt new file mode 100644 index 0000000000..fa588f2b68 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/NavigationOp.kt @@ -0,0 +1,19 @@ +package com.tangem.tangemtest.ucase + +import androidx.navigation.NavOptions +import androidx.navigation.navOptions +import com.tangem.tangemtest.R + +/** +[REDACTED_AUTHOR] + */ +fun getDefaultNavigationOptions(): NavOptions { + return navOptions { + anim { + enter = R.anim.slide_in_right + exit = R.anim.slide_out_left + popEnter = R.anim.slide_in_left + popExit = R.anim.slide_out_right + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt new file mode 100644 index 0000000000..285c8de4b6 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt @@ -0,0 +1,46 @@ +package com.tangem.tangemtest.ucase.domain.actions + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.AfterActionModification +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence +import com.tangem.tasks.TaskEvent + +/** +[REDACTED_AUTHOR] + * + * The Card Action class family is designed for calling Card Manager functions + * and then processing the response. It also allows you to extract the main action as a lambda expression + */ +data class AttrForAction( + val cardManager: CardManager, + val itemList: List, + val payload: Payload, + val consequence: ItemsChangeConsequence? +) + +interface Action { + fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) + fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? = null +} + +abstract class BaseAction : Action { + protected open fun handleResult( + payload: PayloadHolder, + taskEvent: TaskEvent<*>, + modifier: AfterActionModification?, + attrs: AttrForAction, + callback: ActionCallback + ) { + val modifiedItems = mutableListOf() + modifier?.modify(payload, taskEvent, attrs.itemList)?.forEach { item -> + modifiedItems.add(item) + attrs.consequence?.affectChanges(payload, item, attrs.itemList)?.let { modifiedItems.addAll(it) } + } + callback(taskEvent, modifiedItems) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt new file mode 100644 index 0000000000..2433302a4b --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt @@ -0,0 +1,26 @@ +package com.tangem.tangemtest.ucase.domain.actions + +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest._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 + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt new file mode 100644 index 0000000000..8ef5e4c0cc --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt @@ -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 ?: 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): 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): 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) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt new file mode 100644 index 0000000000..5264be9aed --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt @@ -0,0 +1,14 @@ +package com.tangem.tangemtest.ucase.domain.actions + +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.AfterScanModifier + +/** +[REDACTED_AUTHOR] + */ +class ScanAction : BaseAction() { + override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { + attrs.cardManager.scanCard { handleResult(payload, it, AfterScanModifier(), attrs, callback) } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt new file mode 100644 index 0000000000..18fef1d8c5 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt @@ -0,0 +1,28 @@ +package com.tangem.tangemtest.ucase.domain.actions + +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest._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 + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/Factory.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/Factory.kt new file mode 100644 index 0000000000..000e9a53c5 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/Factory.kt @@ -0,0 +1,25 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager + +import com.tangem.tangemtest.ucase.domain.paramsManager.managers.DepersonalizeItemsManager +import com.tangem.tangemtest.ucase.domain.paramsManager.managers.PersonalizeItemsManager +import com.tangem.tangemtest.ucase.domain.paramsManager.managers.ScanItemsManager +import com.tangem.tangemtest.ucase.domain.paramsManager.managers.SignItemsManager +import com.tangem.tangemtest.ucase.resources.ActionType +import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder + +/** +[REDACTED_AUTHOR] + */ +class ParamsManagerFactory : BaseTypedHolder() { + + companion object { + fun createFactory(): ParamsManagerFactory { + return ParamsManagerFactory().apply { + register(ActionType.Scan, ScanItemsManager()) + register(ActionType.Sign, SignItemsManager()) + register(ActionType.Personalize, PersonalizeItemsManager()) + register(ActionType.Depersonalize, DepersonalizeItemsManager()) + } + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt new file mode 100644 index 0000000000..f97013d6f8 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt @@ -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 +typealias AffectedItemsCallback = (AffectedList) -> Unit +typealias ActionCallback = (ActionResponse, AffectedList) -> Unit + +/** +[REDACTED_AUTHOR] + */ +interface ItemsManager : PayloadHolder { + + fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback? = null) + fun getItems(): List + fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) + fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? + fun attachPayload(payload: Payload) + + val consequence: ItemsChangeConsequence? +} + +interface PayloadKey { + companion object { + val cardConfig: String = "cardConfig" + val incomingJson: String = "incomingJson" + val card = "card" + val actionView = "actionView" + val itemList = "itemList" + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt new file mode 100644 index 0000000000..c9016165ac --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt @@ -0,0 +1,51 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._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 by lazy { createItemsList() } + + override val consequence: ItemsChangeConsequence? = null + + override val payload: MutableMap = mutableMapOf() + + override fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback?) { + if (itemList.isEmpty()) return + val foundItem = itemList.findDataItem(id) ?: return + + foundItem.setData(value) + applyChangesByAffectedItems(foundItem, callback) + } + + override fun getItems(): MutableList = itemList.toMutableList() + + override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? = null + + override fun attachPayload(payload: Payload) { + payload.forEach { this.payload[it.key] = it.value } + } + + // Use it if current item needs to be affect any other items + protected open fun applyChangesByAffectedItems(param: Item, callback: AffectedItemsCallback?) { + consequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) } + } + + protected fun getAttrsForAction(cardManager: CardManager) + : AttrForAction = AttrForAction(cardManager, itemList, payload, consequence) + + abstract fun createItemsList(): List +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/DepersonalizeItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/DepersonalizeItemsManager.kt new file mode 100644 index 0000000000..8267c86bb6 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/DepersonalizeItemsManager.kt @@ -0,0 +1,26 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.impl.EditTextItem +import com.tangem.tangemtest.ucase.domain.actions.DepersonalizeAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.variants.TlvId + +/** +[REDACTED_AUTHOR] + */ +class DepersonalizeItemsManager : BaseItemsManager(DepersonalizeAction()) { + override fun createItemsList(): List { + return listOf(EditTextItem(TlvId.CardId, null)) + } + + override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(cardManager), callback) + } + + override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? { + return action.getActionByTag(this, id, getAttrsForAction(cardManager)) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizeItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizeItemsManager.kt new file mode 100644 index 0000000000..7f40ac54f5 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizeItemsManager.kt @@ -0,0 +1,17 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.ucase.domain.actions.PersonalizeAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback + +/** +[REDACTED_AUTHOR] + */ +class PersonalizeItemsManager : BaseItemsManager(PersonalizeAction()) { + override fun createItemsList(): List = listOf() + + override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(cardManager), callback) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/ScanItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/ScanItemsManager.kt new file mode 100644 index 0000000000..1d9c973786 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/ScanItemsManager.kt @@ -0,0 +1,17 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.ucase.domain.actions.ScanAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback + +/** +[REDACTED_AUTHOR] + */ +class ScanItemsManager : BaseItemsManager(ScanAction()) { + override fun createItemsList(): List = listOf() + + override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(cardManager), callback) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SignItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SignItemsManager.kt new file mode 100644 index 0000000000..7d5d77a912 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/SignItemsManager.kt @@ -0,0 +1,34 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.managers + +import com.tangem.CardManager +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.impl.EditTextItem +import com.tangem.tangemtest.ucase.domain.actions.SignAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence +import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.SignScanConsequence +import com.tangem.tangemtest.ucase.variants.TlvId + +/** +[REDACTED_AUTHOR] + */ +class SignItemsManager : BaseItemsManager(SignAction()) { + + override val consequence: ItemsChangeConsequence? by lazy { SignScanConsequence() } + + override fun createItemsList(): List { + return listOf( + EditTextItem(TlvId.CardId, null), + EditTextItem(TlvId.TransactionOutHash, "Data used for hashing") + ) + } + + override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(cardManager), callback) + } + + override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? { + return action.getActionByTag(this, id, getAttrsForAction(cardManager)) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt new file mode 100644 index 0000000000..0b6cecbf64 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt @@ -0,0 +1,16 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction + +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tasks.TaskEvent + +/** +[REDACTED_AUTHOR] + * + * The After Action Modification class family is intended for modifying items (if necessary) + * after calling CardManager.anyAction. + * Returns a list of items that have been modified + */ +interface AfterActionModification { + fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List): List +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt new file mode 100644 index 0000000000..b75c721424 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt @@ -0,0 +1,42 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction + +import com.tangem.commands.Card +import com.tangem.commands.CardStatus +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._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): List { + val foundItem = itemList.findDataItem(TlvId.CardId) ?: return listOf() + val card = smartCast(taskEvent)?.card ?: return listOf() + val actionView = payload.get(PayloadKey.actionView) as? ActionView ?: return listOf() + + return if (isNotPersonalized(card)) { + actionView.showSnackbar(CardError.NotPersonalized) + postUI { actionView.showActionFab(false) } + listOf() + } else { + payload.set(PayloadKey.card, card) + foundItem.setData(card.cardId) + postUI { actionView.showActionFab(true) } + listOf(foundItem) + } + } + + private fun smartCast(taskEvent: TaskEvent<*>): ScanEvent.OnReadEvent? { + return (taskEvent as? TaskEvent.Event)?.data as? ScanEvent.OnReadEvent + } + + private fun isNotPersonalized(card: Card): Boolean = card.status == CardStatus.NotPersonalized +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt new file mode 100644 index 0000000000..2957d43a45 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt @@ -0,0 +1,47 @@ +package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence + +import com.tangem.commands.Card +import com.tangem.commands.EllipticCurve +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.calculateSha512 +import com.tangem.tangemtest._arch.structure.PayloadHolder +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._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): List? +} + +class SignScanConsequence: ItemsChangeConsequence { + + override fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List): List? { + if (changedItem.id != TlvId.CardId) return null + + val hashItem = itemList.findDataItem(TlvId.TransactionOutHash) ?: return null + val affectedItems = mutableListOf(hashItem) + val card = payload.remove(PayloadKey.card) as Card? + if (card == null) { + hashItem.restoreDefaultData() + postUI { (payload.get(PayloadKey.actionView) as? ActionView)?.showActionFab(false) } + } else { + val dataForHashing = hashItem.getData() as? String ?: "Any data mother...s" + val hashedData = when(card.curve) { + EllipticCurve.Secp256k1 -> dataForHashing.calculateSha256() + EllipticCurve.Ed25519 -> dataForHashing.calculateSha512() + else -> return null + } + hashItem.setData(hashedData) + } + return affectedItems.toList() + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/GsonInitializer.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/GsonInitializer.kt new file mode 100644 index 0000000000..32c93a7ff8 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/GsonInitializer.kt @@ -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 { + override fun serialize(src: ByteArray, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(src.toHexString()) + } +} + +class SettingsMaskTypeAdapter : JsonSerializer { + override fun serialize(src: SettingsMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + val arrayElement = JsonArray() + Settings.values() + .filter { src.contains(it) } + .forEach { arrayElement.add(it.name) } + return arrayElement + } +} + +class ProductMaskTypeAdapter : JsonSerializer { + override fun serialize(src: ProductMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(src.rawValue.toString()) + } +} + +class SigningMethodTypeAdapter : JsonSerializer { + override fun serialize(src: SigningMethod, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return JsonPrimitive(src.rawValue.toString()) + } +} + +class DateTypeAdapter: JsonSerializer { + 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()) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/Ids.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/Ids.kt new file mode 100644 index 0000000000..e686f0f8c3 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/Ids.kt @@ -0,0 +1,22 @@ +package com.tangem.tangemtest.ucase.resources + +import com.tangem.tangemtest._arch.structure.Id + +/** +[REDACTED_AUTHOR] + */ +enum class ActionType : Id { + Scan, + Sign, + CreateWallet, + PurgeWallet, + ReadIssuerData, + WriteIssuerData, + ReadIssuerExData, + WriteIssuerExData, + ReadUserData, + WriteUserData, + Personalize, + Depersonalize, + Unknown, +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/MainResourceHolder.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/MainResourceHolder.kt new file mode 100644 index 0000000000..90b908b742 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/MainResourceHolder.kt @@ -0,0 +1,26 @@ +package com.tangem.tangemtest.ucase.resources + +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest.ucase.resources.initializers.ActionResources +import com.tangem.tangemtest.ucase.resources.initializers.PersonalizeResources +import com.tangem.tangemtest.ucase.resources.initializers.TlvResources +import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder + +/** +[REDACTED_AUTHOR] + */ +open class ResourceHolder : BaseTypedHolder() + +object MainResourceHolder : ResourceHolder() { + init { + PersonalizeResources().init(this) + ActionResources().init(this) + TlvResources().init(this) + } + + inline fun safeGet(id: Id): Res { + val result = super.get(id) ?: Resources(R.string.unknown, R.string.unknown) + return result as Res + } +} diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/ResourseTypes.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/ResourseTypes.kt new file mode 100644 index 0000000000..72fad2464a --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/ResourseTypes.kt @@ -0,0 +1,15 @@ +package com.tangem.tangemtest.ucase.resources + +/** +[REDACTED_AUTHOR] + */ +open class Resources( + val resName: Int, + val resDescription: Int? = null +) + +class ActionRes( + resName: Int, + resDescription: Int? = null, + val resNavigation: Int? = null +) : Resources(resName, resDescription) \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/ActionResources.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/ActionResources.kt new file mode 100644 index 0000000000..053710adf7 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/ActionResources.kt @@ -0,0 +1,29 @@ +package com.tangem.tangemtest.ucase.resources.initializers + +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest.ucase.resources.ActionRes +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.resources.ResourceHolder + +/** +[REDACTED_AUTHOR] + */ +class ActionResources { + + fun init(holder: ResourceHolder) { + holder.register(ActionType.Scan, ActionRes(R.string.action_card_scan, R.string.info_action_card_scan, R.id.action_nav_entry_point_to_nav_scan)) + holder.register(ActionType.Sign, ActionRes(R.string.action_card_sign, R.string.info_action_card_sign, R.id.action_nav_entry_point_to_nav_sign)) + holder.register(ActionType.Personalize, ActionRes(R.string.action_personalize, R.string.info_action_personalize, R.id.action_nav_entry_point_to_nav_personalize)) + holder.register(ActionType.Depersonalize, ActionRes(R.string.action_depersonalize, R.string.info_action_depersonalize, R.id.action_nav_entry_point_to_nav_depersonalize)) + holder.register(ActionType.CreateWallet, ActionRes(R.string.action_wallet_create, R.string.info_action_wallet_create, R.id.action_nav_entry_point_to_nav_wallet_create)) + holder.register(ActionType.PurgeWallet, ActionRes(R.string.action_wallet_purge, R.string.info_action_wallet_purge, R.id.action_nav_entry_point_to_nav_wallet_purge)) + holder.register(ActionType.ReadIssuerData, ActionRes(R.string.action_issuer_read_data, R.string.info_action_issuer_read_data, R.id.action_nav_entry_point_to_nav_issuer_read_data)) + holder.register(ActionType.WriteIssuerData, ActionRes(R.string.action_issuer_write_data, R.string.info_action_issuer_write_data, R.id.action_nav_entry_point_to_nav_issuer_write_data)) + holder.register(ActionType.ReadIssuerExData, ActionRes(R.string.action_issuer_read_ex_data, R.string.info_action_issuer_read_ex_data, R.id.action_nav_entry_point_to_nav_issuer_read_ex_data)) + holder.register(ActionType.WriteIssuerExData, ActionRes(R.string.action_issuer_write_ex_data, R.string.info_action_issuer_write_ex_data, R.id.action_nav_entry_point_to_nav_issuer_write_ex_data)) + holder.register(ActionType.ReadUserData, ActionRes(R.string.action_user_read_data, R.string.info_action_user_read_data, R.id.action_nav_entry_point_to_nav_user_read_data)) + holder.register(ActionType.WriteUserData, ActionRes(R.string.action_user_write_data, R.string.info_action_user_write_data, R.id.action_nav_entry_point_to_nav_user_write_data)) +// holder.register(ActionType.Unknown, getIfNotContains()) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizeResources.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizeResources.kt new file mode 100644 index 0000000000..f5e5338d4d --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/PersonalizeResources.kt @@ -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) { + initBlock(holder) + initCardNumber(holder) + initCommon(holder) + initSigningMethod(holder) + initSignHashExProp(holder) + initDenomination(holder) + initToken(holder) + initProductMask(holder) + initSettingsMask(holder) + initSettingsMaskProtocolEnc(holder) + initSettingsMaskNde(holder) + initPins(holder) + } + + private fun initBlock(holder: TypedHolder) { + holder.register(BlockId.CardNumber, Resources(R.string.pers_block_card_number, R.string.info_pers_block_card_number)) + holder.register(BlockId.Common, Resources(R.string.pers_block_common, R.string.info_pers_block_common)) + holder.register(BlockId.SigningMethod, Resources(R.string.pers_block_signing_method, R.string.info_pers_block_signing_method)) + holder.register(BlockId.SignHashExProp, Resources(R.string.pers_block_sign_hash_ex_prop, R.string.info_pers_block_sign_hash_ex_prop)) + holder.register(BlockId.Denomination, Resources(R.string.pers_block_denomination, R.string.info_pers_block_denomination)) + holder.register(BlockId.Token, Resources(R.string.pers_block_token, R.string.info_pers_block_token)) + holder.register(BlockId.ProdMask, Resources(R.string.pers_block_product_mask, R.string.info_pers_block_product_mask)) + holder.register(BlockId.SettingsMask, Resources(R.string.pers_block_settings_mask, R.string.info_pers_block_settings_mask)) + holder.register(BlockId.SettingsMaskProtocolEnc, Resources(R.string.pers_block_settings_mask_protocol_enc, R.string.info_pers_block_settings_mask_protocol_enc)) + holder.register(BlockId.SettingsMaskNdef, Resources(R.string.pers_block_settings_mask_ndef, R.string.info_pers_block_settings_mask_ndef)) + holder.register(BlockId.Pins, Resources(R.string.pers_block_pins, R.string.info_pers_block_pins)) + } + + private fun initCardNumber(holder: TypedHolder) { + holder.register(CardNumber.Series, Resources(R.string.pers_item_series, R.string.info_pers_item_series)) + holder.register(CardNumber.Number, Resources(R.string.pers_item_number, R.string.info_pers_item_number)) + holder.register(CardNumber.BatchId, Resources(R.string.pers_item_batch_id, R.string.info_pers_item_batch_id)) + } + + private fun initCommon(holder: TypedHolder) { + holder.register(Common.Curve, Resources(R.string.pers_item_curve, R.string.info_pers_item_curve)) + holder.register(Common.Blockchain, Resources(R.string.pers_item_blockchain, R.string.info_pers_item_blockchain)) + holder.register(Common.BlockchainCustom, Resources(R.string.pers_item_custom_blockchain, R.string.info_pers_item_custom_blockchain)) + holder.register(Common.MaxSignatures, Resources(R.string.pers_item_max_signatures, R.string.info_pers_item_max_signatures)) + holder.register(Common.CreateWallet, Resources(R.string.pers_item_create_wallet, R.string.info_pers_item_create_wallet)) + } + + private fun initSigningMethod(holder: TypedHolder) { + holder.register(SigningMethod.SignTx, Resources(R.string.pers_item_sign_tx_hashes, R.string.info_pers_item_sign_tx_hashes)) + holder.register(SigningMethod.SignTxRaw, Resources(R.string.pers_item_sign_raw_tx, R.string.info_pers_item_sign_raw_tx)) + holder.register(SigningMethod.SignValidatedTx, Resources(R.string.pers_item_sign_validated_tx_hashes, R.string.info_pers_item_sign_validated_tx_hashes)) + holder.register(SigningMethod.SignValidatedTxRaw, Resources(R.string.pers_item_sign_validated_raw_tx, R.string.info_pers_item_sign_validated_raw_tx)) + holder.register(SigningMethod.SignValidatedTxIssuer, Resources(R.string.pers_item_sign_validated_tx_hashes_with_iss_data, R.string.info_pers_item_sign_validated_tx_hashes_with_iss_data)) + holder.register(SigningMethod.SignValidatedTxRawIssuer, Resources(R.string.pers_item_sign_validated_raw_tx_with_iss_data, R.string.info_pers_item_sign_validated_raw_tx_with_iss_data)) + holder.register(SigningMethod.SignExternal, Resources(R.string.pers_item_sign_hash_ex, R.string.info_pers_item_sign_hash_ex)) + } + + private fun initSignHashExProp(holder: TypedHolder) { + holder.register(SignHashExProp.PinLessFloorLimit, Resources(R.string.pers_item_pin_less_floor_limit, R.string.info_pers_item_pin_less_floor_limit)) + holder.register(SignHashExProp.CryptoExKey, Resources(R.string.pers_item_cr_ex_key, R.string.info_pers_item_cr_ex_key)) + holder.register(SignHashExProp.RequireTerminalCertSig, Resources(R.string.pers_item_require_terminal_cert_sig, R.string.info_pers_item_require_terminal_cert_sig)) + holder.register(SignHashExProp.RequireTerminalTxSig, Resources(R.string.pers_item_require_terminal_tx_sig, R.string.info_pers_item_require_terminal_tx_sig)) + holder.register(SignHashExProp.CheckPin3, Resources(R.string.pers_item_check_pin3_on_card, R.string.info_pers_item_check_pin3_on_card)) + } + + private fun initDenomination(holder: TypedHolder) { + holder.register(Denomination.WriteOnPersonalize, Resources(R.string.pers_item_write_on_personalize, R.string.info_pers_item_write_on_personalize)) + holder.register(Denomination.Denomination, Resources(R.string.pers_item_denomination, R.string.info_pers_item_denomination)) + } + + private fun initToken(holder: TypedHolder) { + holder.register(Token.ItsToken, Resources(R.string.pers_item_its_token, R.string.info_pers_item_its_token)) + holder.register(Token.Symbol, Resources(R.string.pers_item_symbol, R.string.info_pers_item_symbol)) + holder.register(Token.ContractAddress, Resources(R.string.pers_item_contract_address, R.string.info_pers_item_contract_address)) + holder.register(Token.Decimal, Resources(R.string.pers_item_decimal, R.string.info_pers_item_decimal)) + } + + private fun initProductMask(holder: TypedHolder) { + holder.register(ProductMask.Note, Resources(R.string.pers_item_note, R.string.info_pers_item_note)) + holder.register(ProductMask.Tag, Resources(R.string.pers_item_tag, R.string.info_pers_item_tag)) + holder.register(ProductMask.CardId, Resources(R.string.pers_item_id_card, R.string.info_pers_item_id_card)) + } + + private fun initSettingsMask(holder: TypedHolder) { + holder.register(SettingsMask.IsReusable, Resources(R.string.pers_item_is_reusable, R.string.info_pers_item_is_reusable)) + holder.register(SettingsMask.NeedActivation, Resources(R.string.pers_item_need_activation, R.string.info_pers_item_need_activation)) + holder.register(SettingsMask.ForbidPurge, Resources(R.string.pers_item_forbid_purge, R.string.info_pers_item_forbid_purge)) + holder.register(SettingsMask.AllowSelectBlockchain, Resources(R.string.pers_item_allow_select_blockchain, R.string.info_pers_item_allow_select_blockchain)) + holder.register(SettingsMask.UseBlock, Resources(R.string.pers_item_use_block, R.string.info_pers_item_use_block)) + holder.register(SettingsMask.OneApdu, Resources(R.string.pers_item_one_apdu_at_once, R.string.info_pers_item_one_apdu_at_once)) + holder.register(SettingsMask.UseCvc, Resources(R.string.pers_item_use_cvc, R.string.info_pers_item_use_cvc)) + holder.register(SettingsMask.AllowSwapPin, Resources(R.string.pers_item_allow_swap_pin, R.string.info_pers_item_allow_swap_pin)) + holder.register(SettingsMask.AllowSwapPin2, Resources(R.string.pers_item_allow_swap_pin2, R.string.info_pers_item_allow_swap_pin2)) + holder.register(SettingsMask.ForbidDefaultPin, Resources(R.string.pers_item_forbid_default_pin, R.string.info_pers_item_forbid_default_pin)) + holder.register(SettingsMask.SmartSecurityDelay, Resources(R.string.pers_item_smart_security_delay, R.string.info_pers_item_smart_security_delay)) + holder.register(SettingsMask.ProtectIssuerDataAgainstReplay, Resources(R.string.pers_item_protect_issuer_data_against_replay, R.string.info_pers_item_protect_issuer_data_against_replay)) + holder.register(SettingsMask.SkipSecurityDelayIfValidated, Resources(R.string.pers_item_skip_security_delay_if_validated, R.string.info_pers_item_skip_security_delay_if_validated)) + holder.register(SettingsMask.SkipPin2CvcIfValidated, Resources(R.string.pers_item_skip_pin2_and_cvc_if_validated, R.string.info_pers_item_skip_pin2_and_cvc_if_validated)) + holder.register(SettingsMask.SkipSecurityDelayOnLinkedTerminal, Resources(R.string.pers_item_skip_security_delay_on_linked_terminal, R.string.info_pers_item_skip_security_delay_on_linked_terminal)) + holder.register(SettingsMask.RestrictOverwriteExtraIssuerData, Resources(R.string.pers_item_restrict_overwrite_ex_issuer_data, R.string.info_pers_item_restrict_overwrite_ex_issuer_data)) + } + + private fun initSettingsMaskProtocolEnc(holder: TypedHolder) { + holder.register(SettingsMaskProtocolEnc.AllowUnencrypted, Resources(R.string.pers_item_allow_unencrypted, R.string.info_pers_item_allow_unencrypted)) + holder.register(SettingsMaskProtocolEnc.AllowStaticEncryption, Resources(R.string.pers_item_allow_fast_encryption, R.string.info_pers_item_allow_fast_encryption)) + } + + private fun initSettingsMaskNde(holder: TypedHolder) { + holder.register(SettingsMaskNdef.UseNdef, Resources(R.string.pers_item_use_ndef, R.string.info_pers_item_use_ndef)) + holder.register(SettingsMaskNdef.DynamicNdef, Resources(R.string.pers_item_dynamic_ndef, R.string.info_pers_item_dynamic_ndef)) + holder.register(SettingsMaskNdef.DisablePrecomputedNdef, Resources(R.string.pers_item_disable_precomputed_ndef, R.string.info_pers_item_disable_precomputed_ndef)) + holder.register(SettingsMaskNdef.Aar, Resources(R.string.pers_item_aar, R.string.info_pers_item_aar)) + } + + private fun initPins(holder: TypedHolder) { + holder.register(Pins.Pin, Resources(R.string.pers_item_pin, R.string.info_pers_item_pin)) + holder.register(Pins.Pin2, Resources(R.string.pers_item_pin2, R.string.info_pers_item_pin2)) + holder.register(Pins.Pin3, Resources(R.string.pers_item_pin3, R.string.info_pers_item_pin3)) + holder.register(Pins.Cvc, Resources(R.string.pers_item_cvc, R.string.info_pers_item_cvc)) + holder.register(Pins.PauseBeforePin2, Resources(R.string.pers_item_pause_before_pin2, R.string.info_pers_item_pause_before_pin2)) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/TlvResources.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/TlvResources.kt new file mode 100644 index 0000000000..4de4ab008f --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/resources/initializers/TlvResources.kt @@ -0,0 +1,20 @@ +package com.tangem.tangemtest.ucase.resources.initializers + +import com.tangem.tangemtest.R +import com.tangem.tangemtest.ucase.resources.MainResourceHolder +import com.tangem.tangemtest.ucase.resources.Resources +import com.tangem.tangemtest.ucase.variants.TlvId + +/** +[REDACTED_AUTHOR] + */ +class TlvResources { + fun init(holder: MainResourceHolder) { + initScan(holder) + } + + private fun initScan(holder: MainResourceHolder) { + holder.register(TlvId.CardId, Resources(R.string.tlv_card_id, R.string.info_tlv_card_id)) + holder.register(TlvId.TransactionOutHash, Resources(R.string.tlv_transaction_out_hash, R.string.info_tlv_transaction_out_hash)) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/Errors.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/Errors.kt new file mode 100644 index 0000000000..7b7cb530f7 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/Errors.kt @@ -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, +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/ViewScreen.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/ViewScreen.kt new file mode 100644 index 0000000000..ec84ed97ba --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/tunnel/ViewScreen.kt @@ -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) +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt new file mode 100644 index 0000000000..d44d2c998d --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt @@ -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(R.id.ll_incoming_params_container) + } + protected lateinit var mainView: View + protected val actionFab: FloatingActionButton by lazy { mainView.findViewById(R.id.fab_action) } + + protected val paramsVM: ParamsViewModel by viewModels { ActionViewModelFactory(itemsManager) } + protected val widgetList = mutableListOf() + + private val itemsManager: ItemsManager by lazy { ParamsManagerFactory.createFactory().get(getAction())!! } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + Log.d(this, "onCreateView") + mainView = inflater.inflate(getLayoutId(), container, false) + return mainView + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + Log.d(this, "onViewCreated") + + viewLifecycleOwner.lifecycle.addObserver(paramsVM) + paramsVM.setCardManager(CardManager.init(requireActivity())) + paramsVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView)) + initFab() + showActionFab(false) + createWidgets { subscribeToViewModelChanges() } + } + + private fun initFab() { + Log.d(this, "initFab") + actionFab.setOnClickListener { paramsVM.invokeMainAction() } + } + + private fun createWidgets(widgetCreatedCallback: () -> Unit) { + Log.d(this, "createWidgets") + paramsVM.ldParams.observe(viewLifecycleOwner, Observer { itemList -> + itemList.forEach { param -> + val widget = ParameterWidget(inflateParamView(incomingParamsContainer), param) + widget.onValueChanged = { id, value -> paramsVM.userChangedItem(id, value) } + widget.onActionBtnClickListener = paramsVM.getItemAction(param.id) + widgetList.add(widget) + } + widgetCreatedCallback() + }) + } + + private fun subscribeToViewModelChanges() { + Log.d(this, "subscribeToViewModelChanges") + listenResponse() + listenError() + listenChangedItems() + listenDescriptionSwitchChanges() + } + + protected open fun listenResponse() { + val tvResponse by lazy { mainView.findViewById(R.id.tv_action_response_json) } + paramsVM.ldResponse.observe(viewLifecycleOwner, Observer { + Log.d(this, "action response: ${if (it.length > 50) it.substring(0..50) else it}") + tvResponse.text = it + }) + } + + protected open fun listenError() { + paramsVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) }) + } + + protected open fun listenChangedItems() { + paramsVM.seChangedItems.observe(viewLifecycleOwner, Observer { itemList -> + itemList.forEach { item -> + Log.d(this, "item changed from VM - name: ${item.id}") + val dataItem = item as? BaseItem ?: return@Observer + Log.d(this, "item changed from VM - name: ${dataItem.id}, value:${dataItem.viewModel.data}") + widgetList.firstOrNull { it.id == item.id }?.changeParamValue(item.viewModel.data) + } + }) + } + + protected open fun listenDescriptionSwitchChanges() { + val mainActViewModel by activityViewModels() + mainActViewModel.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer { isEnabled -> + widgetList.forEach { it.toggleDescriptionVisibility(isEnabled) } + }) + } + + private fun inflateParamView(where: ViewGroup): ViewGroup { + val inflater = LayoutInflater.from(where.context) + val view = inflater.inflate(R.layout.w_card_incoming_param, where, false) + where.addView(view) + return view as ViewGroup + } + + override fun showActionFab(show: Boolean) { + if (show) actionFab.show() else actionFab.hide() + } + + override fun showSnackbar(id: Id) { +// MainResourceHolder.safeGet<>() + when (id) { + CardError.NotPersonalized -> showSnackbar(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 +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ParamsViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ParamsViewModel.kt new file mode 100644 index 0000000000..2df743786e --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ParamsViewModel.kt @@ -0,0 +1,144 @@ +package com.tangem.tangemtest.ucase.ui + +import androidx.annotation.UiThread +import androidx.lifecycle.* +import com.google.gson.Gson +import com.tangem.CardManager +import com.tangem.commands.Card +import com.tangem.tangemtest._arch.SingleLiveEvent +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.Payload +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.commons.performAction +import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager +import com.tangem.tangemtest.ucase.domain.responses.GsonInitializer +import com.tangem.tangemtest.ucase.tunnel.ViewScreen +import com.tangem.tasks.ScanEvent +import com.tangem.tasks.TaskError +import com.tangem.tasks.TaskEvent +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +class ActionViewModelFactory(private val manager: ItemsManager) : ViewModelProvider.NewInstanceFactory() { + override fun create(modelClass: Class): T = ParamsViewModel(manager) as T +} + +class ParamsViewModel(private val itemsManager: ItemsManager) : ViewModel(), LifecycleObserver { + + val ldCard = MutableLiveData() + val ldIsVerified = MutableLiveData() + val ldResponse = MutableLiveData() + val ldReadResponse = MutableLiveData() + val ldParams = MutableLiveData(itemsManager.getItems()) + + val seError: MutableLiveData = SingleLiveEvent() + val seChangedItems: MutableLiveData> = SingleLiveEvent() + + private val notifier: Notifier = Notifier(this) + private lateinit var cardManager: CardManager + + fun setCardManager(cardManager: CardManager) { + this.cardManager = cardManager + } + + fun userChangedItem(id: Id, value: Any?) { + itemChanged(id, value) + } + + private fun itemChanged(id: Id, value: Any?) { + itemsManager.itemChanged(id, value) { notifier.notifyItemsChanged(it) } + } + + //invokes Scan, Sign etc... + fun invokeMainAction() { + performAction(itemsManager, cardManager) { paramsManager, cardManager -> + paramsManager.invokeMainAction(cardManager) { response, listOfChangedParams -> + notifier.handleActionResult(response, listOfChangedParams) + } + } + } + + fun getItemAction(id: Id): (() -> Unit)? { + val itemFunction = itemsManager.getActionByTag(id, cardManager) ?: return null + + return { + itemFunction { response, listOfChangedParams -> + notifier.handleActionResult(response, listOfChangedParams) + } + } + } + + fun attachToPayload(payload: Payload) { + itemsManager.attachPayload(payload) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun viewOnDestroy() { + val keyList = mutableListOf() + itemsManager.payload.filterValues { it is ViewScreen }.forEach { keyList.add(it.key) } + keyList.forEach { itemsManager.payload.remove(it) } + } +} + +internal class Notifier(private val vm: ParamsViewModel) { + + private var notShowedError: TaskError? = null + private val gson: Gson = GsonInitializer().gson + + fun handleActionResult(response: TaskEvent<*>, list: List) { + if (list.isNotEmpty()) notifyItemsChanged(list) + handleResponse(response) + } + + @UiThread + fun notifyItemsChanged(list: List) { + vm.seChangedItems.postValue(list) + } + + fun handleResponse(response: TaskEvent<*>) { + val taskEvent = response as? TaskEvent<*> ?: return + + when (taskEvent) { + is TaskEvent.Completion -> handleCompletionEvent(taskEvent) + is TaskEvent.Event -> handleDataEvent(taskEvent.data) + } + } + + private fun handleDataEvent(event: Any?) { + when (event) { + is ScanEvent.OnReadEvent -> { + vm.ldCard.postValue(event.card) + vm.ldReadResponse.postValue(gson.toJson(event)) + } + is ScanEvent.OnVerifyEvent -> { + vm.ldIsVerified.postValue(true) + } + else -> vm.ldResponse.postValue(gson.toJson(event)) + } + } + + private fun handleCompletionEvent(taskEvent: TaskEvent.Completion<*>) { + if (taskEvent.error == null) { + Log.d(this, "error = null") + if (notShowedError != null) { + vm.seError.postValue("${notShowedError!!::class.simpleName}") + notShowedError = null + } + } else { + Log.d(this, "error = ${taskEvent.error}") + when (taskEvent.error) { + is TaskError.UserCancelled -> { + if (notShowedError == null) { + vm.seError.postValue("User canceled the action") + } else { + vm.seError.postValue("${notShowedError!!::class.simpleName}") + notShowedError = null + } + } + else -> notShowedError = taskEvent.error + } + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/widgets/ParameterWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/widgets/ParameterWidget.kt new file mode 100644 index 0000000000..15ba1cdcba --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/widgets/ParameterWidget.kt @@ -0,0 +1,119 @@ +package com.tangem.tangemtest.ucase.ui.widgets + +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import androidx.transition.AutoTransition +import androidx.transition.TransitionManager +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.Id +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.resources.MainResourceHolder +import com.tangem.tangemtest.ucase.resources.Resources +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log +import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf + +/** +[REDACTED_AUTHOR] + */ +class ParameterWidget( + private val parent: ViewGroup, + item: Item +) { + + val id: Id = item.id + val dataItem: BaseItem = item as BaseItem + + var onValueChanged: ((Id, Any?) -> Unit)? = null + var onActionBtnClickListener: (() -> Unit)? = null + set(value) { + field = value + toggleActionBtnVisibility() + } + + private val tilValue: TextInputLayout = parent.findViewById(R.id.til_param) + private val etValue: TextInputEditText = parent.findViewById(R.id.et_param) + private val btnAction: Button = parent.findViewById(R.id.btn_action) + private val valueWatcher: TextWatcher by lazy { getWatcher() } + + private val descriptionContainer: ViewGroup by lazy { parent.findViewById(R.id.container_description) } + private val tvDescription: TextView? by lazy { descriptionContainer.findViewById(R.id.tv_description) } + + private var actionBtnVisibilityState: Int = btnAction.visibility + private var value: Any? = dataItem.viewModel.data + + init { + tilValue.hint = tilValue.context.getString(getResNameId()) + etValue.setText(stringOf(dataItem.viewModel.data)) + etValue.addTextChangedListener(valueWatcher) + btnAction.setOnClickListener { onActionBtnClickListener?.invoke() } + btnAction.setText(getResNameId(ActionType.Scan)) + toggleActionBtnVisibility() + initDescriptionWidget() + } + + fun changeParamValue(data: Any?, silent: Boolean = true) { + Log.d(this, "changeParamValue: tag: $id, value: $data") + value = data + toggleActionBtnVisibility() + if (silent) { + etValue.removeTextChangedListener(valueWatcher) + etValue.setText(stringOf(value)) + etValue.addTextChangedListener(valueWatcher) + } else { + etValue.setText(stringOf(value)) + } + } + + fun toggleDescriptionVisibility(state: Boolean) { + TransitionManager.beginDelayedTransition(parent.parent as ViewGroup, AutoTransition()) + descriptionContainer.visibility = if (state) View.VISIBLE else View.GONE + } + + private fun getWatcher(): TextWatcher { + return object : TextWatcher { + override fun afterTextChanged(editable: Editable?) { + Log.d(this, "afterTextChanged $editable") + value = if (editable.isNullOrEmpty()) null else editable.toString() + toggleActionBtnVisibility() + onValueChanged?.invoke(id, value) + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + } + } + + private fun toggleActionBtnVisibility() { + fun switchVisibilityState(newState: Int) { + actionBtnVisibilityState = newState + TransitionManager.beginDelayedTransition(parent, AutoTransition()) + btnAction.visibility = actionBtnVisibilityState + } + when { + onActionBtnClickListener == null -> { + if (actionBtnVisibilityState == View.GONE) return + switchVisibilityState(View.GONE) + } + value == null && actionBtnVisibilityState != View.VISIBLE -> switchVisibilityState(View.VISIBLE) + value != null && actionBtnVisibilityState != View.GONE -> switchVisibilityState(View.GONE) + } + } + + private fun initDescriptionWidget() { + getResDescription()?.let { tvDescription?.setText(it) } + } +} + +fun ParameterWidget.getResNameId(id: Id? = null): Int = MainResourceHolder.safeGet(id + ?: this.id).resName + +fun ParameterWidget.getResDescription(): Int? = MainResourceHolder.safeGet(id).resDescription \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/Ids.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/Ids.kt new file mode 100644 index 0000000000..13f4ddcf27 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/Ids.kt @@ -0,0 +1,11 @@ +package com.tangem.tangemtest.ucase.variants + +import com.tangem.tangemtest._arch.structure.Id + +/** +[REDACTED_AUTHOR] + */ +enum class TlvId : Id { + CardId, + TransactionOutHash, +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt new file mode 100644 index 0000000000..6567e465b1 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt @@ -0,0 +1,16 @@ +package com.tangem.tangemtest.ucase.variants.depersonalize.ui + +import com.tangem.tangemtest.R +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment + +/** +[REDACTED_AUTHOR] + */ +class DepersonalizeActionFragment : BaseCardActionFragment() { + + override fun getLayoutId(): Int = R.layout.fg_depersonalize + + override fun getAction(): ActionType = ActionType.Depersonalize + +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/Ids.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/Ids.kt new file mode 100644 index 0000000000..eb631b200f --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/Ids.kt @@ -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 +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/DefaultValues.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/DefaultValues.kt new file mode 100644 index 0000000000..5b60115730 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/DefaultValues.kt @@ -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() { + + // 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? = mutableListOf() +) { + fun get(): Any? = value + fun set(newValue: Any?) { + value = newValue + } +} + +internal class Helper { + companion object { + fun listOfCurves(): List { + return EllipticCurve.values().map { KeyValue(it.name, it.curve) } + } + + fun listOfBlockchain(): List { + 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 { + return mutableListOf( + KeyValue("--- CUSTOM ---", ""), + KeyValue("Release APP", "com.tangem.wallet"), + KeyValue("Debug APP", "com.tangem.wallet.debug"), + KeyValue("None", "") + ) + } + + fun pauseBeforePin(): List { + return mutableListOf( + KeyValue("immediately", 0L), + KeyValue("2 seconds", 2000L), + KeyValue("5 seconds", 5000L), + KeyValue("15 seconds", 15000L), + KeyValue("30 seconds", 30000L), + KeyValue("1 minute", 60000L), + KeyValue("2 minute", 120000L) + ) + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizeConfigConverter.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizeConfigConverter.kt new file mode 100644 index 0000000000..6b9b041b49 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/PersonalizeConfigConverter.kt @@ -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 { + + private val toModel: ItemsToModel = ItemsToPersonalizeConfig() + private val toItems: ModelToItems = PersonalizeConfigToItem() + + override fun convert(from: List, default: PersonalizeConfig): PersonalizeConfig { + return toModel.convert(from, default) + } + + override fun convert(from: PersonalizeConfig): List { + return toItems.convert(from) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemTypes.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemTypes.kt new file mode 100644 index 0000000000..7deee294d8 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemTypes.kt @@ -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 + ) + +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemsToPersonalizeConfig.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemsToPersonalizeConfig.kt new file mode 100644 index 0000000000..f1a8d7a1ac --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/ItemsToPersonalizeConfig.kt @@ -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 { + protected val valuesHolder = PersonalizeConfigValuesHolder() + + override fun convert(from: List, default: PersonalizeConfig): PersonalizeConfig { + valuesHolder.init(default) + mapListItems(from) + return createModel() + } + + private fun mapListItems(itemList: List) { + 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 getTyped(id: Id): Type { + return getTypedBy(valuesHolder, id)!! + } + + private inline fun 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 + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/PersonalizeConfigTo.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/PersonalizeConfigTo.kt new file mode 100644 index 0000000000..a1186a3291 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/converter/fromTo/PersonalizeConfigTo.kt @@ -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 { + private val valuesHolder = PersonalizeConfigValuesHolder() + private val itemTypes = ItemTypes() + + override fun convert(from: PersonalizeConfig): List { + valuesHolder.init(from) + val blocList = mutableListOf() + blocList.add(cardNumber()) + blocList.add(common()) + blocList.add(signingMethod()) + blocList.add(signHashExProperties()) + blocList.add(denomination()) + blocList.add(token()) + blocList.add(productMask()) + blocList.add(settingsMask()) + blocList.add(settingsMaskProtocolEnc()) + blocList.add(settingsMaskNdef()) + blocList.add(pins()) + val payloadBlock = ListItemBlock(Additional.JSON_TAILS) + addPayload(payloadBlock, from) + blocList.add(payloadBlock) + 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, 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 { + + 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() + 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 + ) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizeParams.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizeParams.kt new file mode 100644 index 0000000000..c8d9afa0d1 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizeParams.kt @@ -0,0 +1,51 @@ +package com.tangem.tangemtest.ucase.variants.personalize.dto + +import com.tangem.commands.personalization.entities.Acquirer +import com.tangem.commands.personalization.entities.Issuer +import com.tangem.commands.personalization.entities.Manufacturer +import com.tangem.common.KeyPair + +/** +[REDACTED_AUTHOR] + */ + +interface DefaultPersonalizeParams { + companion object { + + fun issuer(): Issuer { + val name = "TANGEM SDK" + return Issuer( + name = name, + id = name + "\u0000", + dataKeyPair = KeyPair( + "".toByteArray(), + "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92".toByteArray() + ), + transactionKeyPair = KeyPair( + "".toByteArray(), + "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312".toByteArray() + ) + ) + } + + fun acquirer(): Acquirer { + val name = "Smart Cash" + return Acquirer( + name = name, + id = name + "\u0000", + keyPair = KeyPair( + "".toByteArray(), + "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92".toByteArray() + ) + ) + } + + fun manufacturer(): Manufacturer { + val keys = Manufacturer(com.tangem.tangemtest.ucase.variants.personalize.dto.Manufacturer.Mode.Developer) + return Manufacturer( + name = "Tangem", + keyPair = KeyPair(keys.publicKey, keys.privateKey) + ) + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/Manufacturer.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/Manufacturer.kt new file mode 100644 index 0000000000..8a366eeb26 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/Manufacturer.kt @@ -0,0 +1,53 @@ +package com.tangem.tangemtest.ucase.variants.personalize.dto + +class Manufacturer(private val mode: Mode) { + + private val devPrivateKeyArray = byteArrayOf( + 0x1B.toByte(), 0x48.toByte(), 0xCF.toByte(), 0xD2.toByte(), 0x4B.toByte(), 0xBB.toByte(), 0x5B.toByte(), 0x39.toByte(), + 0x47.toByte(), 0x71.toByte(), 0xED.toByte(), 0x81.toByte(), 0xF2.toByte(), 0xBA.toByte(), 0xCF.toByte(), 0x57.toByte(), + 0x47.toByte(), 0x9E.toByte(), 0x47.toByte(), 0x35.toByte(), 0xEB.toByte(), 0x14.toByte(), 0x05.toByte(), 0x08.toByte(), + 0x39.toByte(), 0x27.toByte(), 0x37.toByte(), 0x2D.toByte(), 0x40.toByte(), 0xDA.toByte(), 0x9E.toByte(), 0x92.toByte() + ) + private val devPublicKeyArray = byteArrayOf(0x04, + 0xBA.toByte(), 0xB8.toByte(), 0x6D.toByte(), 0x56.toByte(), 0x29.toByte(), 0x8C.toByte(), 0x99.toByte(), 0x6F.toByte(), + 0x56.toByte(), 0x4A.toByte(), 0x84.toByte(), 0xFC.toByte(), 0x88.toByte(), 0xE2.toByte(), 0x8A.toByte(), 0xED.toByte(), + 0x38.toByte(), 0x18.toByte(), 0x4B.toByte(), 0x12.toByte(), 0xF0.toByte(), 0x7E.toByte(), 0x51.toByte(), 0x91.toByte(), + 0x13.toByte(), 0xBE.toByte(), 0xF4.toByte(), 0x8C.toByte(), 0x76.toByte(), 0xF3.toByte(), 0xDF.toByte(), 0x3A.toByte(), + 0xDC.toByte(), 0x30.toByte(), 0x35.toByte(), 0x99.toByte(), 0xB0.toByte(), 0x8A.toByte(), 0xC0.toByte(), 0x5B.toByte(), + 0x55.toByte(), 0xEC.toByte(), 0x3D.toByte(), 0xF9.toByte(), 0x8D.toByte(), 0x93.toByte(), 0x38.toByte(), 0x57.toByte(), + 0x3A.toByte(), 0x62.toByte(), 0x42.toByte(), 0xF7.toByte(), 0x6F.toByte(), 0x5D.toByte(), 0x28.toByte(), 0xF4.toByte(), + 0xF0.toByte(), 0xF3.toByte(), 0x64.toByte(), 0xE8.toByte(), 0x7E.toByte(), 0x8F.toByte(), 0xCA.toByte(), 0x2F.toByte() + ) + private val releasePublicKeyArray = byteArrayOf(0x04.toByte(), + 0x2E.toByte(), 0xDE.toByte(), 0x11.toByte(), 0x9B.toByte(), 0xF3.toByte(), 0x37.toByte(), 0xB2.toByte(), 0x64.toByte(), + 0xFD.toByte(), 0xA1.toByte(), 0x32.toByte(), 0xCF.toByte(), 0xC7.toByte(), 0xC1.toByte(), 0x77.toByte(), 0x82.toByte(), + 0x4D.toByte(), 0x36.toByte(), 0x17.toByte(), 0xDA.toByte(), 0xC8.toByte(), 0x0F.toByte(), 0x25.toByte(), 0xDB.toByte(), + 0xB2.toByte(), 0xA4.toByte(), 0xA8.toByte(), 0xA1.toByte(), 0x18.toByte(), 0x3C.toByte(), 0x03.toByte(), 0xB9.toByte(), + 0x15.toByte(), 0x23.toByte(), 0x05.toByte(), 0xF8.toByte(), 0xF1.toByte(), 0xDB.toByte(), 0x97.toByte(), 0x00.toByte(), + 0x45.toByte(), 0x18.toByte(), 0x48.toByte(), 0x0D.toByte(), 0x50.toByte(), 0x91.toByte(), 0xAD.toByte(), 0xC1.toByte(), + 0xCA.toByte(), 0xB9.toByte(), 0xEA.toByte(), 0xCC.toByte(), 0xC1.toByte(), 0x8E.toByte(), 0x1B.toByte(), 0x9E.toByte(), + 0x9C.toByte(), 0x3B.toByte(), 0xEF.toByte(), 0xB2.toByte(), 0x93.toByte(), 0xDD.toByte(), 0x37.toByte(), 0xB2.toByte() + ) + + val publicKey: ByteArray + get() = if (mode == Mode.Developer) { + devPublicKeyArray + } else { + releasePublicKeyArray + } + + @get:Throws(Exception::class) + val privateKey: ByteArray + get() = if (mode == Mode.Developer) { + devPrivateKeyArray + } else { + throw Exception("Real manufacturer key is very secret!") + } + + val name: String + get() = if (mode == Mode.Release) "SMART CASH" else "SMART CASH SDK" + + enum class Mode { + Developer, Release + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizeConfig.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizeConfig.kt new file mode 100644 index 0000000000..1c16a6d5f0 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/PersonalizeConfig.kt @@ -0,0 +1,108 @@ +package com.tangem.tangemtest.ucase.variants.personalize.dto + +/** +[REDACTED_AUTHOR] + */ +class PersonalizeConfig { + + // Card number + var series = "BB" + var startNumber: Long = 300000000000 + 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 +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeFragment.kt new file mode 100644 index 0000000000..3456d91ad2 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeFragment.kt @@ -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(R.id.ll_container) } + private val actionFab: FloatingActionButton by lazy { mainView.findViewById(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() + 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() + } +} + + + + + + + diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeViewModel.kt new file mode 100644 index 0000000000..b2c7ec7fce --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizeViewModel.kt @@ -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 create(modelClass: Class): T = PersonalizeViewModel(config) as T +} + +class PersonalizeViewModel(private val config: PersonalizeConfig) : ViewModel() { + + val ldBlockList: MutableLiveData> by lazy { MutableLiveData(initBlockList()) } + + private fun initBlockList(): List = 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 + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/WidgetBuilder.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/WidgetBuilder.kt new file mode 100644 index 0000000000..a9aa921bcf --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/WidgetBuilder.kt @@ -0,0 +1,48 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets + +import android.view.ViewGroup +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.Block +import com.tangem.tangemtest._arch.structure.abstraction.Item +import com.tangem.tangemtest._arch.structure.abstraction.ListItemBlock +import com.tangem.tangemtest._arch.structure.impl.* +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.ViewWidget +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.StubWidget +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.block.LinearBlockWidget +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item.* + +/** +[REDACTED_AUTHOR] + */ +class WidgetBuilder { + + fun build(item: Item, parent: ViewGroup): ViewWidget? { + return when (item) { + is Block -> buildBlock(item, parent) + is BaseItem<*> -> buildUnitItem(item, parent) + else -> StubWidget(parent) + } + } + + private fun buildBlock(block: Block, parent: ViewGroup): ViewWidget { + return when (block) { + is ListItemBlock -> { + val linearBlock = LinearBlockWidget(parent, block) + block.getItems().forEach { build(it, linearBlock.view as ViewGroup) } + linearBlock + } + else -> StubWidget(parent) + } + } + + private fun buildUnitItem(item: BaseItem<*>, parent: ViewGroup): ViewWidget? { + return when (item) { + is TextItem -> TextWidget(parent, item) + is EditTextItem -> EditTextWidget(parent, item) + is NumberItem -> NumberWidget(parent, item) + is BoolItem -> SwitchWidget(parent, item) + is ListItem -> SpinnerWidget(parent, item) + else -> null + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/abstraction/ViewWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/abstraction/ViewWidget.kt new file mode 100644 index 0000000000..7926c4a0cf --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/abstraction/ViewWidget.kt @@ -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 : ViewWidget { + var dataItem: BaseItem +} + +interface BlockViewWidget : ViewWidget, DataHolder>> + +abstract class BaseWidget( + parent: ViewGroup, + override var dataItem: BaseItem +) : ViewWidget, DataWidget { + + override val view: View = inflate(getLayoutId(), parent) + + 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(dataItem.id).resName + +fun DataWidget<*>.getResDescription(): Int? = MainResourceHolder.safeGet(dataItem.id).resDescription + +internal fun inflate(id: Int, parent: ViewGroup): View { + val layoutId = if (id <= 0) R.layout.w_empty else id + val inflatedView = LayoutInflater.from(parent.context).inflate(layoutId, parent, false) + parent.addView(inflatedView) + return inflatedView +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/StubWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/StubWidget.kt new file mode 100644 index 0000000000..a087c6e0cb --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/StubWidget.kt @@ -0,0 +1,14 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl + +import android.view.ViewGroup +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseBlockWidget + +/** +[REDACTED_AUTHOR] + */ +class StubWidget(parent: ViewGroup) : BaseBlockWidget(parent) { + override fun getLayoutId(): Int = R.layout.w_empty + override var viewModel: List> = listOf() +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/block/LinearBlockWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/block/LinearBlockWidget.kt new file mode 100644 index 0000000000..fb4be30444 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/block/LinearBlockWidget.kt @@ -0,0 +1,21 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.block + +import android.view.ViewGroup +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel +import com.tangem.tangemtest._arch.structure.abstraction.ListItemBlock +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseBlockWidget + +/** +[REDACTED_AUTHOR] + */ +class LinearBlockWidget( + parent: ViewGroup, + private val children: ListItemBlock +) : BaseBlockWidget(parent) { + override fun getLayoutId(): Int = R.layout.w_personilize_block + + override var viewModel: List> = listOf() + get() = children.itemList.map { it as BaseItem<*> }.map { it.viewModel } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/DescriptionWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/DescriptionWidget.kt new file mode 100644 index 0000000000..c50a5e91dd --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/DescriptionWidget.kt @@ -0,0 +1,54 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item + +import android.view.ViewGroup +import android.widget.TextView +import androidx.transition.AutoTransition +import androidx.transition.TransitionManager +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.abstraction.BaseItem +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseWidget +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResDescription +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +abstract class DescriptionWidget( + parent: ViewGroup, + data: BaseItem +) : BaseWidget(parent, data) { + + private val descriptionContainer: ViewGroup by lazy { view.findViewById(R.id.container_description) } + private val tvDescription: TextView? by lazy { descriptionContainer.findViewById(R.id.tv_description) } + + init { + Log.d(this, "init id: ${dataItem.id}") + initDescriptionWidget() + } + + private fun initDescriptionWidget() { + dataItem.viewModel.viewState.onDescriptionVisibilityChanged = { changeDescriptionVisibility(it) } + } + + protected fun changeDescriptionVisibility(state: Int) { + val tv = tvDescription ?: return + val descriptionId = getResDescription() ?: return + val description = tv.context.getString(descriptionId) + if (description.isEmpty()) return + + tv.text = description + TransitionManager.beginDelayedTransition(view.parent as ViewGroup, AutoTransition()) + descriptionContainer.visibility = state + } +} + + + + + + + + + + + diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/EditTextWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/EditTextWidget.kt new file mode 100644 index 0000000000..c4bff5cfab --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/EditTextWidget.kt @@ -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(parent, data) { + override fun getLayoutId(): Int = R.layout.w_personalize_item_edit_text + + private val tilItem = view.findViewById(R.id.til_item) + private val etItem = view.findViewById(R.id.et_item) + + private val watcher = object : TextWatcher { + override fun afterTextChanged(s: Editable?) { + dataItem.viewModel.updateDataByView(stringOf(s)) + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + } + + init { + tilItem.hint = tilItem.context.getString(getResNameId()) + etItem.setText(dataItem.viewModel.data) + etItem.addTextChangedListener(watcher) + dataItem.viewModel.onDataUpdated = { silentUpdate(it) } + } + + private fun silentUpdate(value: String?) { + etItem.removeTextChangedListener(watcher) + etItem.setText(value) + etItem.moveCursorToEnd() + etItem.addTextChangedListener(watcher) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/NumberWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/NumberWidget.kt new file mode 100644 index 0000000000..794c4c1ea0 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/NumberWidget.kt @@ -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(parent, data) { + override fun getLayoutId(): Int = R.layout.w_personalize_item_number + + private val tilItem = view.findViewById(R.id.til_item) + private val etItem = view.findViewById(R.id.et_item) + + private val watcher = object : TextWatcher { + override fun afterTextChanged(s: Editable?) { + dataItem.viewModel.updateDataByView(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() + } + +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SpinnerWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SpinnerWidget.kt new file mode 100644 index 0000000000..b6ec1d006c --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SpinnerWidget.kt @@ -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(parent, listItem) { + override fun getLayoutId(): Int = R.layout.w_personalize_item_spinner + + private val data: ListValueWrapper = listItem.getData()!! + + private val spItem = view.findViewById(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(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?) : BaseAdapter() { + + private val itemList: List = list ?: listOf() + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = convertView ?: parent.inflate(R.layout.vh_sp_item) + + val tv = view.findViewById(R.id.tv_sp_item) + tv.text = extractData(itemList[position]) + + return view + } + + override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = convertView ?: parent.inflate(R.layout.vh_sp_dropdown_item) + + val tv = view.findViewById(R.id.tv_sp_item) + tv.text = extractData(itemList[position]) + + return view + } + + private fun extractData(item: KeyValue): String? = item.key + + override fun getItem(position: Int): String = stringOf(itemList[position]) + + override fun getItemId(position: Int): Long = position.toLong() + + override fun getCount(): Int = itemList.size + + fun getItemPosition(item: String): Int? { + val kv = itemList.firstOrNull { it.value == item } ?: return null + return itemList.indexOf(kv) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SwitchWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SwitchWidget.kt new file mode 100644 index 0000000000..3b7d85bed0 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/SwitchWidget.kt @@ -0,0 +1,32 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item + +import android.view.ViewGroup +import android.widget.CompoundButton +import androidx.appcompat.widget.SwitchCompat +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.impl.BoolItem +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId + +/** +[REDACTED_AUTHOR] + */ +class SwitchWidget(parent: ViewGroup, data: BoolItem) : DescriptionWidget(parent, data) { + override fun getLayoutId(): Int = R.layout.w_personalize_item_switch + + private val switchItem = view.findViewById(R.id.sw_item) + + private val changeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked -> + dataItem.viewModel.updateDataByView(isChecked) + } + + init { + switchItem.setText(getResNameId()) + switchItem.isChecked = dataItem.viewModel.data ?: false + switchItem.setOnCheckedChangeListener(changeListener) + dataItem.viewModel.onDataUpdated = { + switchItem.setOnCheckedChangeListener(null) + switchItem.isChecked = it ?: false + switchItem.setOnCheckedChangeListener(changeListener) + } + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/TextWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/TextWidget.kt new file mode 100644 index 0000000000..ae12a19231 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/widgets/impl/item/TextWidget.kt @@ -0,0 +1,20 @@ +package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item + +import android.view.ViewGroup +import android.widget.TextView +import com.tangem.tangemtest.R +import com.tangem.tangemtest._arch.structure.impl.TextItem +import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId + +/** +[REDACTED_AUTHOR] + */ +class TextWidget(parent: ViewGroup, data: TextItem) : DescriptionWidget(parent, data) { + override fun getLayoutId(): Int = R.layout.w_personalize_item_text + + private val tvName = view.findViewById(R.id.tv_name) + + init { + tvName?.setText(getResNameId()) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/scan/ui/ScanActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/scan/ui/ScanActionFragment.kt new file mode 100644 index 0000000000..ef5e6d8f01 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/scan/ui/ScanActionFragment.kt @@ -0,0 +1,37 @@ +package com.tangem.tangemtest.ucase.variants.scan.ui + +import android.os.Bundle +import android.view.View +import android.widget.TextView +import androidx.lifecycle.Observer +import com.tangem.tangemtest.R +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment +import ru.dev.gbixahue.eu4d.lib.android._android.views.show +import ru.dev.gbixahue.eu4d.lib.android.global.log.Log + +/** +[REDACTED_AUTHOR] + */ +class ScanActionFragment : BaseCardActionFragment() { + + override fun getLayoutId(): Int = R.layout.fg_action_card_scan + + override fun getAction(): ActionType = ActionType.Scan + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + showActionFab(true) + } + + override fun listenResponse() { + val tvStub = mainView.findViewById(R.id.tv_screen_stub) + val tvResponse by lazy { mainView.findViewById(R.id.tv_action_response_json) } + paramsVM.ldReadResponse.observe(viewLifecycleOwner, Observer { + Log.d(this, "action response: ${if (it.length > 50) it.substring(0..50) else it}") + tvStub.show(false) + tvResponse.text = it + }) + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/sign/ui/SignActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/sign/ui/SignActionFragment.kt new file mode 100644 index 0000000000..9ce99ee075 --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/sign/ui/SignActionFragment.kt @@ -0,0 +1,15 @@ +package com.tangem.tangemtest.ucase.variants.sign.ui + +import com.tangem.tangemtest.R +import com.tangem.tangemtest.ucase.resources.ActionType +import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment + +/** +[REDACTED_AUTHOR] + */ +class SignActionFragment : BaseCardActionFragment() { + + override fun getLayoutId(): Int = R.layout.fg_action_card_sign + + override fun getAction(): ActionType = ActionType.Sign +} \ No newline at end of file diff --git a/tangem-demo/src/main/res/anim/fade_in.xml b/tangem-demo/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000000..1052d2aa22 --- /dev/null +++ b/tangem-demo/src/main/res/anim/fade_in.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/anim/fade_out.xml b/tangem-demo/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000000..cfe4193dc3 --- /dev/null +++ b/tangem-demo/src/main/res/anim/fade_out.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/anim/slide_in_left.xml b/tangem-demo/src/main/res/anim/slide_in_left.xml new file mode 100644 index 0000000000..3ab232f97f --- /dev/null +++ b/tangem-demo/src/main/res/anim/slide_in_left.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/anim/slide_in_right.xml b/tangem-demo/src/main/res/anim/slide_in_right.xml new file mode 100644 index 0000000000..5c18975a6f --- /dev/null +++ b/tangem-demo/src/main/res/anim/slide_in_right.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/tangem-demo/src/main/res/anim/slide_out_left.xml b/tangem-demo/src/main/res/anim/slide_out_left.xml new file mode 100644 index 0000000000..3e5db825b6 --- /dev/null +++ b/tangem-demo/src/main/res/anim/slide_out_left.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/tangem-demo/src/main/res/anim/slide_out_right.xml b/tangem-demo/src/main/res/anim/slide_out_right.xml new file mode 100644 index 0000000000..486d3ae08c --- /dev/null +++ b/tangem-demo/src/main/res/anim/slide_out_right.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/color/menu_switch_color_thumb.xml b/tangem-demo/src/main/res/color/menu_switch_color_thumb.xml new file mode 100644 index 0000000000..ad3f42fb35 --- /dev/null +++ b/tangem-demo/src/main/res/color/menu_switch_color_thumb.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/color/menu_switch_color_track.xml b/tangem-demo/src/main/res/color/menu_switch_color_track.xml new file mode 100644 index 0000000000..d3fb4ae385 --- /dev/null +++ b/tangem-demo/src/main/res/color/menu_switch_color_track.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/res/color/mtrl_fab_bg_color_selector.xml b/tangem-demo/src/main/res/color/mtrl_fab_bg_color_selector.xml new file mode 100644 index 0000000000..a6155affe9 --- /dev/null +++ b/tangem-demo/src/main/res/color/mtrl_fab_bg_color_selector.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tangem-demo/src/main/res/color/mtrl_fab_icon_text_color_selector.xml b/tangem-demo/src/main/res/color/mtrl_fab_icon_text_color_selector.xml new file mode 100644 index 0000000000..0e2e94a4af --- /dev/null +++ b/tangem-demo/src/main/res/color/mtrl_fab_icon_text_color_selector.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tangem-demo/src/main/res/layout/a_main.xml b/tangem-demo/src/main/res/layout/a_main.xml new file mode 100644 index 0000000000..a3736051f4 --- /dev/null +++ b/tangem-demo/src/main/res/layout/a_main.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/tangem-demo/src/main/res/layout/activity_main.xml b/tangem-demo/src/main/res/layout/activity_main.xml deleted file mode 100644 index 2af3ad92de..0000000000 --- a/tangem-demo/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - -