Updated on 2026-08-14
This commit is contained in:
commit
4cf313b915
98 changed files with 1886 additions and 585 deletions
|
|
@ -158,4 +158,7 @@ dependencies {
|
|||
//dependencies for flow demo
|
||||
implementation 'io.grpc:grpc-okhttp:1.28.0'
|
||||
implementation 'io.grpc:grpc-stub:1.28.0'
|
||||
|
||||
//dependencies for TokenEmvEngine
|
||||
implementation 'com.github.walleth.kethereum:extensions_kotlin:0.81.4'
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import com.tangem.tangem_sdk.R;
|
|||
* Created by dvol on 06.08.2017.
|
||||
*/
|
||||
public enum Blockchain {
|
||||
Unknown("", "", 1.0, R.drawable.ic_logo_unknown, ""),
|
||||
Unknown("", "", 1.0, R.drawable.ic_logo_unknown, "Unknown"),
|
||||
Bitcoin("BTC", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Bitcoin"),
|
||||
BitcoinTestNet("BTC/test", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin_testnet, "Bitcoin Testnet"),
|
||||
BitcoinDual("BTC/dual", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Bitcoin"),
|
||||
|
|
@ -32,7 +32,8 @@ public enum Blockchain {
|
|||
Eos("EOS", "EOS", 10000.0, R.drawable.tangem2, "EOS"),
|
||||
Ducatus("DUC", "DUC", 100000000.0, R.drawable.tangem2, "Ducatus"),
|
||||
Tezos("TEZOS", "XTZ", 10000000.0, R.drawable.ic_logo_tezos, "Tezos"),
|
||||
FlowDemo("FLOW/demo", "", 1.0, R.drawable.tangem2, "Flow demo");
|
||||
FlowDemo("FLOW/demo", "", 1.0, R.drawable.tangem2, "Flow demo"),
|
||||
TokenEmv("TTW", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum");
|
||||
|
||||
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
|
||||
mID = ID;
|
||||
|
|
@ -67,7 +68,7 @@ public enum Blockchain {
|
|||
for (Blockchain blockchain : values()) {
|
||||
if (blockchain.getID().equals(id)) return blockchain;
|
||||
}
|
||||
return null;
|
||||
return Blockchain.Unknown;
|
||||
}
|
||||
|
||||
public static Blockchain fromCurrency(String currency) {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,14 @@ public class Server {
|
|||
}
|
||||
}
|
||||
|
||||
public static class ApiInfuraRopsten {
|
||||
public static final String URL_INFURA_ROPSTEN = ServerURL.API_INFURA_ROPSTEN;
|
||||
|
||||
public static class Method {
|
||||
public static final String MAIN = "v3/613a0b14833145968b1f656240c7d245";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ApiSoChain {
|
||||
public static final String URL = ServerURL.API_SOCHAIN_V2;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ public class ServerApiInfura {
|
|||
public ServerApiInfura(Blockchain blockchain) {
|
||||
if (blockchain == Blockchain.EthereumTestNet) {
|
||||
infuraApi = App.Companion.getNetworkComponent().getRetrofitInfuraTestnet().create(InfuraApi.class);
|
||||
} else if (blockchain == Blockchain.TokenEmv) {
|
||||
infuraApi = App.Companion.getNetworkComponent().getRetrofitInfuraRopsten().create(InfuraApi.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.data.network
|
||||
|
||||
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import com.tangem.data.network.model.TokenEmvGetTransferFeeAnswer
|
||||
import com.tangem.data.network.model.TokenEmvGetTransferFeeBody
|
||||
import com.tangem.data.network.model.TokenEmvTransferAnswer
|
||||
import com.tangem.data.network.model.TokenEmvTransferBody
|
||||
import com.tangem.tangem_card.util.Log
|
||||
import io.reactivex.SingleObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ServerApiTokenEmv {
|
||||
private val TAG = ServerApiTokenEmv::class.java.simpleName
|
||||
|
||||
private val tangemServer = "https://emvsupport.appspot.com/"
|
||||
|
||||
private val tokenEmvApi = Retrofit.Builder()
|
||||
.baseUrl(tangemServer)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.addConverterFactory(ScalarsConverterFactory.create())
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //logging for testing
|
||||
.client(OkHttpClient.Builder().addInterceptor(
|
||||
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
).build())
|
||||
.build()
|
||||
.create(TokenEmvApi::class.java)
|
||||
|
||||
fun transfer(tokenEmvTransferBody: TokenEmvTransferBody, transferObserver: SingleObserver<TokenEmvTransferAnswer>) {
|
||||
Log.i(TAG, "new transfer request")
|
||||
|
||||
tokenEmvApi.transfer(tokenEmvTransferBody)
|
||||
.timeout(30, TimeUnit.SECONDS)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(transferObserver)
|
||||
}
|
||||
|
||||
fun getTransferFee(tokenEmvGetTransferFeeBody: TokenEmvGetTransferFeeBody, transferObserver: SingleObserver<TokenEmvGetTransferFeeAnswer>) {
|
||||
Log.i(TAG, "new get transfer fee request")
|
||||
|
||||
tokenEmvApi.getTransferFee(tokenEmvGetTransferFeeBody)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(transferObserver)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ class ServerURL {
|
|||
static final String API_COINMARKETCAP = "https://pro-api.coinmarketcap.com/";
|
||||
static final String API_INFURA = "https://mainnet.infura.io/";
|
||||
static final String API_INFURA_TESTNET = "https://rinkeby.infura.io/";
|
||||
static final String API_INFURA_ROPSTEN = "https://ropsten.infura.io/";
|
||||
static final String API_SOCHAIN_V2 = "https://chain.so/";
|
||||
static final String API_ESTIMATEFEE = "https://estimatefee.com/";
|
||||
static final String API_UPDATE_VERSION = "https://raw.githubusercontent.com/";
|
||||
|
|
|
|||
17
app/src/main/java/com/tangem/data/network/TokenEmvApi.kt
Normal file
17
app/src/main/java/com/tangem/data/network/TokenEmvApi.kt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.data.network
|
||||
|
||||
import com.tangem.data.network.model.TokenEmvGetTransferFeeAnswer
|
||||
import com.tangem.data.network.model.TokenEmvGetTransferFeeBody
|
||||
import com.tangem.data.network.model.TokenEmvTransferAnswer
|
||||
import com.tangem.data.network.model.TokenEmvTransferBody
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Single
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface TokenEmvApi {
|
||||
@POST("./card/transfer")
|
||||
fun transfer(@Body tokenEmvTransferBody: TokenEmvTransferBody): Single<TokenEmvTransferAnswer>
|
||||
@POST("./card/transfer/fee")
|
||||
fun getTransferFee(@Body tokenEmvGetTransferFeeBody: TokenEmvGetTransferFeeBody): Single<TokenEmvGetTransferFeeAnswer>
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.data.network.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class TokenEmvTransferBody(
|
||||
val CID: String,
|
||||
val publicKey: String,
|
||||
val amount: String,
|
||||
val currency: String,
|
||||
val recipient: String,
|
||||
@SerializedName("fee_limit")
|
||||
val feeLimit: String,
|
||||
val sequence: Int,
|
||||
val signature: String
|
||||
)
|
||||
|
||||
data class TokenEmvTransferAnswer(
|
||||
val error: String?,
|
||||
val errorCode: Int?,
|
||||
val success: Boolean?,
|
||||
val tx_id: String?,
|
||||
val blockchain_tx_id: String?
|
||||
)
|
||||
|
||||
data class TokenEmvGetTransferFeeBody(
|
||||
val CID: String,
|
||||
val publicKey: String
|
||||
)
|
||||
|
||||
data class TokenEmvGetTransferFeeAnswer(
|
||||
val error: String?,
|
||||
val errorCode: Int?,
|
||||
val success: Boolean?,
|
||||
val fee: String?,
|
||||
val currency: String?
|
||||
)
|
||||
|
|
@ -17,6 +17,9 @@ interface NetworkComponent {
|
|||
@get:Named(Server.ApiInfuraTestnet.URL_INFURA_TESTNET)
|
||||
val retrofitInfuraTestnet: Retrofit
|
||||
|
||||
@get:Named(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN)
|
||||
val retrofitInfuraRopsten: Retrofit
|
||||
|
||||
@get:Named(Server.ApiMaticTesnet.URL_MATIC_TESTNET)
|
||||
val retrofitMaticTesnet: Retrofit
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,18 @@ internal class NetworkModule {
|
|||
return builder.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN)
|
||||
fun provideRetrofitInfuraRopsten(): Retrofit {
|
||||
val builder = Retrofit.Builder()
|
||||
.baseUrl(Server.ApiInfuraRopsten.URL_INFURA_ROPSTEN)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
if (BuildConfig.DEBUG)
|
||||
builder.client(createOkHttpClient())
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named(Server.ApiRootstock.URL_ROOTSTOCK)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import android.nfc.Tag
|
|||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.navigation.findNavController
|
||||
import com.scottyab.rootbeer.RootBeer
|
||||
|
|
@ -16,6 +17,7 @@ import com.tangem.di.ToastHelper
|
|||
import com.tangem.tangem_sdk.android.nfc.NfcLifecycleObserver
|
||||
import com.tangem.tangem_sdk.android.reader.NfcManager
|
||||
import com.tangem.ui.dialog.RootFoundDialog
|
||||
import com.tangem.ui.fragment.MainFragment
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import com.tangem.wallet.R
|
||||
import javax.inject.Inject
|
||||
|
|
@ -44,6 +46,8 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
|
|||
}
|
||||
|
||||
private fun navigateSafelyToMainFragment() {
|
||||
if (getActiveFragment() is MainFragment) return
|
||||
|
||||
try {
|
||||
findNavController(R.id.nav_host_fragment).popBackStack()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
|
|
@ -73,8 +77,7 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
|
|||
}
|
||||
|
||||
override fun onTagDiscovered(tag: Tag) {
|
||||
val activeFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
|
||||
?.childFragmentManager?.primaryNavigationFragment
|
||||
val activeFragment = getActiveFragment()
|
||||
if (activeFragment is NfcAdapter.ReaderCallback) {
|
||||
activeFragment.onTagDiscovered(tag)
|
||||
} else {
|
||||
|
|
@ -82,4 +85,8 @@ class MainActivity : AppCompatActivity(), NfcAdapter.ReaderCallback {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getActiveFragment(): Fragment? {
|
||||
return supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
|
||||
?.childFragmentManager?.primaryNavigationFragment
|
||||
}
|
||||
}
|
||||
|
|
@ -147,21 +147,23 @@ public class WaitSecurityDelayDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
private void setRemainingTimeout(final int msec) {
|
||||
progressBar.post(() -> {
|
||||
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
|
||||
if (timer != null) {
|
||||
// we get delay latency from card for first time - don't change progress by timer, only by card answer
|
||||
progressBar.setMax(progress + msec);
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
} else {
|
||||
int newProgress = progressBar.getMax() - msec;
|
||||
if (newProgress > progress)
|
||||
progressBar.setProgress(newProgress);
|
||||
else
|
||||
if (progressBar != null) {
|
||||
progressBar.post(() -> {
|
||||
int progress = WaitSecurityDelayDialog.this.progressBar.getProgress();
|
||||
if (timer != null) {
|
||||
// we get delay latency from card for first time - don't change progress by timer, only by card answer
|
||||
progressBar.setMax(progress + msec);
|
||||
}
|
||||
});
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
} else {
|
||||
int newProgress = progressBar.getMax() - msec;
|
||||
if (newProgress > progress)
|
||||
progressBar.setProgress(newProgress);
|
||||
else
|
||||
progressBar.setMax(progress + msec);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import com.tangem.wallet.rsk.RskEngine
|
|||
import com.tangem.wallet.rsk.RskTokenEngine
|
||||
import com.tangem.wallet.token.TokenEngine
|
||||
import com.tangem.wallet.tezos.TezosEngine
|
||||
import com.tangem.wallet.tokenEmv.TokenEmvEngine
|
||||
import com.tangem.wallet.xlm.XlmAssetEngine
|
||||
import com.tangem.wallet.xlm.XlmEngine
|
||||
import com.tangem.wallet.xlmTag.XlmTagEngine
|
||||
|
|
@ -60,6 +61,7 @@ object CoinEngineFactory {
|
|||
Blockchain.Tezos -> TezosEngine()
|
||||
Blockchain.BitcoinDual -> BtcMultisigEngine()
|
||||
Blockchain.FlowDemo -> FlowDemoEngine()
|
||||
Blockchain.TokenEmv -> TokenEmvEngine()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -109,6 +111,8 @@ object CoinEngineFactory {
|
|||
BtcMultisigEngine(context)
|
||||
else if (Blockchain.FlowDemo == context.blockchain)
|
||||
FlowDemoEngine(context)
|
||||
else if (Blockchain.TokenEmv == context.blockchain)
|
||||
TokenEmvEngine(context)
|
||||
else
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ public class EthTransaction {
|
|||
return kec.digest(plainMsg);
|
||||
}
|
||||
|
||||
public int BruteRecoveryID2(ECDSASignatureETH sig, byte[] messageHash, byte[] thisKey) {
|
||||
public static int BruteRecoveryID2(ECDSASignatureETH sig, byte[] messageHash, byte[] thisKey) {
|
||||
Log.e("ETH_KZ", BTCUtils.toHex(thisKey));
|
||||
int recId = -1;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
|
|
|
|||
|
|
@ -8,31 +8,46 @@ import com.tangem.wallet.eth.EthData;
|
|||
|
||||
public class TokenData extends EthData {
|
||||
private CoinEngine.InternalAmount balanceAlter = null;
|
||||
|
||||
// private Integer sequence = null;
|
||||
|
||||
@Override
|
||||
public void clearInfo() {
|
||||
super.clearInfo();
|
||||
balanceAlter = null;
|
||||
// sequence = null;
|
||||
}
|
||||
|
||||
public CoinEngine.InternalAmount getBalanceAlterInInternalUnits() {
|
||||
return balanceAlter;
|
||||
|
||||
}
|
||||
|
||||
public void setBalanceAlterInInternalUnits(CoinEngine.InternalAmount value) {
|
||||
balanceAlter = value;
|
||||
}
|
||||
|
||||
// public Integer getSequence() {
|
||||
// return sequence;
|
||||
// }
|
||||
//
|
||||
// public void setSequence(Integer sequence) {
|
||||
// this.sequence = sequence;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void loadFromBundle(Bundle B) {
|
||||
super.loadFromBundle(B);
|
||||
|
||||
if( B.containsKey("BalanceDecimalAlter" )) {
|
||||
if (B.containsKey("BalanceDecimalAlter")) {
|
||||
balanceAlter = new CoinEngine.InternalAmount(B.getString("BalanceDecimalAlter"), "wei");
|
||||
}else{
|
||||
balanceAlter=null;
|
||||
} else {
|
||||
balanceAlter = null;
|
||||
}
|
||||
// if (B.containsKey("Sequence")) {
|
||||
// sequence = B.getInt("Sequence");
|
||||
// } else {
|
||||
// sequence = null;
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -40,9 +55,12 @@ public class TokenData extends EthData {
|
|||
super.saveToBundle(B);
|
||||
|
||||
try {
|
||||
if( balanceAlter!=null ) {
|
||||
if (balanceAlter != null) {
|
||||
B.putString("BalanceDecimalAlter", balanceAlter.toString());
|
||||
}
|
||||
// if (sequence != null) {
|
||||
// B.putInt("Sequence", sequence);
|
||||
// }
|
||||
} catch (Exception e) {
|
||||
Log.e("Can't save to bundle ", e.getMessage());
|
||||
}
|
||||
|
|
|
|||
349
app/src/main/java/com/tangem/wallet/tokenEmv/TokenEmvEngine.kt
Normal file
349
app/src/main/java/com/tangem/wallet/tokenEmv/TokenEmvEngine.kt
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
package com.tangem.wallet.tokenEmv
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.InputFilter
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.tangem.data.Blockchain
|
||||
import com.tangem.data.network.ServerApiInfura
|
||||
import com.tangem.data.network.ServerApiTokenEmv
|
||||
import com.tangem.data.network.model.*
|
||||
import com.tangem.tangem_card.data.TangemCard
|
||||
import com.tangem.tangem_card.reader.CardProtocol.TangemException
|
||||
import com.tangem.tangem_card.reader.TLV
|
||||
import com.tangem.tangem_card.reader.TLVList
|
||||
import com.tangem.tangem_card.tasks.SignTask
|
||||
import com.tangem.tangem_card.util.Util
|
||||
import com.tangem.util.DecimalDigitsInputFilter
|
||||
import com.tangem.wallet.EthTransaction
|
||||
import com.tangem.wallet.Keccak256
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.TangemContext
|
||||
import com.tangem.wallet.token.TokenEngine
|
||||
import io.reactivex.observers.DisposableSingleObserver
|
||||
import org.apache.commons.lang3.SerializationUtils
|
||||
import org.kethereum.extensions.toBytesPadded
|
||||
import java.math.BigInteger
|
||||
|
||||
class TokenEmvEngine : TokenEngine {
|
||||
constructor() : super()
|
||||
constructor(context: TangemContext) : super(context)
|
||||
|
||||
private val TAG = TokenEmvEngine::class.java.simpleName
|
||||
|
||||
private fun hasLinkedContract(): Boolean {
|
||||
return ctx.card.issuerData != null && ctx.card.issuerData.size == 44
|
||||
}
|
||||
|
||||
override fun getBlockchain(): Blockchain {
|
||||
return Blockchain.TokenEmv
|
||||
}
|
||||
|
||||
override fun getChainIdNum(): Int {
|
||||
return EthTransaction.ChainEnum.Ropsten.value
|
||||
}
|
||||
|
||||
override fun getBalance(): Amount? {
|
||||
return if (!hasBalanceInfo()) {
|
||||
null
|
||||
} else {
|
||||
convertToAmount(coinData.balanceInInternalUnits)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBalanceHTML(): String? {
|
||||
return if (hasLinkedContract()) {
|
||||
if (balance != null) {
|
||||
balance!!.toDescriptionString(tokenDecimals)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} else {
|
||||
"NO LINKED CONTRACT"
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBalanceCurrency(): String? {
|
||||
return ctx.card.getTokenSymbol()
|
||||
}
|
||||
|
||||
override fun getAmountInputFilters(): Array<InputFilter>? {
|
||||
return arrayOf(DecimalDigitsInputFilter(tokenDecimals))
|
||||
}
|
||||
|
||||
override fun getFeeCurrency(): String? {
|
||||
return balanceCurrency
|
||||
}
|
||||
|
||||
override fun isBalanceNotZero(): Boolean {
|
||||
if (coinData == null) return false
|
||||
return if (coinData.balanceInInternalUnits == null) {
|
||||
false
|
||||
} else {
|
||||
coinData.balanceInInternalUnits.notZero()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBalanceEquivalent(): String? {
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun evaluateFeeEquivalent(fee: String?): String? {
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun defineWallet() {
|
||||
try {
|
||||
if (hasLinkedContract()) {
|
||||
ctx.coinData.wallet = TLVList.fromBytes(ctx.card.issuerData).getTLV(TLV.Tag.TAG_Token_Contract_Address).asString
|
||||
} else {
|
||||
ctx.coinData.wallet = calculateAddress(ctx.card.walletPublicKey)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ctx.coinData.wallet = "ERROR"
|
||||
throw TangemException("Can't define wallet address")
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasBalanceInfo(): Boolean {
|
||||
return coinData.balanceInInternalUnits != null
|
||||
}
|
||||
|
||||
override fun getWalletExplorerUri(): Uri {
|
||||
return Uri.parse("https://ropsten.etherscan.io/token/" + getContractAddress(ctx.card) + "?a=" + ctx.coinData.wallet)
|
||||
}
|
||||
|
||||
override fun isExtractPossible(): Boolean {
|
||||
if (!hasBalanceInfo()) {
|
||||
ctx.setMessage(R.string.loaded_wallet_error_obtaining_blockchain_data)
|
||||
} else if (!isBalanceNotZero) {
|
||||
ctx.setMessage(R.string.general_wallet_empty)
|
||||
} else if (awaitingConfirmation()) {
|
||||
ctx.setMessage(R.string.loaded_wallet_message_wait)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun checkNewTransactionAmountAndFee(amount: Amount, fee: Amount?, isFeeIncluded: Boolean): Boolean {
|
||||
try {
|
||||
if (isFeeIncluded && (amount > balance || amount < fee)) return false
|
||||
if (!isFeeIncluded && amount.add(fee) > balance) return false
|
||||
} catch (e: NumberFormatException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun constructTransaction(amountValue: Amount, feeValue: Amount?, IncFee: Boolean, targetAddress: String?): SignTask.TransactionToSign? {
|
||||
val functionBytes = "transfer".toByteArray()
|
||||
|
||||
val contractHex = coinData.wallet.substring(2)
|
||||
val contractBytes = Util.hexToBytes(contractHex)
|
||||
val recipientHex = targetAddress!!.substring(2)
|
||||
val recipientBytes = Util.hexToBytes(recipientHex)
|
||||
|
||||
val amountBytes = convertToInternalAmount(amountValue).toBigInteger().toBytesPadded(32)
|
||||
val feeLimitBytes = convertToInternalAmount(feeValue).toBigInteger().toBytesPadded(32)
|
||||
|
||||
val sequence = ctx.card.SignedHashes
|
||||
val sequenceBytes = sequence.toBigInteger().toBytesPadded(4)
|
||||
|
||||
val hashToSign = Keccak256().digest(contractBytes + functionBytes + amountBytes + recipientBytes + feeLimitBytes + sequenceBytes)
|
||||
|
||||
return object : SignTask.TransactionToSign {
|
||||
override fun isSigningMethodSupported(signingMethod: TangemCard.SigningMethod): Boolean {
|
||||
return signingMethod == TangemCard.SigningMethod.Sign_Hash
|
||||
}
|
||||
|
||||
override fun getHashesToSign(): Array<ByteArray> {
|
||||
return arrayOf(hashToSign)
|
||||
}
|
||||
|
||||
@Throws(java.lang.Exception::class)
|
||||
override fun getRawDataToSign(): ByteArray {
|
||||
throw java.lang.Exception("Signing of raw transaction not supported for " + this.javaClass.simpleName)
|
||||
}
|
||||
|
||||
@Throws(java.lang.Exception::class)
|
||||
override fun getHashAlgToSign(): String {
|
||||
throw java.lang.Exception("Signing of raw transaction not supported for " + this.javaClass.simpleName)
|
||||
}
|
||||
|
||||
@Throws(java.lang.Exception::class)
|
||||
override fun getIssuerTransactionSignature(dataToSignByIssuer: ByteArray): ByteArray {
|
||||
throw java.lang.Exception("Transaction validation by issuer not supported in this version")
|
||||
}
|
||||
|
||||
@Throws(java.lang.Exception::class)
|
||||
override fun onSignCompleted(signFromCard: ByteArray): ByteArray {
|
||||
// val r = BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32))
|
||||
// var s: BigInteger? = BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64))
|
||||
// s = CryptoUtil.toCanonicalised(s)
|
||||
//
|
||||
// val publicKey = ctx.getCard().getWalletPublicKey()
|
||||
//
|
||||
// val verified = ECKey.verify(hashToSign, ECKey.ECDSASignature(r, s), publicKey)
|
||||
// if (!verified) {
|
||||
// Log.e(this.javaClass.simpleName + "-CHECK", "sign Failed.")
|
||||
// }
|
||||
//
|
||||
// val v = BruteRecoveryID2(ECDSASignatureETH(r, s), hashToSign, publicKey)
|
||||
// if (v != 27 && v != 28) {
|
||||
// Log.e(TAG, "invalid v")
|
||||
// throw java.lang.Exception("Error in " + this.javaClass.simpleName + " - invalid v")
|
||||
// }
|
||||
// Log.e(TAG, this.javaClass.simpleName + " V: " + v.toString())
|
||||
//
|
||||
// var rBytes = r.toByteArray()
|
||||
// if (rBytes.size == 33) {
|
||||
// rBytes = rBytes.copyOfRange(1,33)
|
||||
// }
|
||||
// val sBytes = s.toByteArray()
|
||||
//
|
||||
// val tokenEmvTransferBody = TokenEmvTransferBody(
|
||||
// CID = Util.bytesToHex(ctx.card.cid),
|
||||
// publicKey = Util.bytesToHex(ctx.card.walletPublicKey),
|
||||
// contract = contractHex,
|
||||
// amount = Util.byteArrayToHexString(amountBytes),
|
||||
// recipient = recipientHex,
|
||||
// feeLimit = Util.byteArrayToHexString(feeLimitBytes),
|
||||
// sequence = sequence,
|
||||
// r = Util.byteArrayToHexString(rBytes),
|
||||
// s = Util.byteArrayToHexString(sBytes),
|
||||
// v = v
|
||||
// )
|
||||
|
||||
val tokenEmvTransferBody = TokenEmvTransferBody(
|
||||
CID = Util.bytesToHex(ctx.card.cid),
|
||||
publicKey = Util.bytesToHex(ctx.card.walletPublicKey),
|
||||
amount = amountValue.toValueString(),
|
||||
currency = amountValue.currency,
|
||||
recipient = targetAddress,
|
||||
feeLimit = feeValue!!.toValueString(),
|
||||
sequence = sequence,
|
||||
signature = Util.bytesToHex(signFromCard)
|
||||
)
|
||||
|
||||
val jsonBody = Gson().toJson(tokenEmvTransferBody)
|
||||
val serializedBody = SerializationUtils.serialize(jsonBody)
|
||||
|
||||
notifyOnNeedSendTransaction(serializedBody)
|
||||
return serializedBody
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks: BlockchainRequestsCallbacks) {
|
||||
val serverApiInfura = ServerApiInfura(ctx.blockchain)
|
||||
|
||||
val responseListener: ServerApiInfura.ResponseListener = object : ServerApiInfura.ResponseListener {
|
||||
override fun onSuccess(method: String, infuraResponse: InfuraResponse) {
|
||||
try {
|
||||
var balanceCap = infuraResponse.result
|
||||
balanceCap = balanceCap!!.substring(2)
|
||||
val l = BigInteger(balanceCap, 16)
|
||||
coinData.balanceInInternalUnits = InternalAmount(l, ctx.card.tokenSymbol)
|
||||
coinData.isBalanceReceived = true
|
||||
// Log.i("$TAG eth_call", balanceCap)
|
||||
} catch (e: java.lang.Exception) {
|
||||
onFail(method, e.message ?: "invalid response")
|
||||
}
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
}
|
||||
|
||||
override fun onFail(method: String, message: String) {
|
||||
Log.e(TAG, "onFail: $method $message")
|
||||
ctx.error = message
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
serverApiInfura.setResponseListener(responseListener)
|
||||
|
||||
if (validateAddress(getContractAddress(ctx.card))) {
|
||||
serverApiInfura.requestData(ServerApiInfura.INFURA_ETH_CALL, 67, coinData.wallet, getContractAddress(ctx.card), "")
|
||||
} else {
|
||||
ctx.error = "Smart contract address not defined"
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestFee(blockchainRequestsCallbacks: BlockchainRequestsCallbacks, targetAddress: String?, amount: Amount) {
|
||||
|
||||
val tokenEmvTransferBody = TokenEmvGetTransferFeeBody(
|
||||
CID = Util.bytesToHex(ctx.card.cid),
|
||||
publicKey = Util.bytesToHex(ctx.card.walletPublicKey)
|
||||
)
|
||||
|
||||
val observer = object : DisposableSingleObserver<TokenEmvGetTransferFeeAnswer>() {
|
||||
override fun onError(e: Throwable) {
|
||||
ctx.error = "Can't get transfer fee, ${e.message}"
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
|
||||
override fun onSuccess(t: TokenEmvGetTransferFeeAnswer) {
|
||||
if (t.success != null && t.success) {
|
||||
ctx.error = null
|
||||
coinData.minFee = Amount(t.fee, t.currency)
|
||||
coinData.normalFee = Amount(t.fee, t.currency)
|
||||
coinData.maxFee = Amount(t.fee, t.currency)
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
} else {
|
||||
if (t.error != null) {
|
||||
ctx.error = t.error
|
||||
} else {
|
||||
ctx.error = "Can't get transfer fee, code ${t.errorCode}"
|
||||
}
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ServerApiTokenEmv().getTransferFee(tokenEmvTransferBody, observer)
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
}
|
||||
|
||||
override fun requestSendTransaction(blockchainRequestsCallbacks: BlockchainRequestsCallbacks, txForSend: ByteArray?) {
|
||||
val jsonBody = SerializationUtils.deserialize<String>(txForSend)
|
||||
|
||||
Log.e(TAG, jsonBody)
|
||||
|
||||
val tokenEmvTransferBody = Gson().fromJson(jsonBody, TokenEmvTransferBody::class.java)
|
||||
|
||||
val observer = object : DisposableSingleObserver<TokenEmvTransferAnswer>() {
|
||||
override fun onError(e: Throwable) {
|
||||
ctx.error = "Can't send transfer, ${e.message}"
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
|
||||
override fun onSuccess(t: TokenEmvTransferAnswer) {
|
||||
if (t.success != null && t.success) {
|
||||
ctx.error = null
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
} else {
|
||||
if (t.error != null) {
|
||||
ctx.error = t.error
|
||||
} else {
|
||||
ctx.error = "Can't send transfer, code ${t.errorCode}"
|
||||
}
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ServerApiTokenEmv().transfer(tokenEmvTransferBody, observer)
|
||||
}
|
||||
|
||||
override fun allowSelectFeeLevel(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun pendingTransactionTimeoutInSeconds(): Int {
|
||||
return 60
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -74,7 +74,10 @@ class CardSession(
|
|||
}
|
||||
|
||||
runnable.run(this) { result ->
|
||||
stop()
|
||||
when (result) {
|
||||
is CompletionResult.Success -> stop()
|
||||
is CompletionResult.Failure -> stopWithError(result.error)
|
||||
}
|
||||
callback(result)
|
||||
}
|
||||
}
|
||||
|
|
@ -99,7 +102,6 @@ class CardSession(
|
|||
}
|
||||
is CompletionResult.Success -> {
|
||||
callback(this, null)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ abstract class Command<T : CommandResponse> : CardSessionRunnable<T> {
|
|||
}
|
||||
}
|
||||
is CompletionResult.Failure ->
|
||||
if (result.error == SessionError.TagLost()) {
|
||||
if (result.error is SessionError.TagLost) {
|
||||
session.viewDelegate.onTagLost()
|
||||
} else {
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
|
|
|
|||
|
|
@ -14,67 +14,50 @@ import java.util.*
|
|||
/**
|
||||
* Determines which type of data is required for signing.
|
||||
*/
|
||||
data class SigningMethod(val rawValue: Int) {
|
||||
data class SigningMethodMask(val rawValue: Int) {
|
||||
|
||||
fun contains(value: Int): Boolean {
|
||||
fun contains(signingMethod: SigningMethod): Boolean {
|
||||
return if (rawValue and 0x80 == 0) {
|
||||
value == rawValue
|
||||
signingMethod.code == rawValue
|
||||
} else {
|
||||
rawValue and (0x01 shl value) != 0
|
||||
rawValue and (0x01 shl signingMethod.code) != 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val signHash = 0
|
||||
const val signRaw = 1
|
||||
const val signHashValidatedByIssuer = 2
|
||||
const val signRawValidatedByIssuer = 3
|
||||
const val signHashValidatedByIssuerAndWriteIssuerData = 4
|
||||
const val signRawValidatedByIssuerAndWriteIssuerData = 5
|
||||
const val signPos = 6
|
||||
enum class SigningMethod(val code: Int) {
|
||||
SignHash(0),
|
||||
SignRaw(1),
|
||||
SignHashValidateByIssuer(2),
|
||||
SignRawValidateByIssuer(3),
|
||||
SignHashValidateByIssuerWriteIssuerData(4),
|
||||
SignRawValidateByIssuerWriteIssuerData(5),
|
||||
SignPos(6)
|
||||
}
|
||||
|
||||
fun build(
|
||||
signHash: Boolean = false,
|
||||
signRaw: Boolean = false,
|
||||
signHashValidatedByIssuer: Boolean = false,
|
||||
signRawValidatedByIssuer: Boolean = false,
|
||||
signHashValidatedByIssuerAndWriteIssuerData: Boolean = false,
|
||||
signRawValidatedByIssuerAndWriteIssuerData: Boolean = false,
|
||||
signPos: Boolean = false
|
||||
class SigningMethodMaskBuilder() {
|
||||
|
||||
): SigningMethod {
|
||||
fun Boolean.toInt() = if (this) 1 else 0
|
||||
private val signingMethods = mutableSetOf<SigningMethod>()
|
||||
|
||||
val signingMethodsCount = 0 +
|
||||
signHash.toInt() +
|
||||
signRaw.toInt() +
|
||||
signHashValidatedByIssuer.toInt() +
|
||||
signRawValidatedByIssuer.toInt() +
|
||||
signHashValidatedByIssuerAndWriteIssuerData.toInt() +
|
||||
signRawValidatedByIssuerAndWriteIssuerData.toInt() +
|
||||
signPos.toInt()
|
||||
fun add(signingMethod: SigningMethod) {
|
||||
signingMethods.add(signingMethod)
|
||||
}
|
||||
|
||||
var signingMethod: Int = 0
|
||||
if (signingMethodsCount == 1) {
|
||||
if (signHash) signingMethod += SigningMethod.signHash
|
||||
if (signRaw) signingMethod += SigningMethod.signRaw
|
||||
if (signHashValidatedByIssuer) signingMethod += SigningMethod.signHashValidatedByIssuer
|
||||
if (signRawValidatedByIssuer) signingMethod += SigningMethod.signRawValidatedByIssuer
|
||||
if (signHashValidatedByIssuerAndWriteIssuerData) signingMethod += SigningMethod.signHashValidatedByIssuerAndWriteIssuerData
|
||||
if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += SigningMethod.signRawValidatedByIssuerAndWriteIssuerData
|
||||
if (signPos) signingMethod += SigningMethod.signPos
|
||||
} else if (signingMethodsCount > 1) {
|
||||
signingMethod = 0x80
|
||||
if (signHash) signingMethod += 0x01
|
||||
if (signRaw) signingMethod += 0x01 shl SigningMethod.signRaw
|
||||
if (signHashValidatedByIssuer) signingMethod += 0x01 shl SigningMethod.signHashValidatedByIssuer
|
||||
if (signRawValidatedByIssuer) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuer
|
||||
if (signHashValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signHashValidatedByIssuerAndWriteIssuerData
|
||||
if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuerAndWriteIssuerData
|
||||
if (signPos) signingMethod += 0x01 shl SigningMethod.signPos
|
||||
fun build(): SigningMethodMask {
|
||||
val rawValue: Int = when {
|
||||
signingMethods.count() == 0 -> {
|
||||
0
|
||||
}
|
||||
signingMethods.count() == 1 -> {
|
||||
signingMethods.iterator().next().code
|
||||
}
|
||||
else -> {
|
||||
signingMethods.fold(
|
||||
0x80, { acc, singingMethod -> acc + (0x01 shl singingMethod.code) }
|
||||
)
|
||||
}
|
||||
return SigningMethod(signingMethod)
|
||||
}
|
||||
return SigningMethodMask(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,22 +96,23 @@ enum class CardStatus(val code: Int) {
|
|||
*/
|
||||
data class ProductMask(val rawValue: Int) {
|
||||
|
||||
fun contains(value: Int): Boolean = (rawValue and value) != 0
|
||||
fun contains(product: Product): Boolean = (rawValue and product.code) != 0
|
||||
|
||||
companion object {
|
||||
const val note = 0x01
|
||||
const val tag = 0x02
|
||||
const val idCard = 0x04
|
||||
const val idIssuer = 0x08
|
||||
}
|
||||
}
|
||||
|
||||
enum class Product(val code: Int) {
|
||||
Note(0x01),
|
||||
Tag(0x02),
|
||||
IdCard(0x04),
|
||||
IdIssuer(0x08)
|
||||
}
|
||||
|
||||
class ProductMaskBuilder() {
|
||||
|
||||
private var productMaskValue = 0
|
||||
|
||||
fun add(productCode: Int) {
|
||||
productMaskValue = productMaskValue or productCode
|
||||
fun add(product: Product) {
|
||||
productMaskValue = productMaskValue or product.code
|
||||
}
|
||||
|
||||
fun build() = ProductMask(productMaskValue)
|
||||
|
|
@ -299,7 +283,7 @@ class Card(
|
|||
/**
|
||||
* Defines what data should be submitted to SIGN command.
|
||||
*/
|
||||
val signingMethod: SigningMethod?,
|
||||
val signingMethods: SigningMethodMask?,
|
||||
|
||||
/**
|
||||
* Delay in seconds before COS executes commands protected by PIN2.
|
||||
|
|
@ -415,7 +399,7 @@ class ReadCommand : Command<Card>() {
|
|||
issuerPublicKey = decoder.decodeOptional(TlvTag.IssuerDataPublicKey),
|
||||
curve = decoder.decodeOptional(TlvTag.CurveId),
|
||||
maxSignatures = decoder.decodeOptional(TlvTag.MaxSignatures),
|
||||
signingMethod = decoder.decodeOptional(TlvTag.SigningMethod),
|
||||
signingMethods = decoder.decodeOptional(TlvTag.SigningMethod),
|
||||
pauseBeforePin2 = decoder.decodeOptional(TlvTag.PauseBeforePin2),
|
||||
walletPublicKey = decoder.decodeOptional(TlvTag.WalletPublicKey),
|
||||
walletRemainingSignatures = decoder.decodeOptional(TlvTag.RemainingSignatures),
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class PersonalizeCommand(
|
|||
issuerPublicKey = decoder.decodeOptional(TlvTag.IssuerDataPublicKey),
|
||||
curve = decoder.decodeOptional(TlvTag.CurveId),
|
||||
maxSignatures = decoder.decodeOptional(TlvTag.MaxSignatures),
|
||||
signingMethod = decoder.decodeOptional(TlvTag.SigningMethod),
|
||||
signingMethods = decoder.decodeOptional(TlvTag.SigningMethod),
|
||||
pauseBeforePin2 = decoder.decodeOptional(TlvTag.PauseBeforePin2),
|
||||
walletPublicKey = decoder.decodeOptional(TlvTag.WalletPublicKey),
|
||||
walletRemainingSignatures = decoder.decodeOptional(TlvTag.RemainingSignatures),
|
||||
|
|
@ -105,7 +105,7 @@ class PersonalizeCommand(
|
|||
tlvBuilder.append(TlvTag.CardId, cardId)
|
||||
tlvBuilder.append(TlvTag.CurveId, config.curveID)
|
||||
tlvBuilder.append(TlvTag.MaxSignatures, config.maxSignatures)
|
||||
tlvBuilder.append(TlvTag.SigningMethod, config.signingMethod)
|
||||
tlvBuilder.append(TlvTag.SigningMethod, config.signingMethods)
|
||||
tlvBuilder.append(TlvTag.SettingsMask, config.createSettingsMask())
|
||||
tlvBuilder.append(TlvTag.PauseBeforePin2, config.pauseBeforePin2 / 10)
|
||||
tlvBuilder.append(TlvTag.Cvc, config.cvc.toByteArray())
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.commands.personalization.entities
|
|||
|
||||
import com.tangem.commands.CardData
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.commands.SigningMethod
|
||||
import com.tangem.commands.SigningMethodMask
|
||||
|
||||
data class NdefRecord(
|
||||
val type: Type,
|
||||
|
|
@ -33,7 +33,7 @@ data class CardConfig(
|
|||
val pauseBeforePin2: Int,
|
||||
val smartSecurityDelay: Boolean,
|
||||
val curveID: EllipticCurve,
|
||||
val signingMethod: SigningMethod,
|
||||
val signingMethods: SigningMethodMask,
|
||||
val maxSignatures: Int,
|
||||
val isReusable: Boolean,
|
||||
val allowSwapPin: Boolean,
|
||||
|
|
|
|||
|
|
@ -122,9 +122,9 @@ class TlvDecoder(val tlvList: List<Tlv>) {
|
|||
}
|
||||
}
|
||||
TlvValueType.SigningMethod -> {
|
||||
typeCheck<T, SigningMethod>(tag)
|
||||
typeCheck<T, SigningMethodMask>(tag)
|
||||
try {
|
||||
SigningMethod(tlvValue.toInt()) as T
|
||||
SigningMethodMask(tlvValue.toInt()) as T
|
||||
} catch (exception: Exception) {
|
||||
logException(tag, tlvValue.toInt().toString(), exception)
|
||||
throw SessionError.DecodingFailed()
|
||||
|
|
|
|||
|
|
@ -87,8 +87,8 @@ class TlvEncoder {
|
|||
(value as CardStatus).code.toByteArray()
|
||||
}
|
||||
TlvValueType.SigningMethod -> {
|
||||
typeCheck<T, SigningMethod>(tag)
|
||||
byteArrayOf((value as SigningMethod).rawValue.toByte())
|
||||
typeCheck<T, SigningMethodMask>(tag)
|
||||
byteArrayOf((value as SigningMethodMask).rawValue.toByte())
|
||||
}
|
||||
TlvValueType.IssuerDataMode -> {
|
||||
typeCheck<T, IssuerDataMode>(tag)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ internal class ScanTask : CardSessionRunnable<Card> {
|
|||
if (card == null) {
|
||||
callback(CompletionResult.Failure(SessionError.MissingPreflightRead()))
|
||||
|
||||
} else if (card.cardData?.productMask?.contains(ProductMask.tag) != false) {
|
||||
} else if (card.cardData?.productMask?.contains(Product.Tag) != false) {
|
||||
callback(CompletionResult.Success(card))
|
||||
|
||||
} else if (card.status != CardStatus.Loaded) {
|
||||
|
|
|
|||
|
|
@ -82,8 +82,8 @@ class TlvDecoderTest {
|
|||
|
||||
@Test
|
||||
fun `map SigningMethods single value returns correct value`() {
|
||||
val signingMethods: SigningMethod = tlvMapper.decode(TlvTag.SigningMethod)
|
||||
assertThat(signingMethods.contains(SigningMethod.signHash))
|
||||
val signingMethods: SigningMethodMask = tlvMapper.decode(TlvTag.SigningMethod)
|
||||
assertThat(signingMethods.contains(SigningMethod.SignHash))
|
||||
.isTrue()
|
||||
}
|
||||
|
||||
|
|
@ -91,20 +91,20 @@ class TlvDecoderTest {
|
|||
fun `map SigningMethods set of methods returns correct value`() {
|
||||
val localMapper = TlvDecoder(Tlv.deserialize("070195".hexToBytes())!!)
|
||||
|
||||
val signingMethod: SigningMethod = localMapper.decode(TlvTag.SigningMethod)
|
||||
assertThat(signingMethod.contains(SigningMethod.signHash))
|
||||
val signingMethods: SigningMethodMask = localMapper.decode(TlvTag.SigningMethod)
|
||||
assertThat(signingMethods.contains(SigningMethod.SignHash))
|
||||
.isTrue()
|
||||
assertThat(signingMethod.contains(SigningMethod.signHashValidatedByIssuer))
|
||||
assertThat(signingMethods.contains(SigningMethod.SignHashValidateByIssuer))
|
||||
.isTrue()
|
||||
assertThat(signingMethod.contains(SigningMethod.signHashValidatedByIssuerAndWriteIssuerData))
|
||||
assertThat(signingMethods.contains(SigningMethod.SignHashValidateByIssuerWriteIssuerData))
|
||||
.isTrue()
|
||||
assertThat(signingMethod.contains(SigningMethod.signRaw))
|
||||
assertThat(signingMethods.contains(SigningMethod.SignRaw))
|
||||
.isFalse()
|
||||
assertThat(signingMethod.contains(SigningMethod.signRawValidatedByIssuer))
|
||||
assertThat(signingMethods.contains(SigningMethod.SignRawValidateByIssuer))
|
||||
.isFalse()
|
||||
assertThat(signingMethod.contains(SigningMethod.signRawValidatedByIssuerAndWriteIssuerData))
|
||||
assertThat(signingMethods.contains(SigningMethod.SignRawValidateByIssuerWriteIssuerData))
|
||||
.isFalse()
|
||||
assertThat(signingMethod.contains(SigningMethod.signPos))
|
||||
assertThat(signingMethods.contains(SigningMethod.SignPos))
|
||||
.isFalse()
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ class TlvDecoderTest {
|
|||
fun `map ProductMask with raw value 5 returns correct value`() {
|
||||
val localMapper = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(5))))
|
||||
val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask)
|
||||
assertThat(productMask.contains(ProductMask.note) && productMask.contains(ProductMask.idCard))
|
||||
assertThat(productMask.contains(Product.Note) && productMask.contains(Product.IdCard))
|
||||
.isTrue()
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ class TlvDecoderTest {
|
|||
fun `map ProductMask with raw value 1 returns correct value`() {
|
||||
val localMapper = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(1))))
|
||||
val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask)
|
||||
assertThat(productMask.contains(ProductMask.note))
|
||||
assertThat(productMask.contains(Product.Note))
|
||||
.isTrue()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,10 +46,11 @@ dependencies {
|
|||
implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:2.2.1"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:2.2.1"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha01"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||
implementation "com.google.android.material:material:1.2.0-alpha05"
|
||||
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.github.gbIxaHue:eu4d:0.3.7'
|
||||
implementation 'com.github.gbIxaHue:eu4d:0.3.8'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ fun List<Item>.iterate(func: (Item) -> Unit) {
|
|||
forEach {
|
||||
when (it) {
|
||||
is BaseItem -> func(it)
|
||||
is ItemGroup -> it.itemList.iterate(func)
|
||||
is ItemGroup -> {
|
||||
func(it)
|
||||
it.itemList.iterate(func)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,12 @@ import com.tangem.tangemtest._arch.structure.Id
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Item {
|
||||
|
||||
interface UpdateBy<B>{
|
||||
fun update(value: B)
|
||||
}
|
||||
|
||||
interface Item: UpdateBy<Item> {
|
||||
val id: Id
|
||||
var parent: Item?
|
||||
var viewModel: ItemViewModel
|
||||
|
|
@ -36,4 +41,7 @@ open class BaseItem(
|
|||
|
||||
override var parent: Item? = null
|
||||
|
||||
override fun update(value: Item) {
|
||||
viewModel.update(value.viewModel)
|
||||
}
|
||||
}
|
||||
|
|
@ -49,4 +49,8 @@ open class SimpleItemGroup(
|
|||
ILog.d(this, "clear $id")
|
||||
itemList.clear()
|
||||
}
|
||||
|
||||
override fun update(value: Item) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ class KeyValue(val key: String, val value: Any)
|
|||
class ViewState(
|
||||
isVisible: Boolean? = null,
|
||||
bgColor: Int? = -1
|
||||
) {
|
||||
) : UpdateBy<ViewState> {
|
||||
|
||||
class State<T>(
|
||||
stateValue: T,
|
||||
|
|
@ -41,9 +41,15 @@ class ViewState(
|
|||
val states = listOf(isVisibleState, backgroundColor, descriptionVisibility)
|
||||
states.forEach { it.preventSameChanges = isPrevented }
|
||||
}
|
||||
|
||||
override fun update(value: ViewState) {
|
||||
// isVisibleState.update(value.isVisibleState)
|
||||
// backgroundColor.update(value.backgroundColor)
|
||||
// descriptionVisibility.update(value.descriptionVisibility)
|
||||
}
|
||||
}
|
||||
|
||||
interface ItemViewModel : PayloadHolder {
|
||||
interface ItemViewModel : PayloadHolder, UpdateBy<ItemViewModel> {
|
||||
val viewState: ViewState
|
||||
var data: Any?
|
||||
var defaultData: Any?
|
||||
|
|
@ -92,6 +98,14 @@ open class BaseItemViewModel(
|
|||
this.data = data
|
||||
onDataUpdated = callback
|
||||
}
|
||||
|
||||
override fun update(value: ItemViewModel) {
|
||||
viewState.update(value.viewState)
|
||||
defaultData = value.defaultData
|
||||
data = value.data
|
||||
payload.clear()
|
||||
payload.putAll(value.payload)
|
||||
}
|
||||
}
|
||||
|
||||
class ListViewModel(
|
||||
|
|
|
|||
|
|
@ -11,29 +11,29 @@ open class TypedItem<D>(id: Id, viewModel: ItemViewModel) : BaseItem(id, viewMod
|
|||
open fun getTypedData(): D? = viewModel.data as? D
|
||||
}
|
||||
|
||||
class TextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
|
||||
open class TextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
|
||||
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
|
||||
: this(id, BaseItemViewModel(value, viewState))
|
||||
}
|
||||
|
||||
class NumberItem(id: Id, viewModel: ItemViewModel) : TypedItem<Number>(id, viewModel) {
|
||||
open class NumberItem(id: Id, viewModel: ItemViewModel) : TypedItem<Number>(id, viewModel) {
|
||||
constructor(id: Id, value: Number? = null, viewState: ViewState = ViewState())
|
||||
: this(id, BaseItemViewModel(value, viewState))
|
||||
}
|
||||
|
||||
class BoolItem(id: Id, viewModel: ItemViewModel) : TypedItem<Boolean>(id, viewModel) {
|
||||
open class BoolItem(id: Id, viewModel: ItemViewModel) : TypedItem<Boolean>(id, viewModel) {
|
||||
constructor(id: Id, value: Boolean? = null, viewState: ViewState = ViewState())
|
||||
: this(id, BaseItemViewModel(value, viewState))
|
||||
}
|
||||
|
||||
|
||||
class EditTextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
|
||||
open class EditTextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
|
||||
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
|
||||
: this(id, BaseItemViewModel(value, viewState))
|
||||
}
|
||||
|
||||
|
||||
class SpinnerItem(id: Id, viewModel: ListViewModel) : TypedItem<ListViewModel>(id, viewModel) {
|
||||
open class SpinnerItem(id: Id, viewModel: ListViewModel) : TypedItem<ListViewModel>(id, viewModel) {
|
||||
constructor(id: Id, list: List<KeyValue>, selectedValue: Any?, viewState: ViewState = ViewState())
|
||||
: this(id, ListViewModel(list, selectedValue, viewState))
|
||||
}
|
||||
|
|
@ -3,9 +3,9 @@ package com.tangem.tangemtest._main
|
|||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
|
|
@ -20,7 +20,7 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
|||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private val vm: MainViewModel by viewModels<MainViewModel>()
|
||||
private val mainVM: MainViewModel by viewModels<MainViewModel>()
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
@ -52,11 +52,24 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.main_menu, menu)
|
||||
val switchMenu = menu.findItem(R.id.action_favorite)
|
||||
(switchMenu.actionView as? SwitchCompat)?.let {
|
||||
it.setOnCheckedChangeListener { buttonView, isChecked -> vm.switchToggled(isChecked) }
|
||||
menuInflater.inflate(R.menu.menu_activity_main, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
val switchItem = menu.findItem(R.id.action_toggle_description_visibility)
|
||||
switchItem.isChecked = mainVM.descriptionSwitchState
|
||||
return super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val result = when (item.itemId) {
|
||||
R.id.action_toggle_description_visibility -> {
|
||||
item.isChecked = !item.isChecked
|
||||
mainVM.switchToggled(item.isChecked)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
return if (result == null) super.onOptionsItemSelected(item) else true
|
||||
}
|
||||
}
|
||||
|
|
@ -10,10 +10,12 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
|||
*/
|
||||
class MainViewModel : ViewModel() {
|
||||
val ldDescriptionSwitch = MutableLiveData<Boolean>(false)
|
||||
var descriptionSwitchState = false
|
||||
|
||||
var commandResponse: CommandResponse? = null
|
||||
|
||||
fun switchToggled(state: Boolean) {
|
||||
descriptionSwitchState = state
|
||||
ldDescriptionSwitch.postValue(state)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,14 @@ package com.tangem.tangemtest._main.entryPoint
|
|||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._main.MainViewModel
|
||||
import com.tangem.tangemtest.extensions.view.beginDelayedTransition
|
||||
import com.tangem.tangemtest.ucase.getDefaultNavigationOptions
|
||||
import com.tangem.tangemtest.ucase.resources.ActionType
|
||||
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
|
||||
|
|
@ -47,7 +45,7 @@ class ActionListFragment : BaseFragment() {
|
|||
}
|
||||
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
|
||||
vhDataWrapper.descriptionIsVisible = it
|
||||
TransitionManager.beginDelayedTransition(rvActions as ViewGroup, AutoTransition())
|
||||
rvActions.beginDelayedTransition()
|
||||
rvActions.adapter?.notifyDataSetChanged()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,8 +59,6 @@ class RvActionsVH(
|
|||
|
||||
containerDescription.visibility = if (wrapper.descriptionIsVisible) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun onDataBound(data: ActionType) {}
|
||||
}
|
||||
|
||||
fun RecyclerView.ViewHolder.getString(@StringRes id: Int?, ifNull: String = ""): String {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@ package com.tangem.tangemtest.commons
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Store<M> {
|
||||
fun save(config: M)
|
||||
fun save(value: M)
|
||||
fun restore(): M
|
||||
}
|
||||
|
||||
interface KeyedStore<M> {
|
||||
fun save(key: String, value: M)
|
||||
fun restore(key: String): M
|
||||
fun restoreAll(): MutableMap<String, M>
|
||||
fun delete(key: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.tangem.tangemtest.commons
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
|
||||
class DialogController {
|
||||
var onDismissCallback: (() -> Unit)? = null
|
||||
var onShowCallback: (() -> Unit)? = null
|
||||
var view: View? = null
|
||||
|
||||
private var rawDialog: Dialog? = null
|
||||
|
||||
private var inShowingProcess = false
|
||||
private var inDismissingProcess = false
|
||||
private var autoReleaseOnDismiss = false
|
||||
|
||||
fun createAlert(context: Activity, resLayout: Int): AlertDialog {
|
||||
view = LayoutInflater.from(context).inflate(resLayout, null)
|
||||
rawDialog = AlertDialog.Builder(context).setView(view).create().apply {
|
||||
setOnShowListener { onShow() }
|
||||
setOnDismissListener { onDismiss() }
|
||||
}
|
||||
return rawDialog as AlertDialog
|
||||
}
|
||||
|
||||
fun set(dialog: Dialog) {
|
||||
rawDialog = dialog
|
||||
dialog.setOnShowListener { onShow() }
|
||||
dialog.setOnDismissListener { onDismiss() }
|
||||
}
|
||||
|
||||
private fun onShow() {
|
||||
inShowingProcess = false
|
||||
onShowCallback?.invoke()
|
||||
}
|
||||
|
||||
private fun onDismiss() {
|
||||
inDismissingProcess = false
|
||||
if (autoReleaseOnDismiss) release()
|
||||
onDismissCallback?.invoke()
|
||||
}
|
||||
|
||||
fun show() {
|
||||
val dialog = rawDialog ?: return
|
||||
if (inShowingProcess) return
|
||||
if (dialog.isShowing) return
|
||||
|
||||
inShowingProcess = true
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
fun dismiss(autoRelease: Boolean = true) {
|
||||
val dialog = rawDialog ?: return
|
||||
if (inDismissingProcess) return
|
||||
if (!dialog.isShowing) return
|
||||
|
||||
autoReleaseOnDismiss = autoRelease
|
||||
inDismissingProcess = true
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
fun release() {
|
||||
onDismissCallback = null
|
||||
onShowCallback = null
|
||||
|
||||
inShowingProcess = false
|
||||
inDismissingProcess = false
|
||||
autoReleaseOnDismiss = false
|
||||
|
||||
view = null
|
||||
rawDialog = null
|
||||
}
|
||||
}
|
||||
|
|
@ -11,24 +11,38 @@ fun CardConfig.Companion.create(application: Application): CardConfig {
|
|||
|
||||
val preferences = application.getSharedPreferences("prefs", Context.MODE_PRIVATE)
|
||||
|
||||
val signingMethod = SigningMethod.build(
|
||||
signHash = preferences.getBoolean("personalization_SigningMethod_0", false),
|
||||
signRaw = preferences.getBoolean("personalization_SigningMethod_1", false),
|
||||
signHashValidatedByIssuer = preferences.getBoolean("personalization_SigningMethod_2", false),
|
||||
signRawValidatedByIssuer = preferences.getBoolean("personalization_SigningMethod_3", false),
|
||||
signHashValidatedByIssuerAndWriteIssuerData = preferences.getBoolean("personalization_SigningMethod_4", false),
|
||||
signRawValidatedByIssuerAndWriteIssuerData = preferences.getBoolean("personalization_SigningMethod_5", false),
|
||||
signPos = preferences.getBoolean("personalization_SigningMethod_6", false)
|
||||
)
|
||||
val signingMethodMaskBuilder = SigningMethodMaskBuilder()
|
||||
if (preferences.getBoolean("personalization_SigningMethod_0", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHash)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_1", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRaw)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_2", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuer)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_3", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuer)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_4", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuerWriteIssuerData)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_5", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuerWriteIssuerData)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_6", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHash)
|
||||
}
|
||||
val signingMethod = signingMethodMaskBuilder.build()
|
||||
|
||||
val isNote = preferences.getBoolean("personalization_ProductMask_IsNote", true)
|
||||
val isTag = preferences.getBoolean("personalization_ProductMask_IsTag", false)
|
||||
val isIdCard = preferences.getBoolean("personalization_ProductMask_IsIDCard", false)
|
||||
|
||||
val productMaskBuilder = ProductMaskBuilder()
|
||||
if (isNote) productMaskBuilder.add(ProductMask.note)
|
||||
if (isTag) productMaskBuilder.add(ProductMask.tag)
|
||||
if (isIdCard) productMaskBuilder.add(ProductMask.idCard)
|
||||
if (isNote) productMaskBuilder.add(Product.Note)
|
||||
if (isTag) productMaskBuilder.add(Product.Tag)
|
||||
if (isIdCard) productMaskBuilder.add(Product.IdCard)
|
||||
val productMask = productMaskBuilder.build()
|
||||
|
||||
var tokenSymbol: String? = null
|
||||
|
|
@ -80,7 +94,7 @@ fun CardConfig.Companion.create(application: Application): CardConfig {
|
|||
cardData = cardData,
|
||||
curveID = EllipticCurve.byName(preferences.getString("personalization_CurveId", "secp256k1")!!)
|
||||
?: EllipticCurve.Secp256k1,
|
||||
signingMethod = signingMethod,
|
||||
signingMethods = signingMethod,
|
||||
createWallet = preferences.getBoolean("personalization_CreateWallet", true),
|
||||
maxSignatures = preferences.getString("personalization_MaxSignatures", "1000")!!.toInt(),
|
||||
isReusable = preferences.getBoolean("personalization_SettingsMask_IsReusable", true),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tangemtest.extensions.view
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.Transition
|
||||
import androidx.transition.TransitionManager
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||
TransitionManager.beginDelayedTransition(this, transition)
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ class PersonalizeAction : BaseAction() {
|
|||
val acquirer = DefaultPersonalizationParams.acquirer()
|
||||
val manufacturer = DefaultPersonalizationParams.manufacturer()
|
||||
|
||||
val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig())
|
||||
val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig.default())
|
||||
val cardConfig = PersonalizationConfigToCardConfig().convert(personalizeConfig)
|
||||
|
||||
attrs.tangemSdk.personalize(cardConfig, issuer, manufacturer, acquirer) {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
|||
class SignAction : BaseAction() {
|
||||
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
|
||||
val dataForHashing = attrs.itemList.findItem(TlvId.TransactionOutHash) ?: return
|
||||
val hash = dataForHashing.getData() as? ByteArray ?: return
|
||||
val hash = dataForHashing.getData() as? String ?: return
|
||||
val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return
|
||||
|
||||
attrs.tangemSdk.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
|
||||
attrs.tangemSdk.sign(arrayOf(hash.toByteArray()), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
|
||||
}
|
||||
|
||||
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ interface ItemsManager : PayloadHolder {
|
|||
fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback)
|
||||
fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)?
|
||||
fun attachPayload(payload: Payload)
|
||||
fun updateByItemList(list: List<Item>)
|
||||
}
|
||||
|
||||
interface PayloadKey {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.tangemtest._arch.structure.Id
|
|||
import com.tangem.tangemtest._arch.structure.Payload
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.findItem
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.iterate
|
||||
import com.tangem.tangemtest.ucase.domain.actions.Action
|
||||
import com.tangem.tangemtest.ucase.domain.actions.AttrForAction
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
|
||||
|
|
@ -47,6 +48,10 @@ open class BaseItemsManager(protected val action: Action) : ItemsManager, Lifecy
|
|||
this.changeConsequence = consequence
|
||||
}
|
||||
|
||||
override fun updateByItemList(list: List<Item>) {
|
||||
list.iterate { itemList.findItem(it.id)?.update(it) }
|
||||
}
|
||||
|
||||
override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) {
|
||||
action.executeMainAction(this, getAttrsForAction(tangemSdk), callback)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class PersonalizationItemsManager(
|
|||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
fun onDestroy() {
|
||||
val config = converter.convert(itemList, PersonalizationConfig())
|
||||
val config = converter.convert(itemList, PersonalizationConfig.default())
|
||||
store.save(config)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.commands.Card
|
|||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.calculateSha512
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tangemtest._arch.structure.PayloadHolder
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.findItem
|
||||
|
|
@ -40,7 +41,7 @@ class SignScanConsequence : ItemsChangeConsequence {
|
|||
EllipticCurve.Ed25519 -> dataForHashing.calculateSha512()
|
||||
else -> return null
|
||||
}
|
||||
hashItem.setData(hashedData)
|
||||
hashItem.setData(hashedData.toHexString())
|
||||
}
|
||||
return affectedItems.toList()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tangemtest.ucase.domain.responses
|
|||
import com.google.gson.*
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tangemtest.extensions.print
|
||||
import java.lang.reflect.Type
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
|
@ -11,14 +12,17 @@ import java.util.*
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ResponseJsonConverter {
|
||||
|
||||
val gson: Gson by lazy { init() }
|
||||
|
||||
private val fieldConverter = ResponseFieldConverter()
|
||||
|
||||
private fun init(): Gson {
|
||||
val builder = GsonBuilder().apply {
|
||||
registerTypeAdapter(ByteArray::class.java, ByteTypeAdapter())
|
||||
registerTypeAdapter(SigningMethod::class.java, SigningMethodTypeAdapter())
|
||||
registerTypeAdapter(SettingsMask::class.java, SettingsMaskTypeAdapter())
|
||||
registerTypeAdapter(ProductMask::class.java, ProductMaskTypeAdapter())
|
||||
registerTypeAdapter(ByteArray::class.java, ByteTypeAdapter(fieldConverter))
|
||||
registerTypeAdapter(SigningMethodMask::class.java, SigningMethodTypeAdapter(fieldConverter))
|
||||
registerTypeAdapter(SettingsMask::class.java, SettingsMaskTypeAdapter(fieldConverter))
|
||||
registerTypeAdapter(ProductMask::class.java, ProductMaskTypeAdapter(fieldConverter))
|
||||
registerTypeAdapter(Date::class.java, DateTypeAdapter())
|
||||
}
|
||||
builder.setPrettyPrinting()
|
||||
|
|
@ -28,31 +32,41 @@ class ResponseJsonConverter {
|
|||
fun convertResponse(response: CommandResponse?): String = gson.toJson(response)
|
||||
}
|
||||
|
||||
class ByteTypeAdapter : JsonSerializer<ByteArray> {
|
||||
class ByteTypeAdapter(
|
||||
private val fieldConverter: ResponseFieldConverter
|
||||
) : JsonSerializer<ByteArray> {
|
||||
override fun serialize(src: ByteArray, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
return JsonPrimitive(src.toHexString())
|
||||
return JsonPrimitive(fieldConverter.byteArray(src))
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsMaskTypeAdapter : JsonSerializer<SettingsMask> {
|
||||
class SettingsMaskTypeAdapter(
|
||||
private val fieldConverter: ResponseFieldConverter
|
||||
) : JsonSerializer<SettingsMask> {
|
||||
override fun serialize(src: SettingsMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
val arrayElement = JsonArray()
|
||||
Settings.values()
|
||||
.filter { src.contains(it) }
|
||||
.forEach { arrayElement.add(it.name) }
|
||||
return arrayElement
|
||||
return JsonArray().apply {
|
||||
fieldConverter.settingsMaskList(src).forEach { add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ProductMaskTypeAdapter : JsonSerializer<ProductMask> {
|
||||
class ProductMaskTypeAdapter(
|
||||
private val fieldConverter: ResponseFieldConverter
|
||||
) : JsonSerializer<ProductMask> {
|
||||
override fun serialize(src: ProductMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
return JsonPrimitive(src.rawValue.toString())
|
||||
return JsonArray().apply {
|
||||
fieldConverter.productMaskList(src).forEach { add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SigningMethodTypeAdapter : JsonSerializer<SigningMethod> {
|
||||
override fun serialize(src: SigningMethod, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
return JsonPrimitive(src.rawValue.toString())
|
||||
class SigningMethodTypeAdapter(
|
||||
private val fieldConverter: ResponseFieldConverter
|
||||
) : JsonSerializer<SigningMethodMask> {
|
||||
override fun serialize(src: SigningMethodMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
return JsonArray().apply {
|
||||
fieldConverter.signingMethodList(src).forEach { add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,4 +75,41 @@ class DateTypeAdapter : JsonSerializer<Date> {
|
|||
val formatter = DateFormat.getDateInstance(DateFormat.DEFAULT, Locale("en_US"))
|
||||
return JsonPrimitive(formatter.format(src).toString())
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseFieldConverter {
|
||||
|
||||
fun productMask(productMask: ProductMask?): String {
|
||||
return productMaskList(productMask).print(wrap = false)
|
||||
}
|
||||
|
||||
fun productMaskList(productMask: ProductMask?): List<String> {
|
||||
val mask = productMask ?: return emptyList()
|
||||
|
||||
return Product.values().filter { mask.contains(it) }.map { it.name }
|
||||
}
|
||||
|
||||
fun signingMethod(signingMask: SigningMethodMask?): String {
|
||||
return signingMethodList(signingMask).print(wrap = false)
|
||||
}
|
||||
|
||||
fun signingMethodList(signingMask: SigningMethodMask?): List<String> {
|
||||
val mask = signingMask ?: return emptyList()
|
||||
|
||||
return SigningMethod.values().filter { mask.contains(it) }.map { it.name }
|
||||
}
|
||||
|
||||
fun settingsMask(settingsMask: SettingsMask?): String {
|
||||
return settingsMaskList(settingsMask).print(wrap = false)
|
||||
}
|
||||
|
||||
fun settingsMaskList(settingsMask: SettingsMask?): List<String> {
|
||||
val masks = settingsMask ?: return emptyList()
|
||||
|
||||
return Settings.values().filter { masks.contains(it) }.map { it.name }
|
||||
}
|
||||
|
||||
fun byteArray(byteArray: ByteArray?): String? {
|
||||
return byteArray?.toHexString()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ import com.tangem.tangemtest.ucase.resources.MainResourceHolder
|
|||
import com.tangem.tangemtest.ucase.resources.Resources
|
||||
import com.tangem.tangemtest.ucase.variants.responses.CardDataId
|
||||
import com.tangem.tangemtest.ucase.variants.responses.CardId
|
||||
import com.tangem.tangemtest.ucase.variants.responses.DepersonalizeId
|
||||
import com.tangem.tangemtest.ucase.variants.responses.SignId
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -13,6 +15,8 @@ class ResponseResources {
|
|||
fun init(holder: MainResourceHolder) {
|
||||
initCard(holder)
|
||||
initCardData(holder)
|
||||
initSignResponse(holder)
|
||||
initDepersonalizeResponse(holder)
|
||||
}
|
||||
|
||||
private fun initCard(holder: MainResourceHolder) {
|
||||
|
|
@ -26,7 +30,7 @@ class ResponseResources {
|
|||
holder.register(CardId.issuerPublicKey, Resources(R.string.response_card_issuer_data_public_key, R.string.info_response_card_issuer_data_public_key))
|
||||
holder.register(CardId.curve, Resources(R.string.response_card_curve, R.string.info_response_card_curve))
|
||||
holder.register(CardId.maxSignatures, Resources(R.string.response_card_max_signatures, R.string.info_response_card_max_signatures))
|
||||
holder.register(CardId.signingMethod, Resources(R.string.response_card_signing_method, R.string.response_card_signing_method))
|
||||
holder.register(CardId.signingMethod, Resources(R.string.response_card_signing_method, R.string.info_response_card_signing_method))
|
||||
holder.register(CardId.pauseBeforePin2, Resources(R.string.response_card_pause_before_pin2, R.string.info_response_card_allow_pin2))
|
||||
holder.register(CardId.walletPublicKey, Resources(R.string.response_card_wallet_public_key, R.string.info_response_card_wallet_public_key))
|
||||
holder.register(CardId.walletRemainingSignatures, Resources(R.string.response_card_wallet_remaining_signatures, R.string.info_response_card_wallet_remaining_signatures))
|
||||
|
|
@ -50,4 +54,15 @@ class ResponseResources {
|
|||
holder.register(CardDataId.tokenContractAddress, Resources(R.string.response_card_card_data_token_contract_address, R.string.info_response_card_card_data_token_contract_address))
|
||||
holder.register(CardDataId.tokenDecimal, Resources(R.string.response_card_card_data_token_decimal, R.string.info_response_card_card_data_token_decimal))
|
||||
}
|
||||
|
||||
private fun initSignResponse(holder: MainResourceHolder) {
|
||||
holder.register(SignId.cid, Resources(R.string.response_sign_cid, R.string.info_response_sign_cid))
|
||||
holder.register(SignId.walletSignedHashes, Resources(R.string.response_sign_wallet_signed_hashes, R.string.info_response_sign_wallet_signed_hashes))
|
||||
holder.register(SignId.walletRemainingSignatures, Resources(R.string.response_sign_wallet_remaining_signatures, R.string.info_response_sign_wallet_remaining_signatures))
|
||||
holder.register(SignId.signature, Resources(R.string.response_sign_signature, R.string.info_response_sign_signature))
|
||||
}
|
||||
|
||||
private fun initDepersonalizeResponse(holder: MainResourceHolder) {
|
||||
holder.register(DepersonalizeId.isSuccess, Resources(R.string.response_depersonalize_is_success, R.string.info_response_depersonalize_is_success))
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,11 @@ import androidx.core.view.plusAssign
|
|||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
|
|
@ -28,7 +30,8 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
|||
*/
|
||||
abstract class BaseCardActionFragment : BaseFragment(), ActionView {
|
||||
|
||||
protected lateinit var itemContainer: ViewGroup
|
||||
protected lateinit var swrLayout: SwipeRefreshLayout
|
||||
protected lateinit var contentContainer: ViewGroup
|
||||
protected lateinit var actionFab: FloatingActionButton
|
||||
|
||||
protected abstract val itemsManager: ItemsManager
|
||||
|
|
@ -38,6 +41,8 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
|
|||
|
||||
private val paramsWidgetList = mutableListOf<ParameterWidget>()
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_base_action_layout
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
Log.d(this, "onViewCreated")
|
||||
|
|
@ -47,7 +52,7 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
|
|||
actionVM.setCardManager(TangemSdk.init(requireActivity()))
|
||||
actionVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView))
|
||||
|
||||
initFab()
|
||||
initViews()
|
||||
createWidgets {
|
||||
widgetsWasCreated()
|
||||
subscribeToViewModelChanges()
|
||||
|
|
@ -57,11 +62,13 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
|
|||
protected open fun widgetsWasCreated() {}
|
||||
|
||||
protected open fun bindViews() {
|
||||
itemContainer = mainView.findViewById(R.id.ll_container)
|
||||
swrLayout = mainView.findViewById(R.id.swr_layout)
|
||||
contentContainer = mainView.findViewById(R.id.ll_content_container)
|
||||
actionFab = mainView.findViewById(R.id.fab_action)
|
||||
}
|
||||
|
||||
protected open fun initFab() {
|
||||
protected open fun initViews() {
|
||||
swrLayout.isEnabled = false
|
||||
enableActionFab(false)
|
||||
actionFab.setOnClickListener { actionVM.invokeMainAction() }
|
||||
}
|
||||
|
|
@ -70,7 +77,7 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
|
|||
Log.d(this, "createWidgets")
|
||||
actionVM.ldItemList.observe(viewLifecycleOwner, Observer { itemList ->
|
||||
itemList.forEach { param ->
|
||||
val widget = ParameterWidget(inflateParamView(itemContainer), param)
|
||||
val widget = ParameterWidget(inflateParamView(contentContainer), param)
|
||||
widget.onValueChanged = { id, value -> actionVM.userChangedItem(id, value) }
|
||||
widget.onActionBtnClickListener = actionVM.getItemAction(param.id)
|
||||
paramsWidgetList.add(widget)
|
||||
|
|
@ -81,39 +88,46 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
|
|||
|
||||
protected open fun subscribeToViewModelChanges() {
|
||||
Log.d(this, "subscribeToViewModelChanges")
|
||||
listenResponse()
|
||||
listenResponseData()
|
||||
listenResponseCardData()
|
||||
listenError()
|
||||
listenChangedItems()
|
||||
listenDescriptionSwitchChanges()
|
||||
}
|
||||
|
||||
private fun listenResponse() {
|
||||
actionVM.seResponse.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "listen response: $it")
|
||||
mainActivityVM.changeResponseEvent(it)
|
||||
Log.d(this, "handle response: $it")
|
||||
handleResponse(it)
|
||||
})
|
||||
}
|
||||
|
||||
protected open fun listenResponseData() {
|
||||
actionVM.seResponseData.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "listen responseData: $it")
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen)
|
||||
Log.d(this, "handle responseData: $it")
|
||||
handleResponseData(it)
|
||||
})
|
||||
}
|
||||
|
||||
protected open fun listenResponseCardData() {
|
||||
actionVM.seResponseCardData.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "listen responseCardData: $it")
|
||||
responseCardDataHandled(it)
|
||||
Log.d(this, "handle responseCardData: $it")
|
||||
handleResponseCardData(it)
|
||||
})
|
||||
actionVM.seError.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "handle error: $it")
|
||||
handleError(it)
|
||||
})
|
||||
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "handle descriptionVisibilityState: $it")
|
||||
handleDescriptionSwitchChanges(it)
|
||||
})
|
||||
listenChangedItems()
|
||||
}
|
||||
|
||||
protected open fun responseCardDataHandled(card: Card?) {}
|
||||
protected open fun handleResponse(response: CommandResponse) {
|
||||
mainActivityVM.changeResponseEvent(response)
|
||||
}
|
||||
|
||||
protected open fun listenError() {
|
||||
actionVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) })
|
||||
protected open fun handleResponseData(response: CommandResponse) {
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen, options = null)
|
||||
}
|
||||
|
||||
protected open fun handleResponseCardData(card: Card) {}
|
||||
|
||||
protected open fun handleError(error: String) {
|
||||
showSnackbar(error)
|
||||
}
|
||||
|
||||
protected open fun handleDescriptionSwitchChanges(descriptionVisibilityState: Boolean) {
|
||||
actionVM.toggleDescriptionVisibility(descriptionVisibilityState)
|
||||
paramsWidgetList.forEach { it.toggleDescriptionVisibility(descriptionVisibilityState) }
|
||||
}
|
||||
|
||||
@Deprecated("Start to use itemViewModel")
|
||||
|
|
@ -126,12 +140,6 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
|
|||
})
|
||||
}
|
||||
|
||||
protected open fun listenDescriptionSwitchChanges() {
|
||||
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
|
||||
actionVM.toggleDescriptionVisibility(it)
|
||||
})
|
||||
}
|
||||
|
||||
private fun inflateParamView(where: ViewGroup): ViewGroup {
|
||||
val inflater = LayoutInflater.from(where.context)
|
||||
val view = inflater.inflate(R.layout.w_card_incoming_param, where, false)
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.impl.EditTextItem
|
||||
import com.tangem.tangemtest.extensions.view.beginDelayedTransition
|
||||
import com.tangem.tangemtest.ucase.resources.ActionType
|
||||
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
|
||||
import com.tangem.tangemtest.ucase.resources.Resources
|
||||
|
|
@ -73,7 +72,7 @@ class ParameterWidget(
|
|||
}
|
||||
|
||||
fun toggleDescriptionVisibility(state: Boolean) {
|
||||
TransitionManager.beginDelayedTransition(parent.parent as ViewGroup, AutoTransition())
|
||||
(parent.parent as ViewGroup).beginDelayedTransition()
|
||||
descriptionContainer.visibility = if (state) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +94,7 @@ class ParameterWidget(
|
|||
private fun toggleActionBtnVisibility() {
|
||||
fun switchVisibilityState(newState: Int) {
|
||||
actionBtnVisibilityState = newState
|
||||
TransitionManager.beginDelayedTransition(parent, AutoTransition())
|
||||
parent.beginDelayedTransition()
|
||||
btnAction.visibility = actionBtnVisibilityState
|
||||
}
|
||||
when {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tangemtest.ucase.variants.depersonalize.ui
|
||||
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.DepersonalizeItemsManager
|
||||
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
|
||||
|
|
@ -11,6 +10,4 @@ import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
|
|||
class DepersonalizeActionFragment : BaseCardActionFragment() {
|
||||
|
||||
override val itemsManager: ItemsManager by lazy { DepersonalizeItemsManager() }
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_depersonalize
|
||||
}
|
||||
|
|
@ -5,22 +5,49 @@ import android.content.SharedPreferences
|
|||
import androidx.core.content.edit
|
||||
import com.google.gson.Gson
|
||||
import com.tangem.tangemtest.AppTangemDemo
|
||||
import com.tangem.tangemtest.commons.KeyedStore
|
||||
import com.tangem.tangemtest.commons.Store
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig
|
||||
|
||||
class PersonalizationConfigStore(context: Context) : Store<PersonalizationConfig> {
|
||||
class PersonalizationConfigStore(context: Context) : Store<PersonalizationConfig>, KeyedStore<PersonalizationConfig> {
|
||||
|
||||
private val key = "personalization_config"
|
||||
companion object {
|
||||
val defaultKey = "default"
|
||||
}
|
||||
|
||||
private val sp: SharedPreferences = (context.applicationContext as AppTangemDemo).sharedPreferences()
|
||||
private val sharedPreferencesKey = "personalization_presets"
|
||||
|
||||
private val sp: SharedPreferences = (context.applicationContext as AppTangemDemo).sharedPreferences(sharedPreferencesKey)
|
||||
private val gson: Gson = Gson()
|
||||
|
||||
override fun save(config: PersonalizationConfig) {
|
||||
sp.edit(true) { putString(key, gson.toJson(config)) }
|
||||
override fun save(value: PersonalizationConfig) {
|
||||
save(defaultKey, value)
|
||||
}
|
||||
|
||||
override fun restore(): PersonalizationConfig {
|
||||
val json = sp.getString(key, gson.toJson(PersonalizationConfig()))
|
||||
return gson.fromJson(json, PersonalizationConfig::class.java)
|
||||
override fun restore(): PersonalizationConfig = restore(defaultKey)
|
||||
|
||||
override fun save(key: String, value: PersonalizationConfig) {
|
||||
sp.edit(true) { putString(key, toJson(value)) }
|
||||
}
|
||||
|
||||
override fun restore(key: String): PersonalizationConfig {
|
||||
val json = sp.getString(key, toJson(getDefaultConfig()))
|
||||
return fromJson(json!!)
|
||||
}
|
||||
|
||||
override fun restoreAll(): MutableMap<String, PersonalizationConfig> {
|
||||
val map = mutableMapOf<String, PersonalizationConfig>()
|
||||
sp.all.forEach { map[it.key] = fromJson(it.value as String) }
|
||||
return map.toSortedMap()
|
||||
}
|
||||
|
||||
override fun delete(key: String) {
|
||||
sp.edit(true) { remove(key) }
|
||||
}
|
||||
|
||||
private fun getDefaultConfig(): PersonalizationConfig = PersonalizationConfig.default()
|
||||
|
||||
private fun toJson(value: PersonalizationConfig): String = gson.toJson(value)
|
||||
|
||||
private fun fromJson(json: String): PersonalizationConfig = gson.fromJson(json, PersonalizationConfig::class.java)
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ class ConfigValuesHolder : BaseTypedHolder<Id, Value>() {
|
|||
register(CardNumber.BatchId, Value(default.batchId))
|
||||
register(Common.Curve, Value(default.curveID, Helper.listOfCurves()))
|
||||
register(Common.Blockchain, Value(default.blockchain, Helper.listOfBlockchain()))
|
||||
register(Common.BlockchainCustom, Value(""))
|
||||
register(Common.BlockchainCustom, Value(default.blockchainCustom))
|
||||
register(Common.MaxSignatures, Value(default.MaxSignatures))
|
||||
register(Common.CreateWallet, Value(default.createWallet))
|
||||
register(SigningMethod.SignTx, Value(default.SigningMethod0))
|
||||
|
|
@ -66,7 +66,7 @@ class ConfigValuesHolder : BaseTypedHolder<Id, Value>() {
|
|||
register(SettingsMaskNdef.DynamicNdef, Value(default.useDynamicNDEF))
|
||||
register(SettingsMaskNdef.DisablePrecomputedNdef, Value(default.disablePrecomputedNDEF))
|
||||
register(SettingsMaskNdef.Aar, Value(default.aar, Helper.aarList()))
|
||||
register(SettingsMaskNdef.AarCustom, Value(default.aar))
|
||||
register(SettingsMaskNdef.AarCustom, Value(default.aarCustom))
|
||||
register(SettingsMaskNdef.Uri, Value(default.uri))
|
||||
register(Pins.Pin, Value(default.PIN))
|
||||
register(Pins.Pin2, Value(default.PIN2))
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class ItemsToPersonalizationConfig : ItemsToModel<PersonalizationConfig> {
|
|||
val export = PersonalizationConfig()
|
||||
export.series = getTyped(CardNumber.Series)
|
||||
export.startNumber = getTyped(CardNumber.Number)
|
||||
export.batchId = getTyped(CardNumber.BatchId)
|
||||
export.curveID = getTyped(Common.Curve)
|
||||
export.blockchain = getTyped(Common.Blockchain)
|
||||
export.blockchainCustom = getTyped(Common.BlockchainCustom)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.converter
|
||||
|
||||
import com.tangem.commands.CardData
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.commands.ProductMaskBuilder
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.personalization.entities.CardConfig
|
||||
import com.tangem.commands.personalization.entities.NdefRecord
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig
|
||||
|
|
@ -12,15 +10,29 @@ import java.util.*
|
|||
class PersonalizationConfigToCardConfig : Converter<PersonalizationConfig, CardConfig> {
|
||||
|
||||
override fun convert(from: PersonalizationConfig): CardConfig {
|
||||
val signingMethod = com.tangem.commands.SigningMethod.build(
|
||||
signHash = from.SigningMethod0,
|
||||
signRaw = from.SigningMethod1,
|
||||
signHashValidatedByIssuer = from.SigningMethod2,
|
||||
signRawValidatedByIssuer = from.SigningMethod3,
|
||||
signHashValidatedByIssuerAndWriteIssuerData = from.SigningMethod4,
|
||||
signRawValidatedByIssuerAndWriteIssuerData = from.SigningMethod5,
|
||||
signPos = from.SigningMethod6
|
||||
)
|
||||
val signingMethodMaskBuilder = SigningMethodMaskBuilder()
|
||||
if (from.SigningMethod0) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHash)
|
||||
}
|
||||
if (from.SigningMethod1) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRaw)
|
||||
}
|
||||
if (from.SigningMethod2) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuer)
|
||||
}
|
||||
if (from.SigningMethod3) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuer)
|
||||
}
|
||||
if (from.SigningMethod4) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuerWriteIssuerData)
|
||||
}
|
||||
if (from.SigningMethod5) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuerWriteIssuerData)
|
||||
}
|
||||
if (from.SigningMethod6) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHash)
|
||||
}
|
||||
val signingMethod = signingMethodMaskBuilder.build()
|
||||
|
||||
val isNote = from.cardData.product_note
|
||||
val isTag = from.cardData.product_tag
|
||||
|
|
@ -28,10 +40,10 @@ class PersonalizationConfigToCardConfig : Converter<PersonalizationConfig, CardC
|
|||
val isIdIssuer = from.cardData.product_id_issuer
|
||||
|
||||
val productMaskBuilder = ProductMaskBuilder()
|
||||
if (isNote) productMaskBuilder.add(com.tangem.commands.ProductMask.note)
|
||||
if (isTag) productMaskBuilder.add(com.tangem.commands.ProductMask.tag)
|
||||
if (isIdCard) productMaskBuilder.add(com.tangem.commands.ProductMask.idCard)
|
||||
if (isIdIssuer) productMaskBuilder.add(com.tangem.commands.ProductMask.idIssuer)
|
||||
if (isNote) productMaskBuilder.add(com.tangem.commands.Product.Note)
|
||||
if (isTag) productMaskBuilder.add(com.tangem.commands.Product.Tag)
|
||||
if (isIdCard) productMaskBuilder.add(com.tangem.commands.Product.IdCard)
|
||||
if (isIdIssuer) productMaskBuilder.add(com.tangem.commands.Product.IdIssuer)
|
||||
val productMask = productMaskBuilder.build()
|
||||
|
||||
var tokenSymbol: String? = null
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.dto
|
||||
|
||||
import com.tangem.commands.EllipticCurve
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PersonalizationConfig {
|
||||
|
||||
// Card number
|
||||
var series = "BB"
|
||||
var startNumber: Long = 300000000000
|
||||
var batchId = "ffff"
|
||||
|
||||
var series = ""
|
||||
var startNumber: Long = 0
|
||||
var batchId = ""
|
||||
|
||||
// Common
|
||||
var curveID = "ed25519"
|
||||
var blockchain = "BTC/test"
|
||||
var curveID = ""
|
||||
var blockchain = ""
|
||||
var blockchainCustom = ""
|
||||
var MaxSignatures: Long = 999999
|
||||
var createWallet = true
|
||||
var MaxSignatures: Long = 0
|
||||
var createWallet = false
|
||||
|
||||
// Signing method
|
||||
var SigningMethod0 = true
|
||||
var SigningMethod0 = false
|
||||
var SigningMethod1 = false
|
||||
var SigningMethod2 = false
|
||||
var SigningMethod3 = false
|
||||
|
|
@ -27,19 +28,16 @@ class PersonalizationConfig {
|
|||
var SigningMethod5 = false
|
||||
var SigningMethod6 = false
|
||||
|
||||
|
||||
// Sign hash external properties
|
||||
var pinLessFloorLimit: Long = 100000
|
||||
var hexCrExKey = "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"
|
||||
var pinLessFloorLimit: Long = 0
|
||||
var hexCrExKey = ""
|
||||
var requireTerminalTxSignature = false
|
||||
var requireTerminalCertSignature = false
|
||||
var checkPIN3onCard = true
|
||||
|
||||
var checkPIN3onCard = false
|
||||
|
||||
// Denomination
|
||||
var writeOnPersonalization = false
|
||||
var denomination: Long = 1000000
|
||||
|
||||
var denomination: Long = 0
|
||||
|
||||
// Token
|
||||
var itsToken = false
|
||||
|
|
@ -47,49 +45,125 @@ class PersonalizationConfig {
|
|||
var contractAddress = ""
|
||||
var decimal: Long = 0
|
||||
|
||||
|
||||
var cardData = CardData()
|
||||
|
||||
|
||||
// Settings mask
|
||||
var isReusable = true
|
||||
var isReusable = false
|
||||
var useActivation = false
|
||||
var forbidPurgeWallet = false
|
||||
var allowSelectBlockchain = false
|
||||
var useBlock = false
|
||||
var oneApdu = false
|
||||
var useCVC = false
|
||||
var allowSwapPIN = true
|
||||
var allowSwapPIN2 = true
|
||||
var allowSwapPIN = false
|
||||
var allowSwapPIN2 = false
|
||||
var forbidDefaultPIN = false
|
||||
var smartSecurityDelay = true
|
||||
var protectIssuerDataAgainstReplay = true
|
||||
var skipSecurityDelayIfValidatedByIssuer = true
|
||||
var skipCheckPIN2andCVCIfValidatedByIssuer = true
|
||||
var skipSecurityDelayIfValidatedByLinkedTerminal = true
|
||||
var smartSecurityDelay = false
|
||||
var protectIssuerDataAgainstReplay = false
|
||||
var skipSecurityDelayIfValidatedByIssuer = false
|
||||
var skipCheckPIN2andCVCIfValidatedByIssuer = false
|
||||
var skipSecurityDelayIfValidatedByLinkedTerminal = false
|
||||
var restrictOverwriteIssuerDataEx = false
|
||||
|
||||
|
||||
// Settings mask - protocol encryption
|
||||
var protocolAllowUnencrypted = true
|
||||
var protocolAllowStaticEncryption = true
|
||||
var protocolAllowUnencrypted = false
|
||||
var protocolAllowStaticEncryption = false
|
||||
|
||||
|
||||
var useNDEF = true
|
||||
var useDynamicNDEF = true
|
||||
var useNDEF = false
|
||||
var useDynamicNDEF = false
|
||||
var disablePrecomputedNDEF = false
|
||||
var aar = "com.tangem.wallet"
|
||||
var aar = ""
|
||||
var aarCustom = ""
|
||||
var uri = "https://tangem.com"
|
||||
|
||||
var uri = ""
|
||||
|
||||
// Pins
|
||||
var PIN = "000000"
|
||||
var PIN2 = "000"
|
||||
var PIN = ""
|
||||
var PIN2 = ""
|
||||
var PIN3 = ""
|
||||
var CVC = "000"
|
||||
var pauseBeforePIN2: Long = 5000L
|
||||
var CVC = ""
|
||||
var pauseBeforePIN2: Long = 0
|
||||
|
||||
companion object {
|
||||
fun default(): PersonalizationConfig {
|
||||
return PersonalizationConfig().apply {
|
||||
// Card number
|
||||
series = "BB"
|
||||
startNumber = 300000000000L
|
||||
batchId = "ffff"
|
||||
|
||||
// Common
|
||||
curveID = EllipticCurve.Secp256k1.curve
|
||||
blockchain = "ETH"
|
||||
blockchainCustom = ""
|
||||
MaxSignatures = 999999L
|
||||
createWallet = true
|
||||
|
||||
// Signing method
|
||||
SigningMethod0 = true
|
||||
SigningMethod1 = false
|
||||
SigningMethod2 = false
|
||||
SigningMethod3 = false
|
||||
SigningMethod4 = false
|
||||
SigningMethod5 = false
|
||||
SigningMethod6 = false
|
||||
|
||||
// Sign hash external properties
|
||||
pinLessFloorLimit = 100000L
|
||||
hexCrExKey = "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"
|
||||
requireTerminalTxSignature = false
|
||||
requireTerminalCertSignature = false
|
||||
checkPIN3onCard = true
|
||||
|
||||
// Denomination
|
||||
writeOnPersonalization = false
|
||||
denomination = 1000000L
|
||||
|
||||
// Token
|
||||
itsToken = false
|
||||
symbol = ""
|
||||
contractAddress = ""
|
||||
decimal = 0L
|
||||
|
||||
cardData = CardData()
|
||||
|
||||
// Settings mask
|
||||
isReusable = true
|
||||
useActivation = false
|
||||
forbidPurgeWallet = false
|
||||
allowSelectBlockchain = false
|
||||
useBlock = false
|
||||
oneApdu = false
|
||||
useCVC = false
|
||||
allowSwapPIN = true
|
||||
allowSwapPIN2 = true
|
||||
forbidDefaultPIN = false
|
||||
smartSecurityDelay = true
|
||||
protectIssuerDataAgainstReplay = true
|
||||
skipSecurityDelayIfValidatedByIssuer = true
|
||||
skipCheckPIN2andCVCIfValidatedByIssuer = true
|
||||
skipSecurityDelayIfValidatedByLinkedTerminal = true
|
||||
restrictOverwriteIssuerDataEx = false
|
||||
|
||||
// Settings mask - protocol encryption
|
||||
protocolAllowUnencrypted = true
|
||||
protocolAllowStaticEncryption = true
|
||||
|
||||
useNDEF = true
|
||||
useDynamicNDEF = true
|
||||
disablePrecomputedNDEF = false
|
||||
aar = "com.tangem.wallet"
|
||||
aarCustom = ""
|
||||
uri = "https://tangem.com"
|
||||
|
||||
// Pins
|
||||
PIN = "000000"
|
||||
PIN2 = "000"
|
||||
PIN3 = ""
|
||||
CVC = "000"
|
||||
pauseBeforePIN2 = 5000L
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CardData {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,30 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.*
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.transition.Fade
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
import com.tangem.tangemtest._arch.structure.StringId
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.SafeValueChanged
|
||||
import com.tangem.tangemtest._arch.widget.WidgetBuilder
|
||||
import com.tangem.tangemtest.commons.DialogController
|
||||
import com.tangem.tangemtest.commons.view.MultiActionView
|
||||
import com.tangem.tangemtest.commons.view.ViewAction
|
||||
import com.tangem.tangemtest.extensions.view.beginDelayedTransition
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.PersonalizationItemsManager
|
||||
|
|
@ -21,28 +33,80 @@ import com.tangem.tangemtest.ucase.tunnel.ActionView
|
|||
import com.tangem.tangemtest.ucase.tunnel.ItemError
|
||||
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.PersonalizationConfigStore
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.presets.PersonalizationPresetManager
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.presets.PersonalizationPresetView
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.presets.RvPresetNamesAdapter
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.PersonalizationItemBuilder
|
||||
import com.tangem.tangemtest.ucase.variants.responses.ui.ResponseFragment
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.threading.post
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postWork
|
||||
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PersonalizationFragment : BaseCardActionFragment() {
|
||||
class PersonalizationFragment : BaseCardActionFragment(), PersonalizationPresetView {
|
||||
|
||||
override val itemsManager: ItemsManager by lazy { PersonalizationItemsManager(PersonalizationConfigStore(requireContext())) }
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_personalization
|
||||
override fun getLayoutId(): Int = R.layout.fg_base_action_layout
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
lifecycle.addObserver(itemsManager as PersonalizationItemsManager)
|
||||
}
|
||||
|
||||
override fun bindViews() {
|
||||
super.bindViews()
|
||||
swrLayout.isRefreshing = true
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
actionFab.setOnClickListener { actionVM.invokeMainAction() }
|
||||
}
|
||||
|
||||
override fun createWidgets(widgetCreatedCallback: () -> Unit) {
|
||||
Log.d(this, "createWidgets")
|
||||
val itemList = mutableListOf<Item>()
|
||||
|
||||
val maxDelay = 500
|
||||
val timeStart = System.currentTimeMillis()
|
||||
actionVM.ldItemList.observe(viewLifecycleOwner, Observer { list ->
|
||||
Log.d(this, "ldBlockList size: ${list.size}")
|
||||
itemList.addAll(list)
|
||||
val llContainer = LinearLayout(requireContext()).apply { orientation = LinearLayout.VERTICAL }
|
||||
postWork {
|
||||
val builder = WidgetBuilder(PersonalizationItemBuilder())
|
||||
itemList.forEach { builder.build(it, llContainer) }
|
||||
actionVM.attachToPayload(mutableMapOf(
|
||||
PayloadKey.actionView to this as ActionView,
|
||||
PayloadKey.itemList to itemList
|
||||
))
|
||||
val timeEnd = System.currentTimeMillis()
|
||||
val diff = timeEnd - timeStart
|
||||
postUI(maxDelay - diff) {
|
||||
contentContainer.beginDelayedTransition(Fade())
|
||||
contentContainer.addView(llContainer)
|
||||
widgetCreatedCallback()
|
||||
swrLayout.isRefreshing = false
|
||||
swrLayout.isEnabled = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun widgetsWasCreated() {
|
||||
super.widgetsWasCreated()
|
||||
|
||||
val btnContainer = itemContainer.inflate<ViewGroup>(R.layout.view_simple_button)
|
||||
val btnContainer = contentContainer.inflate<ViewGroup>(R.layout.view_simple_button)
|
||||
val btn = btnContainer.findViewById<Button>(R.id.button)
|
||||
|
||||
val show = StringId("show")
|
||||
|
|
@ -55,28 +119,30 @@ class PersonalizationFragment : BaseCardActionFragment() {
|
|||
multiAction.state = if (it == show) hide else show
|
||||
}
|
||||
multiAction.performAction(hide)
|
||||
itemContainer.addView(btnContainer)
|
||||
contentContainer.addView(btnContainer)
|
||||
}
|
||||
|
||||
override fun initFab() {
|
||||
actionFab.setOnClickListener { actionVM.invokeMainAction() }
|
||||
override fun handleResponseCardData(card: Card) {
|
||||
super.handleResponseCardData(card)
|
||||
val bundle = ResponseFragment.setTittle(R.string.fg_name_response_personalization)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen, bundle, null)
|
||||
}
|
||||
|
||||
override fun createWidgets(widgetCreatedCallback: () -> Unit) {
|
||||
Log.d(this, "createWidgets")
|
||||
val itemList = mutableListOf<Item>()
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_fg_peronalization, menu)
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
actionVM.ldItemList.observe(viewLifecycleOwner, Observer { list ->
|
||||
Log.d(this, "ldBlockList size: ${list.size}")
|
||||
itemList.clear()
|
||||
itemList.addAll(list)
|
||||
itemList.forEach { WidgetBuilder(PersonalizationItemBuilder()).build(it, itemContainer) }
|
||||
actionVM.attachToPayload(mutableMapOf(
|
||||
PayloadKey.actionView to this as ActionView,
|
||||
PayloadKey.itemList to itemList
|
||||
))
|
||||
widgetCreatedCallback()
|
||||
})
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val store = PersonalizationConfigStore(requireContext())
|
||||
val presetManager = PersonalizationPresetManager(itemsManager, store, this)
|
||||
val result = when (item.itemId) {
|
||||
R.id.action_reset -> presetManager.resetToDefault()
|
||||
R.id.action_save -> presetManager.savePreset()
|
||||
R.id.action_load -> presetManager.loadPreset()
|
||||
else -> null
|
||||
}
|
||||
return if (result == null) super.onOptionsItemSelected(item) else true
|
||||
}
|
||||
|
||||
override fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)?) {
|
||||
|
|
@ -89,8 +155,51 @@ class PersonalizationFragment : BaseCardActionFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun responseCardDataHandled(card: Card?) {
|
||||
super.responseCardDataHandled(card)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen)
|
||||
override fun showSavePresetDialog(onOk: SafeValueChanged<String>) {
|
||||
val dlgController = DialogController()
|
||||
val dlg = dlgController.createAlert(requireActivity(), R.layout.dlg_personalization_preset_save)
|
||||
dlg.setTitle(R.string.menu_personalization_preset_save)
|
||||
dlg.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.btn_cancel)) { dialog, which -> }
|
||||
dlg.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.btn_ok)) { dialog, which ->
|
||||
val tvName = dlgController.view?.findViewById<EditText>(R.id.et_item)
|
||||
?: return@setButton
|
||||
val name = tvName.text.toString()
|
||||
if (name.isEmpty()) showSnackbar("Not saved")
|
||||
else onOk.invoke(name)
|
||||
}
|
||||
dlgController.onShowCallback = {
|
||||
dlgController.view?.findViewById<TextView>(R.id.et_item)?.let {
|
||||
post(150) {
|
||||
it.requestFocus()
|
||||
val imm = getSystemService(requireContext(), InputMethodManager::class.java)
|
||||
imm?.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
}
|
||||
dlgController.show()
|
||||
}
|
||||
|
||||
override fun showLoadPresetDialog(namesList: List<String>, onChoose: SafeValueChanged<String>, onDelete: SafeValueChanged<String>) {
|
||||
val dlgController = DialogController()
|
||||
dlgController.createAlert(requireActivity(), R.layout.dlg_personalization_preset_load)
|
||||
.setTitle(R.string.menu_personalization_preset_load)
|
||||
|
||||
val rvPresetNames: RecyclerView = dlgController.view?.findViewById(R.id.recycler_view) ?: return
|
||||
val layoutManager = LinearLayoutManager(context)
|
||||
rvPresetNames.layoutManager = layoutManager
|
||||
rvPresetNames.addItemDecoration(DividerItemDecoration(activity, layoutManager.orientation))
|
||||
|
||||
val adapter = RvPresetNamesAdapter({
|
||||
onChoose(it)
|
||||
dlgController.dismiss()
|
||||
}, {
|
||||
onDelete(it)
|
||||
if (rvPresetNames.adapter?.itemCount == 0)
|
||||
dlgController.dismiss()
|
||||
})
|
||||
adapter.setItemList(namesList.toMutableList())
|
||||
|
||||
rvPresetNames.adapter = adapter
|
||||
dlgController.show()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.presets
|
||||
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.PersonalizationConfigStore
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizationConfigConverter
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig
|
||||
|
||||
class PersonalizationPresetManager(
|
||||
private val itemsManager: ItemsManager,
|
||||
private val store: PersonalizationConfigStore,
|
||||
private val view: PersonalizationPresetView
|
||||
) {
|
||||
|
||||
fun resetToDefault() {
|
||||
val config = PersonalizationConfig.default()
|
||||
val converter = PersonalizationConfigConverter()
|
||||
itemsManager.updateByItemList(converter.convert(config))
|
||||
store.save(config)
|
||||
}
|
||||
|
||||
fun loadPreset() {
|
||||
val presets = store.restoreAll()
|
||||
presets.remove(PersonalizationConfigStore.defaultKey)
|
||||
val namesList = presets.map { it.key }.toMutableList()
|
||||
if (namesList.isEmpty()) {
|
||||
view.showSnackbar(R.string.error_nothing_to_load)
|
||||
return
|
||||
}
|
||||
|
||||
view.showLoadPresetDialog(namesList, {
|
||||
val converter = PersonalizationConfigConverter()
|
||||
val config = store.restore(it)
|
||||
itemsManager.updateByItemList(converter.convert(config))
|
||||
}, {
|
||||
store.delete(it)
|
||||
})
|
||||
}
|
||||
|
||||
fun savePreset() {
|
||||
view.showSavePresetDialog { name ->
|
||||
val converter = PersonalizationConfigConverter()
|
||||
val config = converter.convert(itemsManager.getItems(), PersonalizationConfig.default())
|
||||
store.save(name, config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.presets
|
||||
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.SafeValueChanged
|
||||
import com.tangem.tangemtest.ucase.tunnel.SnackbarHolder
|
||||
|
||||
interface PersonalizationPresetView : SnackbarHolder {
|
||||
fun showSavePresetDialog(onOk: SafeValueChanged<String>)
|
||||
fun showLoadPresetDialog(namesList: List<String>, onChoose: SafeValueChanged<String>, onDelete: SafeValueChanged<String>)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.presets
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.SafeValueChanged
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseAdapter
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvVH
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class RvPresetNamesAdapter(
|
||||
private val onItemClicked: SafeValueChanged<String>,
|
||||
private val onDeleteClicked: SafeValueChanged<String>
|
||||
) : RvBaseAdapter<PresetNameVH, String>() {
|
||||
override fun onBindViewHolder(holder: PresetNameVH, position: Int) {
|
||||
holder.bindData(itemList[position])
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PresetNameVH {
|
||||
val view = parent.inflate<View>(R.layout.vh_personalization_preset_name, false)
|
||||
return PresetNameVH(view, onItemClicked, { position, value ->
|
||||
itemList.removeAt(position)
|
||||
this.notifyItemRemoved(position)
|
||||
onDeleteClicked(value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class PresetNameVH(
|
||||
itemView: View,
|
||||
private val onItemClicked: SafeValueChanged<String>,
|
||||
private val onDeleteClicked: (Int, String) -> Unit
|
||||
) : RvVH<String>(itemView) {
|
||||
private val tvName = itemView.findViewById<TextView>(R.id.tv_name)
|
||||
private val btnDelete = itemView.findViewById<View>(R.id.btn_delete)
|
||||
override fun onDataBound(data: String) {
|
||||
tvName.text = data
|
||||
tvName.isClickable = false
|
||||
itemView.setOnClickListener { onItemClicked(data) }
|
||||
btnDelete.setOnClickListener { onDeleteClicked(absoluteAdapterPosition, data) }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,10 @@ package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
|
|||
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ViewState
|
||||
import com.tangem.tangemtest.extensions.view.beginDelayedTransition
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -31,7 +30,7 @@ abstract class DescriptionWidget(
|
|||
if (description.isEmpty()) return
|
||||
|
||||
tv.text = description
|
||||
TransitionManager.beginDelayedTransition(view.parent as ViewGroup, AutoTransition())
|
||||
(view.parent as ViewGroup).beginDelayedTransition()
|
||||
descriptionContainer.visibility = state
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
|
|||
class PersonalizationItemBuilder : ItemWidgetBuilder {
|
||||
override fun build(item: BaseItem, parent: ViewGroup): ViewWidget? {
|
||||
return when (item) {
|
||||
is TextItem -> GroupTitleWidget(parent, item)
|
||||
is TextItem -> TextHeaderWidget(parent, item)
|
||||
is EditTextItem -> EditTextWidget(parent, item)
|
||||
is NumberItem -> NumberWidget(parent, item)
|
||||
is BoolItem -> SwitchWidget(parent, item)
|
||||
|
|
|
|||
|
|
@ -46,9 +46,8 @@ class SpinnerWidget(
|
|||
}
|
||||
spinner.onItemSelectedListener = onItemSelectedListener
|
||||
item.viewModel.onDataUpdated = {
|
||||
val selectedItem = it as? String
|
||||
spinner.onItemSelectedListener = null
|
||||
viewModel.itemList.firstOrNull { item -> item.value == selectedItem }?.let { keyValue ->
|
||||
viewModel.itemList.firstOrNull { item -> item.value == it }?.let { keyValue ->
|
||||
val position = viewModel.itemList.indexOf(keyValue)
|
||||
spinner.setSelection(position)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import com.tangem.tangemtest._arch.structure.impl.TextItem
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GroupTitleWidget(parent: ViewGroup, data: TextItem) : DescriptionWidget(parent, data) {
|
||||
override fun getLayoutId(): Int = R.layout.w_personalize_item_text
|
||||
class TextHeaderWidget(parent: ViewGroup, data: TextItem) : DescriptionWidget(parent, data) {
|
||||
override fun getLayoutId(): Int = R.layout.w_personalize_item_header
|
||||
|
||||
private val tvName = view.findViewById<TextView>(R.id.tv_name)
|
||||
|
||||
|
|
@ -29,6 +29,7 @@ enum class CardId : ResponseId {
|
|||
paymentFlowVersion,
|
||||
userCounter,
|
||||
userProtectedCounter,
|
||||
empty
|
||||
}
|
||||
|
||||
enum class CardDataId : ResponseId {
|
||||
|
|
@ -41,4 +42,15 @@ enum class CardDataId : ResponseId {
|
|||
tokenSymbol,
|
||||
tokenContractAddress,
|
||||
tokenDecimal,
|
||||
}
|
||||
|
||||
enum class SignId : ResponseId {
|
||||
cid,
|
||||
walletSignedHashes,
|
||||
walletRemainingSignatures,
|
||||
signature,
|
||||
}
|
||||
|
||||
enum class DepersonalizeId: ResponseId {
|
||||
isSuccess
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tangemtest.ucase.variants.responses.converter
|
||||
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.*
|
||||
import com.tangem.tangemtest.ucase.domain.responses.ResponseFieldConverter
|
||||
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseResponseConverter<M> : ModelToItems<M> {
|
||||
protected val fieldConverter = ResponseFieldConverter()
|
||||
|
||||
protected open fun createGroup(id: Id, colorId: Int? = null, addHeaderItem: Boolean = true): ItemGroup {
|
||||
val group = if (colorId == null) SimpleItemGroup(id)
|
||||
else SimpleItemGroup(id, BaseItemViewModel(viewState = ViewState(bgColor = colorId)))
|
||||
|
||||
if (addHeaderItem) group.addItem(TextHeaderItem(id, ""))
|
||||
return group
|
||||
}
|
||||
|
||||
protected open fun valueToString(value: Any?): String? {
|
||||
if (value == null) return null
|
||||
return stringOf(value)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +1,27 @@
|
|||
package com.tangem.tangemtest.ucase.variants.responses.converter
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CardData
|
||||
import com.tangem.commands.Settings
|
||||
import com.tangem.commands.SettingsMask
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
import com.tangem.tangemtest._arch.structure.StringId
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.*
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ItemGroup
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.iterate
|
||||
import com.tangem.tangemtest._arch.structure.impl.BoolItem
|
||||
import com.tangem.tangemtest._arch.structure.impl.TextItem
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.BlockId
|
||||
import com.tangem.tangemtest.ucase.variants.responses.CardDataId
|
||||
import com.tangem.tangemtest.ucase.variants.responses.CardId
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CardConverter : ModelToItems<Card> {
|
||||
class CardConverter : BaseResponseConverter<Card>() {
|
||||
|
||||
override fun convert(from: Card): List<Item> {
|
||||
val itemList = mutableListOf<Item>()
|
||||
// val holder = GsonInitializer()
|
||||
// itemList.add(TextItem(Additional.JSON_INCOMING, holder.gson.toJson(from)))
|
||||
|
||||
itemList.add(simpleFields(from))
|
||||
itemList.add(cardData(from.cardData))
|
||||
itemList.add(settingsMask(from.settingsMask))
|
||||
commonGroup(itemList, from)
|
||||
cardDataGroup(itemList, from.cardData)
|
||||
settingsMaskGroup(itemList, from.settingsMask)
|
||||
hideEmptyNullFields(itemList)
|
||||
|
||||
return itemList
|
||||
|
|
@ -35,71 +29,66 @@ class CardConverter : ModelToItems<Card> {
|
|||
|
||||
private fun hideEmptyNullFields(itemList: MutableList<Item>) {
|
||||
itemList.iterate {
|
||||
val data = it.getData<Any?>()
|
||||
val isHidden = if (data == null) true
|
||||
else when (data) {
|
||||
is String -> data.isEmpty()
|
||||
else -> false
|
||||
}
|
||||
if (it is ItemGroup) return@iterate
|
||||
|
||||
if (isHidden) it.viewModel.viewState.isVisibleState.value = false
|
||||
if (it.getData<Any?>() == null) it.viewModel.viewState.isVisibleState.value = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun simpleFields(from: Card): Item {
|
||||
val group = createGroup(BlockId.Common)
|
||||
private fun commonGroup(itemList: MutableList<Item>, from: Card) {
|
||||
val group = createGroup(CardId.empty, addHeaderItem = false)
|
||||
itemList.add(group)
|
||||
|
||||
group.addItem(TextItem(CardId.cardId, from.cardId))
|
||||
group.addItem(TextItem(CardId.manufacturerName, from.manufacturerName))
|
||||
group.addItem(TextItem(CardId.status, stringOf(from.status)))
|
||||
group.addItem(TextItem(CardId.status, valueToString(from.status)))
|
||||
group.addItem(TextItem(CardId.firmwareVersion, from.firmwareVersion))
|
||||
group.addItem(TextItem(CardId.cardPublicKey, from.cardPublicKey?.toHexString()))
|
||||
group.addItem(TextItem(CardId.issuerPublicKey, from.issuerPublicKey?.toHexString()))
|
||||
group.addItem(TextItem(CardId.curve, stringOf(from.curve)))
|
||||
group.addItem(TextItem(CardId.maxSignatures, stringOf(from.maxSignatures)))
|
||||
group.addItem(TextItem(CardId.signingMethod, stringOf(from.signingMethod?.rawValue)))
|
||||
group.addItem(TextItem(CardId.pauseBeforePin2, stringOf(from.pauseBeforePin2)))
|
||||
group.addItem(TextItem(CardId.walletPublicKey, stringOf(from.walletPublicKey)))
|
||||
group.addItem(TextItem(CardId.walletRemainingSignatures, stringOf(from.walletRemainingSignatures)))
|
||||
group.addItem(TextItem(CardId.walletSignedHashes, stringOf(from.walletSignedHashes)))
|
||||
group.addItem(TextItem(CardId.health, stringOf(from.health)))
|
||||
group.addItem(TextItem(CardId.isActivated, stringOf(from.isActivated)))
|
||||
group.addItem(TextItem(CardId.activationSeed, stringOf(from.activationSeed)))
|
||||
group.addItem(TextItem(CardId.paymentFlowVersion, stringOf(from.paymentFlowVersion)))
|
||||
group.addItem(TextItem(CardId.userCounter, stringOf(from.userCounter)))
|
||||
// block.addItem(TextItem(CardId.UserProtectedCounter, stringOf(from.userProtectedCounter)))
|
||||
return group
|
||||
group.addItem(TextItem(CardId.cardPublicKey, fieldConverter.byteArray(from.cardPublicKey)))
|
||||
group.addItem(TextItem(CardId.issuerPublicKey, fieldConverter.byteArray(from.issuerPublicKey)))
|
||||
group.addItem(TextItem(CardId.curve, valueToString(from.curve)))
|
||||
group.addItem(TextItem(CardId.maxSignatures, valueToString(from.maxSignatures)))
|
||||
group.addItem(TextItem(CardId.pauseBeforePin2, valueToString(from.pauseBeforePin2)))
|
||||
group.addItem(TextItem(CardId.walletPublicKey, fieldConverter.byteArray(from.walletPublicKey)))
|
||||
group.addItem(TextItem(CardId.walletRemainingSignatures, valueToString(from.walletRemainingSignatures)))
|
||||
group.addItem(TextItem(CardId.walletSignedHashes, valueToString(from.walletSignedHashes)))
|
||||
group.addItem(TextItem(CardId.health, valueToString(from.health)))
|
||||
group.addItem(TextItem(CardId.isActivated, valueToString(from.isActivated)))
|
||||
group.addItem(TextItem(CardId.activationSeed, valueToString(from.activationSeed)))
|
||||
group.addItem(TextItem(CardId.paymentFlowVersion, valueToString(from.paymentFlowVersion)))
|
||||
group.addItem(TextItem(CardId.userCounter, valueToString(from.userCounter)))
|
||||
|
||||
val signingMethodMask = from.signingMethods ?: return
|
||||
|
||||
group.addItem(TextHeaderItem(CardId.signingMethod, ""))
|
||||
SigningMethod.values().forEach { group.addItem(BoolItem(StringId(it.name), signingMethodMask.contains(it))) }
|
||||
}
|
||||
|
||||
private fun cardData(from: CardData?): Item {
|
||||
val group = createGroup(BlockId.Common, R.color.group_card_data)
|
||||
val data = from ?: return group
|
||||
private fun cardDataGroup(itemList: MutableList<Item>, cardData: CardData?) {
|
||||
val data = cardData ?: return
|
||||
|
||||
// group.addItem(TextItem(StringResId(R.string.response_card_card_data)))
|
||||
val group = createGroup(CardId.cardData, R.color.group_card_data)
|
||||
itemList.add(group)
|
||||
group.addItem(TextItem(CardDataId.batchId, data.batchId))
|
||||
// Format: Year (2 bytes) | Month (1 byte) | Day (1 byte)
|
||||
group.addItem(TextItem(CardDataId.manufactureDateTime, stringOf(data.manufactureDateTime)))
|
||||
group.addItem(TextItem(CardDataId.manufactureDateTime, valueToString(data.manufactureDateTime)))
|
||||
group.addItem(TextItem(CardDataId.issuerName, data.issuerName))
|
||||
group.addItem(TextItem(CardDataId.blockchainName, data.blockchainName))
|
||||
group.addItem(TextItem(CardDataId.manufacturerSignature, data.manufacturerSignature?.toHexString()))
|
||||
group.addItem(TextItem(CardDataId.productMask, stringOf(data.productMask?.rawValue)))
|
||||
group.addItem(TextItem(CardDataId.manufacturerSignature, fieldConverter.byteArray(data.manufacturerSignature)))
|
||||
group.addItem(TextItem(CardDataId.tokenSymbol, data.tokenSymbol))
|
||||
group.addItem(TextItem(CardDataId.tokenContractAddress, data.tokenContractAddress))
|
||||
group.addItem(TextItem(CardDataId.tokenDecimal, data.tokenSymbol))
|
||||
|
||||
return group
|
||||
val productMask = data.productMask ?: return
|
||||
|
||||
group.addItem(TextHeaderItem(CardDataId.productMask, ""))
|
||||
Product.values().forEach { group.addItem(BoolItem(StringId(it.name), productMask.contains(it))) }
|
||||
}
|
||||
|
||||
private fun settingsMask(from: SettingsMask?): Item {
|
||||
val group = createGroup(CardId.settingsMask, R.color.group_signing_method)
|
||||
val data = from ?: return group
|
||||
private fun settingsMaskGroup(itemList: MutableList<Item>, from: SettingsMask?) {
|
||||
val data = from ?: return
|
||||
|
||||
val group = createGroup(CardId.settingsMask, R.color.group_settings_mask)
|
||||
itemList.add(group)
|
||||
|
||||
// group.addItem(TextItem(StringResId(R.string.response_card_settings_mask)))
|
||||
Settings.values().forEach { group.addItem(BoolItem(StringId(it.name), data.contains(it))) }
|
||||
return group
|
||||
}
|
||||
|
||||
private fun createGroup(id: Id, colorId: Int? = null): ItemGroup {
|
||||
return if (colorId == null) SimpleItemGroup(id)
|
||||
else SimpleItemGroup(id, BaseItemViewModel(viewState = ViewState(bgColor = colorId)))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +1,30 @@
|
|||
package com.tangem.tangemtest.ucase.variants.responses.converter
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.commands.personalization.DepersonalizeResponse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tangemtest._arch.structure.StringId
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ModelToItems
|
||||
import com.tangem.tangemtest._arch.structure.impl.TextItem
|
||||
import com.tangem.tangemtest.ucase.variants.responses.DepersonalizeId
|
||||
import com.tangem.tangemtest.ucase.variants.responses.SignId
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ReadEventConverter : ModelToItems<CompletionResult.Success<Card>> {
|
||||
override fun convert(from: CompletionResult.Success<Card>): List<Item> = CardConverter().convert(from.data)
|
||||
}
|
||||
|
||||
class SignResponseConverter : ModelToItems<SignResponse> {
|
||||
class SignResponseConverter : BaseResponseConverter<SignResponse>() {
|
||||
|
||||
override fun convert(from: SignResponse): List<Item> {
|
||||
return listOf(
|
||||
TextItem(StringId("CID"), from.cardId),
|
||||
TextItem(StringId("Wallet signed hashes"), stringOf(from.walletSignedHashes)),
|
||||
TextItem(StringId("Wallet remaining signatures"), stringOf(from.walletRemainingSignatures)),
|
||||
TextItem(StringId("Signature"), stringOf(from.signature))
|
||||
TextItem(SignId.cid, from.cardId),
|
||||
TextItem(SignId.walletSignedHashes, valueToString(from.walletSignedHashes)),
|
||||
TextItem(SignId.walletRemainingSignatures, valueToString(from.walletRemainingSignatures)),
|
||||
TextItem(SignId.signature, fieldConverter.byteArray(from.signature))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class DepersonalizeResponseConverter : ModelToItems<DepersonalizeResponse> {
|
||||
class DepersonalizeResponseConverter : BaseResponseConverter<DepersonalizeResponse>() {
|
||||
override fun convert(from: DepersonalizeResponse): List<Item> {
|
||||
return listOf(TextItem(StringId("Is success"), stringOf(from.success)))
|
||||
return listOf(TextItem(DepersonalizeId.isSuccess, stringOf(from.success)))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tangemtest.ucase.variants.responses.item
|
||||
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ViewState
|
||||
import com.tangem.tangemtest._arch.structure.impl.TypedItem
|
||||
|
||||
open class TextHeaderItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
|
||||
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
|
||||
: this(id, BaseItemViewModel(value, viewState))
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ package com.tangem.tangemtest.ucase.variants.responses.ui
|
|||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
|
|
@ -23,24 +25,25 @@ open class ResponseFragment : BaseFragment() {
|
|||
private val mainActivityVM: MainViewModel by activityViewModels()
|
||||
private val selfVM: ResponseViewModel by viewModels()
|
||||
|
||||
private val itemContainer: ViewGroup by lazy { mainView.findViewById<LinearLayout>(R.id.ll_container) }
|
||||
private val itemContainer: ViewGroup by lazy { mainView.findViewById<LinearLayout>(R.id.ll_content_container) }
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_card_response
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
setTittle()
|
||||
}
|
||||
|
||||
private fun setTittle() {
|
||||
val titleId = selfVM.determineTitleId(mainActivityVM.commandResponse)
|
||||
val titleId = getTittleId(arguments) ?: selfVM.determineTitleId(mainActivityVM.commandResponse)
|
||||
activity?.setTitle(titleId)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
buildWidgets()
|
||||
listenDescriptionSwitchChanges()
|
||||
}
|
||||
|
|
@ -58,10 +61,8 @@ open class ResponseFragment : BaseFragment() {
|
|||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_fg_response, menu)
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
|
||||
val menuItem = menu.findItem(R.id.action_share)
|
||||
menuItem.isVisible = true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
|
|
@ -72,4 +73,12 @@ open class ResponseFragment : BaseFragment() {
|
|||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val argTittle = "tittle"
|
||||
|
||||
fun setTittle(@StringRes id: Int): Bundle = bundleOf(Pair(argTittle, id))
|
||||
|
||||
private fun getTittleId(args: Bundle?): Int? = args?.getInt(argTittle)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.tangemtest._arch.structure.impl.BoolItem
|
|||
import com.tangem.tangemtest._arch.structure.impl.TextItem
|
||||
import com.tangem.tangemtest._arch.widget.ItemWidgetBuilder
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
|
||||
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -13,6 +14,7 @@ import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
|
|||
class ResponseItemBuilder : ItemWidgetBuilder {
|
||||
override fun build(item: BaseItem, parent: ViewGroup): ViewWidget? {
|
||||
return when (item) {
|
||||
is TextHeaderItem -> ResponseHeaderWidget(parent, item)
|
||||
is TextItem -> ResponseTextWidget(parent, item)
|
||||
is BoolItem -> CheckBoxWidget(parent, item)
|
||||
else -> null
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.view.ViewGroup
|
|||
import android.widget.TextView
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.impl.TextItem
|
||||
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -27,4 +28,18 @@ class ResponseTextWidget(
|
|||
tvName.text = getName()
|
||||
tvValue.text = data
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseHeaderWidget(
|
||||
parent: ViewGroup,
|
||||
private val typedItem: TextHeaderItem
|
||||
) : ResponseWidget(parent, typedItem) {
|
||||
override fun getLayoutId(): Int = R.layout.w_response_item_header
|
||||
|
||||
private val tvName: TextView = view.findViewById(R.id.tv_name)
|
||||
|
||||
init {
|
||||
tvName.text = getName()
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class ResponseWidget(parent: ViewGroup, item: Item): DescriptionWidget(parent, item) {
|
||||
abstract class ResponseWidget(parent: ViewGroup, item: Item) : DescriptionWidget(parent, item) {
|
||||
|
||||
init {
|
||||
view.setOnClickListener {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
package com.tangem.tangemtest.ucase.variants.scan.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.ScanItemsManager
|
||||
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
|
||||
import com.tangem.tangemtest.ucase.variants.responses.ui.ResponseFragment
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.dpToPx
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -13,14 +21,36 @@ class ScanActionFragment : BaseCardActionFragment() {
|
|||
|
||||
override val itemsManager: ItemsManager by lazy { ScanItemsManager() }
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_action_card_scan
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
override fun initFab() {
|
||||
val howToUseView = createHowToUse()
|
||||
contentContainer.layoutParams = FrameLayout.LayoutParams(-1, -1, Gravity.CENTER)
|
||||
contentContainer.addView(howToUseView)
|
||||
}
|
||||
|
||||
private fun createHowToUse(): View {
|
||||
val fl = FrameLayout(requireContext())
|
||||
fl.layoutParams = FrameLayout.LayoutParams(-1, -1, Gravity.CENTER)
|
||||
val tv = TextView(requireContext()).apply {
|
||||
val padding = dpToPx(16f).toInt()
|
||||
setPadding(padding, 0, padding, 0)
|
||||
gravity = Gravity.CENTER
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
|
||||
setText(R.string.htu_scan_action)
|
||||
}
|
||||
fl.addView(tv)
|
||||
return fl
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
swrLayout.isEnabled = false
|
||||
actionFab.setOnClickListener { actionVM.invokeMainAction() }
|
||||
}
|
||||
|
||||
override fun responseCardDataHandled(card: Card?) {
|
||||
super.responseCardDataHandled(card)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen)
|
||||
override fun handleResponseCardData(card: Card) {
|
||||
super.handleResponseCardData(card)
|
||||
val bundle = ResponseFragment.setTittle(R.string.fg_name_response_scan)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen, bundle, null)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tangemtest.ucase.variants.sign.ui
|
||||
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.SignItemsManager
|
||||
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
|
||||
|
|
@ -11,6 +10,4 @@ import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
|
|||
class SignActionFragment : BaseCardActionFragment() {
|
||||
|
||||
override val itemsManager: ItemsManager by lazy { SignItemsManager() }
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_action_card_sign
|
||||
}
|
||||
|
|
@ -10,7 +10,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
||||
app:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"/>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_host_fragment"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="@dimen/def_indent"
|
||||
android:paddingBottom="@dimen/def_half_indent">
|
||||
|
||||
<include
|
||||
layout="@layout/m_divider_h"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/def_indent"
|
||||
android:layout_marginEnd="@dimen/def_indent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/til_item"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
app:boxBackgroundColor="@android:color/transparent"
|
||||
android:hint="Enter a preset name"
|
||||
tools:hint="Field name">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/et_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Some text field" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/coordinator_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/w_action_response_json" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_screen_stub"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:textSize="18sp"
|
||||
android:layout_margin="@dimen/def_double_indent"
|
||||
android:text="@string/empty_screen_stub" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="@dimen/def_indent"
|
||||
android:layout_marginBottom="@dimen/def_double_indent"
|
||||
android:src="@drawable/ic_nfc" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/coordinator_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/w_action_response_json" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="@dimen/def_indent"
|
||||
android:layout_marginBottom="@dimen/def_double_indent"
|
||||
android:src="@drawable/ic_nfc" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -5,29 +5,17 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
<include layout="@layout/v_content_container" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="@dimen/def_indent"
|
||||
android:layout_marginBottom="@dimen/def_double_indent"
|
||||
android:src="@drawable/ic_nfc"
|
||||
app:layout_anchor="@id/scroll_view"
|
||||
app:layout_anchor="@id/swr_layout"
|
||||
app:layout_anchorGravity="bottom|end" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_container"
|
||||
android:id="@+id/ll_content_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/coordinator_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/w_action_response_json" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="@dimen/def_indent"
|
||||
android:layout_marginBottom="@dimen/def_double_indent"
|
||||
android:src="@drawable/ic_nfc" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
20
tangem-demo/src/main/res/layout/v_content_container.xml
Normal file
20
tangem-demo/src/main/res/layout/v_content_container.xml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/swr_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_content_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
26
tangem-demo/src/main/res/layout/v_item_list_container.xml
Normal file
26
tangem-demo/src/main/res/layout/v_item_list_container.xml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/root_items"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/item_header_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/item_list_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/item_footer_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_name"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btn_delete"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:hint="Field name" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_delete"
|
||||
android:layout_width="26dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@android:drawable/ic_menu_delete" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/delimiter"
|
||||
android:background="@color/field_header_background"
|
||||
android:minHeight="@dimen/iw_layout_frame_header_min_height"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="6dp">
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/w_personalize_item_text" />
|
||||
<include layout="@layout/w_personalize_item_header" />
|
||||
|
||||
<include layout="@layout/w_personalize_item_edit_text" />
|
||||
|
||||
|
|
|
|||
35
tangem-demo/src/main/res/layout/w_response_item_header.xml
Normal file
35
tangem-demo/src/main/res/layout/w_response_item_header.xml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/field_header_background">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/container_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/def_indent"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/field_content"
|
||||
android:textSize="16sp"
|
||||
tools:text="Custom blockchain" />
|
||||
|
||||
<include layout="@layout/w_field_description" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
layout="@layout/m_divider_h"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_gravity="bottom" />
|
||||
|
||||
</FrameLayout>
|
||||
11
tangem-demo/src/main/res/menu/menu_activity_main.xml
Normal file
11
tangem-demo/src/main/res/menu/menu_activity_main.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_toggle_description_visibility"
|
||||
android:checkable="true"
|
||||
android:title="@string/menu_main_description"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
18
tangem-demo/src/main/res/menu/menu_fg_peronalization.xml
Normal file
18
tangem-demo/src/main/res/menu/menu_fg_peronalization.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<group android:id="@+id/menu_group_personalization_preset">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_reset"
|
||||
android:title="@string/menu_personalization_preset_reset" />
|
||||
<item
|
||||
android:id="@+id/action_save"
|
||||
android:title="@string/menu_personalization_preset_save" />
|
||||
<item
|
||||
android:id="@+id/action_load"
|
||||
android:title="@string/menu_personalization_preset_load" />
|
||||
|
||||
</group>
|
||||
|
||||
</menu>
|
||||
|
|
@ -5,14 +5,7 @@
|
|||
<item
|
||||
android:id="@+id/action_share"
|
||||
android:icon="@drawable/ic_share_white_18dp"
|
||||
android:title="Share"
|
||||
android:visible="false"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_favorite"
|
||||
android:title=""
|
||||
app:actionLayout="@layout/menu_item_switch"
|
||||
app:showAsAction="always" />
|
||||
android:title="@string/menu_response_share"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
||||
|
|
@ -6,7 +6,13 @@
|
|||
|
||||
<action
|
||||
android:id="@+id/action_nav_card_action_to_response_screen"
|
||||
app:destination="@+id/nav_card_response" />
|
||||
app:destination="@+id/nav_card_response"
|
||||
app:enterAnim="@anim/slide_in_right"
|
||||
app:exitAnim="@anim/slide_out_left"
|
||||
app:popEnterAnim="@anim/slide_in_left"
|
||||
app:popExitAnim="@anim/slide_out_right"
|
||||
app:popUpTo="@id/nav_entry_point"
|
||||
app:popUpToInclusive="false" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_entry_point"
|
||||
|
|
@ -57,25 +63,25 @@
|
|||
android:id="@+id/nav_scan"
|
||||
android:name="com.tangem.tangemtest.ucase.variants.scan.ui.ScanActionFragment"
|
||||
android:label="@string/action_card_scan"
|
||||
tools:layout="@layout/fg_action_card_scan" />
|
||||
tools:layout="@layout/fg_base_action_layout" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_sign"
|
||||
android:name="com.tangem.tangemtest.ucase.variants.sign.ui.SignActionFragment"
|
||||
android:label="@string/action_card_sign"
|
||||
tools:layout="@layout/fg_action_card_sign" />
|
||||
tools:layout="@layout/fg_base_action_layout" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_personalize"
|
||||
android:name="com.tangem.tangemtest.ucase.variants.personalize.ui.PersonalizationFragment"
|
||||
android:label="@string/action_personalize"
|
||||
tools:layout="@layout/fg_personalization" />
|
||||
tools:layout="@layout/fg_base_action_layout" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_depersonalize"
|
||||
android:name="com.tangem.tangemtest.ucase.variants.depersonalize.ui.DepersonalizeActionFragment"
|
||||
android:label="@string/action_depersonalize"
|
||||
tools:layout="@layout/fg_depersonalize" />
|
||||
tools:layout="@layout/fg_base_action_layout" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_issuer_read_data"
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@
|
|||
|
||||
<color name="action_name">#2B2B2B</color>
|
||||
|
||||
<color name="field_header_background">#C3C3C3</color>
|
||||
<color name="field_info">#888888</color>
|
||||
<color name="field_content">#303030</color>
|
||||
<color name="field_description">@color/field_info</color>
|
||||
|
||||
<color name="group_card_data">#D7E4F3</color>
|
||||
<color name="group_signing_method">#D7F3E6</color>
|
||||
<color name="group_settings_mask">#D7F3E6</color>
|
||||
|
||||
|
||||
<color name="switchTrack">#C6C6C6</color>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,25 @@
|
|||
<resources>
|
||||
<string name="app_name">Tangem Development Kit</string>
|
||||
|
||||
<string name="menu_main_description">Description</string>
|
||||
|
||||
<string name="copy_to_clipboard">Copy to clipboard</string>
|
||||
<string name="btn_delete">Delete</string>
|
||||
<string name="btn_ok">OK</string>
|
||||
<string name="btn_cancel">Cancel</string>
|
||||
<string name="btn_save">Save</string>
|
||||
<string name="btn_load">Load</string>
|
||||
|
||||
<string name="unknown">unknown</string>
|
||||
<string name="error_nothing_to_load">Nothing to load</string>
|
||||
|
||||
<string name="fg_name_entry_point">@string/app_name</string>
|
||||
<string name="fg_name_response_scan">Scan response</string>
|
||||
<string name="fg_name_response_scan">Read response</string>
|
||||
<string name="fg_name_response_sign">Sign response</string>
|
||||
<string name="fg_name_response_personalization">Personalization response</string>
|
||||
<string name="fg_name_response_depersonalization">Depersonalization response</string>
|
||||
|
||||
<string name="unknown">unknown</string>
|
||||
<string name="copy_to_clipboard">Copy to clipboard</string>
|
||||
|
||||
<string name="switch_description">Docs</string>
|
||||
|
||||
<string name="card_error_not_personalized">Your card hasn\'t been personalized yet. You need to run personalize command first.</string>
|
||||
|
|
@ -18,7 +29,7 @@
|
|||
<string name="stub">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit</string>
|
||||
<!-- <string name="stub">Sed ut perspiciatis unde omnis iste natus</string>-->
|
||||
|
||||
<string name="show_rare_fields">Show rarely used fields</string>
|
||||
<string name="hide_rare_fields">Hide</string>
|
||||
<string name="show_rare_fields">Show all fields</string>
|
||||
<string name="hide_rare_fields">Hide fields</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="menu_personalization_preset_reset">Reset to default</string>
|
||||
<string name="menu_personalization_preset_save">Save configuration</string>
|
||||
<string name="menu_personalization_preset_load">Load configuration</string>
|
||||
|
||||
<string name="personalize">Personalize</string>
|
||||
<string name="depersonalize">Depersonalize</string>
|
||||
<string name="pers_block_card_number">Card number</string>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="menu_response_share">Share</string>
|
||||
|
||||
<!-- Field names - Response: Card -->
|
||||
<string name="response_card_cid">CID</string>
|
||||
|
|
@ -56,7 +57,7 @@
|
|||
<string name="info_response_card_status">Current status of the card</string>
|
||||
<string name="info_response_card_firmware_version">Version of Tangem COS</string>
|
||||
<string name="info_response_card_public_key">Public key that is used to authenticate the card against manufacturer’s database. It is generated one time during card manufacturing. See Security section for more details.</string>
|
||||
<string name="info_response_card_settings_mask">Card settings defined by personalization (bit mask: 0 – Enabled, 1 – Disabled):</string>
|
||||
<string name="info_response_card_settings_mask">Card settings defined by personalization.</string>
|
||||
<string name="info_response_card_is_reusable">Defines what happens when user calls PURGE_WALLET command:
|
||||
\n0 - Card will switch to Purged state
|
||||
\n1 - Card will switch to Empty state and let create a new wallet again</string>
|
||||
|
|
@ -71,29 +72,29 @@
|
|||
<string name="info_response_card_one_apdu_at_time">Card will execute only one command during one communication session, thus requiring user to physically take the card away from the host after each action (all commands except for READ_CARD).</string>
|
||||
<string name="info_response_card_use_ndef">Whether the card should emulate NDEF. In default configuration, two NDEF records are loaded during personalization: (1) Tangem web site address, (2) name of Android App package in Google Play Store.</string>
|
||||
<string name="info_response_card_use_dynamic_ndef">0 – Disable dynamic generation of NDEF for iOS. See Dynamic NDEF section for more details.
|
||||
/n1 – Enable dynamic NDEF for iOS.</string>
|
||||
\n1 – Enable dynamic NDEF for iOS.</string>
|
||||
<string name="info_response_card_smart_security_delay">Security delay Pause_Before_PIN2 will not be applied if PIN2 is not default.</string>
|
||||
<string name="info_response_card_allow_unencrypted">Whether the card supports unencrypted NFC communication. See NFC communication section for more details.</string>
|
||||
<string name="info_response_card_allow_fast_encryption">Whether the card supports fast encrypted NFC communication. See NFC communication section for more details.</string>
|
||||
<string name="info_response_card_protect_issuer_data_against_replay">0 – No replay protection on write issuer data
|
||||
/n1 – Enable replay protection on write issuer data (card will require additional Issuer_Data_Counter incremented on each write)</string>
|
||||
\n1 – Enable replay protection on write issuer data (card will require additional Issuer_Data_Counter incremented on each write)</string>
|
||||
<string name="info_response_card_allow_select_blockchain">0 – Wallet elliptic curve and blockchain information stored during PERSONALIZE command and never change
|
||||
/n1 – Wallet elliptic curve and blockchain information can be changed on CREATE_WALLET command</string>
|
||||
\n1 – Wallet elliptic curve and blockchain information can be changed on CREATE_WALLET command</string>
|
||||
<string name="info_response_card_disable_precomputed_ndef">0 – Enable precomputed dynamic NDEF to work around iPhone 7+ NFC bug.
|
||||
/n1 – Disable precomputed dynamic NDEF. See Dynamic NDEF section for more details.</string>
|
||||
\n1 – Disable precomputed dynamic NDEF. See Dynamic NDEF section for more details.</string>
|
||||
<string name="info_response_card_security_delay_if_validated">0 – Enforce security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).
|
||||
/n1 – Skip security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).</string>
|
||||
\n1 – Skip security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).</string>
|
||||
<string name="info_response_card_skip_pin2_cvc_if_validated_by_issuer">0 – Require and check PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).
|
||||
/n1 – Skip checking PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).</string>
|
||||
\n1 – Skip checking PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).</string>
|
||||
<string name="info_response_card_skip_security_delay_if_validated_by_linked_terminal">1 - Store Terminal_PublicKey public key of linked terminal no each SIGN command, skip security delay if valid signature of transaction is made with Terminal_PrivateKey is provided in SIGN command</string>
|
||||
<string name="info_response_card_restrict_overwrite_issuer_ex_data"></string>
|
||||
<string name="info_info_response_card_prohibit_overwriting_issuer_ex_data"></string>
|
||||
<string name="info_response_card_require_terminal_tx_sig">0 – Skip checking terminal’s signature when signing POS transaction
|
||||
/n1 – Check terminal’s signature when signing POS transaction</string>
|
||||
\n1 – Check terminal’s signature when signing POS transaction</string>
|
||||
<string name="info_response_card_require_terminal_cert_sig">0 – Skip checking acquirer’s signature of terminal certificate when signing POS transaction
|
||||
/n1 – Check acquirer’s signature of terminal certificate when signing POS transaction</string>
|
||||
\n1 – Check acquirer’s signature of terminal certificate when signing POS transaction</string>
|
||||
<string name="info_response_card_check_pin3">0 – Additionally encrypt POS transaction signature with key derived from PIN3 when the transaction amount exceeds PIN3 floor limit
|
||||
/n1 – Require terminal to send PIN3 to card when the POS transaction amount exceeds PIN3_Floor_Limit</string>
|
||||
\n1 – Require terminal to send PIN3 to card when the POS transaction amount exceeds PIN3_Floor_Limit</string>
|
||||
<string name="info_response_card_card_data">Detailed information about card contents. Format is defined by the card issuer. Cards complaint with Tangem Wallet application should have TLV format described in Personalization section.</string>
|
||||
<string name="info_response_card_issuer_data_public_key">Public key that is used by the card issuer to sign Issuer_Data field. See Security section for more details.</string>
|
||||
<string name="info_response_card_curve">Explicit text name of the elliptic curve used for all wallet key operations.</string>
|
||||
|
|
@ -105,12 +106,12 @@
|
|||
<string name="info_response_card_wallet_signed_hashes">Total number of signed single hashes returned by the card in SIGN command responses since card personalization. Sums up array elements within all SIGN commands.</string>
|
||||
<string name="info_response_card_health">Any non-zero value indicates that the card experiences some hardware problems. User should withdraw the value to other blockchain wallet as soon as possible. Non-zero Health tag will also appear in responses of all other commands.</string>
|
||||
<string name="info_response_card_is_activated">Whether the card requires issuer’s confirmation of activation.
|
||||
/n0 – card will require issuer’s confirmation of activation,
|
||||
/notherwise this field will not be returned (card is activated and operational).</string>
|
||||
\n0 – card will require issuer’s confirmation of activation,
|
||||
\notherwise this field will not be returned (card is activated and operational).</string>
|
||||
<string name="info_response_card_activation_seed">A random challenge generated by PERSONALIZE command that should be signed and returned to COS by the issuer to confirm the card has been activated. See ACTIVATE_CARD command for more details.
|
||||
/nThis field will not be returned if the card is activated.</string>
|
||||
\nThis field will not be returned if the card is activated.</string>
|
||||
<string name="info_response_card_payment_flow_version">Version of POS payment scheme supported by COS ([0x02,0x01] for version 2.30)
|
||||
/nReturned only if SigningMethod ‘6’ enabling POS transactions is supported by card.</string>
|
||||
\nReturned only if SigningMethod ‘6’ enabling POS transactions is supported by card.</string>
|
||||
<string name="info_response_card_user_counter">This value can be initialized by App and will be increased by COS with the execution of each SIGN command. For example, this field can store blockchain “nonce” for a quick one-touch transaction on POS terminals. Returned only if SigningMethod =6.</string>
|
||||
<string name="info_response_card_user_protected_counter">This value can be initialized by App (with PIN2 confirmation) and will be increased by COS with the execution of each SIGN command. For example, this field can store blockchain “nonce” for a quick one-touch transaction on POS terminals. Returned only if SigningMethod =6.</string>
|
||||
|
||||
|
|
@ -135,4 +136,23 @@
|
|||
<string name="info_response_card_card_data_token_symbol"></string>
|
||||
<string name="info_response_card_card_data_token_contract_address"></string>
|
||||
<string name="info_response_card_card_data_token_decimal"></string>
|
||||
|
||||
|
||||
<!-- Field names - Response: Sign -->
|
||||
<string name="response_sign_cid">CID</string>
|
||||
<string name="response_sign_wallet_signed_hashes">Wallet signed hashes</string>
|
||||
<string name="response_sign_wallet_remaining_signatures">Wallet remaining signatures</string>
|
||||
<string name="response_sign_signature">Signature</string>
|
||||
|
||||
<string name="info_response_sign_cid">@string/info_response_card_cid</string>
|
||||
<string name="info_response_sign_wallet_signed_hashes">Total number of signed single hashes returned by the card in SIGN command responses since card personalization. Sums up array elements within all SIGN commands.</string>
|
||||
<string name="info_response_sign_wallet_remaining_signatures">Remaining number of SIGN operations before the wallet will stop signing transactions.</string>
|
||||
<string name="info_response_sign_signature">Array of resulting signatures that App should embed into a raw transaction according to a transaction format of an appropriate blockchain.</string>
|
||||
|
||||
|
||||
<!-- Field names - Response: Depersonalize -->
|
||||
<string name="response_depersonalize_is_success">Is success</string>
|
||||
|
||||
<string name="info_response_depersonalize_is_success">Is success</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="empty_screen_stub">To read the card, press the button at the right bottom corner of the screen</string>
|
||||
<!-- How to use -->
|
||||
<string name="htu_scan_action">To read the card, press the button at the right bottom corner of the screen</string>
|
||||
|
||||
</resources>
|
||||
73
tangem-demo/src/main/res/values/theme_debug.xml
Normal file
73
tangem-demo/src/main/res/values/theme_debug.xml
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- You can change the parent around to whatever you normally use -->
|
||||
<style name="DebugColors" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
|
||||
<!-- System colors -->
|
||||
<item name="android:windowBackground">@color/__debugWindowBackground</item>
|
||||
|
||||
<item name="android:colorPressedHighlight">#FF4400</item>
|
||||
<item name="android:colorLongPressedHighlight">#FF0044</item>
|
||||
<item name="android:colorFocusedHighlight">#44FF00</item>
|
||||
<item name="android:colorActivatedHighlight">#00FF44</item>
|
||||
<item name="android:colorMultiSelectHighlight">#4400FF</item>
|
||||
|
||||
<item name="android:statusBarColor">#FFF000</item>
|
||||
<item name="android:navigationBarColor">#000FFF</item>
|
||||
|
||||
<item name="android:colorForeground">#440000</item>
|
||||
<item name="android:colorForegroundInverse">#004400</item>
|
||||
<item name="android:colorBackground">#444400</item>
|
||||
<item name="android:colorBackgroundCacheHint">#440044</item>
|
||||
|
||||
//Only for >21
|
||||
<item name="android:textColorPrimary">#FFFF00</item>
|
||||
<item name="android:textColorSecondary">#FF00FF</item>
|
||||
<item name="android:textColorTertiary">#00FFFF</item> <!-- Overrides a TextView textColor-->
|
||||
|
||||
<item name="android:textColorPrimaryInverse">#CCCC00</item>
|
||||
<item name="android:textColorSecondaryInverse">#CC00CC</item>
|
||||
<item name="android:textColorTertiaryInverse">#00CCCC</item>
|
||||
|
||||
<item name="android:textColorPrimaryDisableOnly">#FFCC00</item>
|
||||
<item name="android:textColorPrimaryInverseDisableOnly">#FF00CC</item>
|
||||
|
||||
<item name="android:textColorPrimaryNoDisable">#CCFF00</item>
|
||||
<item name="android:textColorSecondaryNoDisable">#00FFCC</item>
|
||||
|
||||
<item name="android:textColorPrimaryInverseNoDisable">#CC00FF</item>
|
||||
<item name="android:textColorSecondaryInverseNoDisable">#00CCFF</item>
|
||||
|
||||
<item name="android:textColorHint">#FF8800</item>
|
||||
<item name="android:textColorHintInverse">#FF0088</item>
|
||||
|
||||
<item name="android:textColorHighlight">#88FF00</item>
|
||||
<item name="android:textColorHighlightInverse">#00FF88</item>
|
||||
|
||||
<item name="android:textColorLink">#8800FF</item>
|
||||
<item name="android:textColorLinkInverse">#0088FF</item>
|
||||
|
||||
<item name="android:textColorAlertDialogListItem">#444444</item>
|
||||
|
||||
<!-- Color palette (via app-compat) -->
|
||||
<item name="colorPrimary">#FF0000</item>
|
||||
<item name="colorPrimaryDark">#00FF00</item>
|
||||
<item name="colorAccent">#0000FF</item>
|
||||
|
||||
<item name="colorControlNormal">#CC0000</item>
|
||||
<item name="colorControlActivated">#00CC00</item>
|
||||
<item name="colorControlHighlight">#0000CC</item>
|
||||
|
||||
<item name="colorButtonNormal">#880000</item>
|
||||
<item name="colorSwitchThumbNormal">#008800</item>
|
||||
|
||||
<!-- Random other things found in app-compat -->
|
||||
<item name="actionMenuTextColor">#440000</item>
|
||||
<item name="editTextColor">#FF4400</item> <!-- Overrides textColorPrimary-->
|
||||
<item name="textColorSearchUrl">#000044</item>
|
||||
|
||||
</style>
|
||||
|
||||
<!-- Also needed, since windowBackground is a reference, not a color -->
|
||||
<color name="__debugWindowBackground">#888888</color>
|
||||
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue