Updated on 2026-08-14
|
|
@ -57,12 +57,6 @@ android {
|
|||
applicationId "com.tangem.wallet"
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
tangemCardano {
|
||||
versionCode 1
|
||||
versionName "0.1.0"
|
||||
applicationId "com.tangem.cardano"
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
|
|
|||
|
|
@ -28,7 +28,15 @@ public class Server {
|
|||
public static final String URL_INFURA = ServerURL.API_INFURA;
|
||||
|
||||
public static class Method {
|
||||
static final String MAIN = URL_INFURA + "v3/613a0b14833145968b1f656240c7d245";
|
||||
public static final String MAIN = "v3/613a0b14833145968b1f656240c7d245";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ApiInfuraTestnet {
|
||||
public static final String URL_INFURA_TESTNET = ServerURL.API_INFURA_TESTNET;
|
||||
|
||||
public static class Method {
|
||||
public static final String MAIN = "v3/613a0b14833145968b1f656240c7d245";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.util.Log;
|
|||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.tangem.App;
|
||||
import com.tangem.data.Blockchain;
|
||||
import com.tangem.data.network.model.InfuraBody;
|
||||
import com.tangem.data.network.model.InfuraResponse;
|
||||
|
||||
|
|
@ -34,6 +35,16 @@ public class ServerApiInfura {
|
|||
|
||||
private int requestsCount=0;
|
||||
|
||||
private InfuraApi infuraApi = App.Companion.getNetworkComponent().getRetrofitInfura().create(InfuraApi.class);
|
||||
|
||||
public ServerApiInfura() {}
|
||||
|
||||
public ServerApiInfura(Blockchain blockchain) {
|
||||
if (blockchain == Blockchain.EthereumTestNet) {
|
||||
infuraApi = App.Companion.getNetworkComponent().getRetrofitInfuraTestnet().create(InfuraApi.class);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRequestsSequenceCompleted() {
|
||||
Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount));
|
||||
return requestsCount <= 0;
|
||||
|
|
@ -53,7 +64,7 @@ public class ServerApiInfura {
|
|||
|
||||
public void requestData(String method, int id, String wallet, String contract, String tx) {
|
||||
requestsCount++;
|
||||
InfuraApi infuraApi = App.Companion.getNetworkComponent().getRetrofitInfura().create(InfuraApi.class);
|
||||
|
||||
|
||||
InfuraBody infuraBody;
|
||||
switch (method) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ class ServerURL {
|
|||
static final String API_TANGEM = "https://verify.tangem.com/";
|
||||
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_SOCHAIN_V2 = "https://chain.so/";
|
||||
static final String API_ESTIMATEFEE = "https://estimatefee.com/";
|
||||
static final String API_UPDATE_VERSION = "https://raw.githubusercontent.com/";
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ interface NetworkComponent {
|
|||
@get:Named(Server.ApiInfura.URL_INFURA)
|
||||
val retrofitInfura: Retrofit
|
||||
|
||||
@get:Named(Server.ApiInfuraTestnet.URL_INFURA_TESTNET)
|
||||
val retrofitInfuraTestnet: Retrofit
|
||||
|
||||
@get:Named(Server.ApiMaticTesnet.URL_MATIC_TESTNET)
|
||||
val retrofitMaticTesnet: Retrofit
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,18 @@ internal class NetworkModule {
|
|||
return builder.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named(Server.ApiInfuraTestnet.URL_INFURA_TESTNET)
|
||||
fun provideRetrofitInfuraTestnet(): Retrofit {
|
||||
val builder = Retrofit.Builder()
|
||||
.baseUrl(Server.ApiInfuraTestnet.URL_INFURA_TESTNET)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
if (BuildConfig.DEBUG)
|
||||
builder.client(createOkHttpClient())
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named(Server.ApiRootstock.URL_ROOTSTOCK)
|
||||
|
|
|
|||
|
|
@ -466,7 +466,7 @@ public class EthEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
|
||||
final ServerApiInfura serverApiInfura = new ServerApiInfura();
|
||||
final ServerApiInfura serverApiInfura = new ServerApiInfura(ctx.getBlockchain());
|
||||
final ServerApiBlockcypher serverApiBlockcypher = new ServerApiBlockcypher();
|
||||
|
||||
ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() {
|
||||
|
|
@ -561,11 +561,21 @@ public class EthEngine extends CoinEngine {
|
|||
|
||||
public void onSuccess(String method, BlockcypherFee blockcypherFee) {
|
||||
Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions");
|
||||
if (serverApiInfura.isRequestsSequenceCompleted()&& serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String method, String message) {
|
||||
Log.i(TAG, "onFail: " + method + " " + message);
|
||||
if (serverApiInfura.isRequestsSequenceCompleted()&& serverApiBlockcypher.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
serverApiBlockcypher.setResponseListener(blockcypherListener);
|
||||
|
|
@ -579,7 +589,7 @@ public class EthEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
|
||||
ServerApiInfura serverApiInfura = new ServerApiInfura();
|
||||
ServerApiInfura serverApiInfura = new ServerApiInfura(ctx.getBlockchain());
|
||||
// request requestData eth gasPrice listener
|
||||
ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() {
|
||||
@Override
|
||||
|
|
@ -629,7 +639,7 @@ public class EthEngine extends CoinEngine {
|
|||
|
||||
String txStr = String.format("0x%s", BTCUtils.toHex(txForSend));
|
||||
|
||||
ServerApiInfura serverApiInfura = new ServerApiInfura();
|
||||
ServerApiInfura serverApiInfura = new ServerApiInfura(ctx.getBlockchain());
|
||||
// request requestData eth gasPrice listener
|
||||
ServerApiInfura.ResponseListener responseListener = new ServerApiInfura.ResponseListener() {
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import android.view.inputmethod.EditorInfo
|
|||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import com.tangem.Constant
|
||||
import com.tangem.data.Blockchain
|
||||
import com.tangem.ui.activity.MainActivity
|
||||
import com.tangem.ui.fragment.BaseFragment
|
||||
import com.tangem.ui.fragment.qr.CameraPermissionManager
|
||||
|
|
@ -151,6 +152,9 @@ class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, Nfc
|
|||
// etAmount?.setText(amount)
|
||||
// rgIncFee.check(R.id.rbFeeOut)
|
||||
// }
|
||||
} else if (ctx.blockchain == Blockchain.Ripple && schemeSplit[0] == "ripple") {
|
||||
val uri = Uri.parse(schemeSplit[1])
|
||||
etWallet?.setText(uri.path)
|
||||
} else {
|
||||
etWallet?.setText(code)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
package com.tangem;
|
||||
|
||||
public class Test1 {
|
||||
}
|
||||
|
|
@ -1,310 +0,0 @@
|
|||
package com.tangem.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.Html
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.os.bundleOf
|
||||
import com.tangem.Constant
|
||||
import com.tangem.tangem_sdk.android.data.PINStorage
|
||||
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD
|
||||
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID
|
||||
import com.tangem.tangem_sdk.data.loadFromBundle
|
||||
import com.tangem.tangem_card.data.TangemCard
|
||||
import com.tangem.ui.activity.MainActivity
|
||||
import com.tangem.ui.event.TransactionFinishWithError
|
||||
import com.tangem.ui.fragment.BaseFragment
|
||||
import com.tangem.ui.fragment.pin.PinRequestFragment
|
||||
import com.tangem.ui.navigation.NavigationResultListener
|
||||
import com.tangem.util.UtilHelper
|
||||
import com.tangem.wallet.CoinEngine
|
||||
import com.tangem.wallet.CoinEngineFactory
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.TangemContext
|
||||
import kotlinx.android.synthetic.tangemCardano.fragment_confirm_transaction.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class ConfirmTransactionFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback {
|
||||
|
||||
override val layoutId = R.layout.fragment_confirm_transaction
|
||||
|
||||
private val ctx: TangemContext by lazy { TangemContext() }
|
||||
private lateinit var amount: CoinEngine.Amount
|
||||
|
||||
private var isIncludeFee: Boolean = true
|
||||
private var requestPIN2Count = 0
|
||||
private var nodeCheck = true
|
||||
private var dtVerified: Date? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val callback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
navigateBackWithResult(Activity.RESULT_CANCELED)
|
||||
}
|
||||
}
|
||||
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val engine = CoinEngineFactory.createCardano(ctx)
|
||||
|
||||
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
Html.fromHtml(engine!!.balanceHTML, Html.FROM_HTML_MODE_LEGACY)
|
||||
else
|
||||
Html.fromHtml(engine!!.balanceHTML)
|
||||
tvBalance.text = html
|
||||
|
||||
isIncludeFee = arguments?.getBoolean(Constant.EXTRA_FEE_INCLUDED, true) ?: true
|
||||
|
||||
if (isIncludeFee)
|
||||
tvIncFee.setText(R.string.confirm_transaction_including_fee)
|
||||
else
|
||||
tvIncFee.setText(R.string.confirm_transaction_not_including_fee)
|
||||
|
||||
amount = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_AMOUNT) ?: "0",
|
||||
arguments?.getString(Constant.EXTRA_AMOUNT_CURRENCY) ?: "")
|
||||
|
||||
if (engine.allowSelectFeeInclusion())
|
||||
tvIncFee.visibility = View.VISIBLE
|
||||
else
|
||||
tvIncFee.visibility = View.INVISIBLE
|
||||
|
||||
// if (ctx.card.blockchainID == Blockchain.Token.id) {
|
||||
// // for Blockchain.Token limit decimals
|
||||
// etAmount.setText(amount.toValueString(ctx.card.tokensDecimal))
|
||||
// } else {
|
||||
// for others
|
||||
etAmount.setText(amount.toValueString())
|
||||
// }
|
||||
|
||||
tvCurrency.text = engine.balanceCurrency
|
||||
tvCurrency2.text = engine.feeCurrency
|
||||
// tvCardID.text = ctx.card.cidDescription
|
||||
etWallet.setText(arguments?.getString(Constant.EXTRA_TARGET_ADDRESS))
|
||||
|
||||
btnSend.visibility = View.INVISIBLE
|
||||
|
||||
if (!engine.allowSelectFeeLevel()) {
|
||||
rgFee.visibility = View.GONE
|
||||
}
|
||||
|
||||
// set listeners
|
||||
rgFee.setOnCheckedChangeListener { _, checkedId -> doSetFee(checkedId) }
|
||||
etFee.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
try {
|
||||
val eqFee = engine.evaluateFeeEquivalent(etFee!!.text.toString())
|
||||
tvFeeEquivalent.text = eqFee
|
||||
|
||||
if (!ctx.coinData!!.amountEquivalentDescriptionAvailable) {
|
||||
tvFeeEquivalent.error = getString(R.string.confirm_transaction_error_service_unavailable)
|
||||
tvCurrency2.visibility = View.GONE
|
||||
tvFeeEquivalent.visibility = View.GONE
|
||||
} else
|
||||
tvFeeEquivalent.error = null
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
tvFeeEquivalent.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
|
||||
}
|
||||
})
|
||||
btnSend.setOnClickListener {
|
||||
if (UtilHelper.isOnline(requireContext())) {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.add(Calendar.MINUTE, -1)
|
||||
|
||||
if (dtVerified == null || dtVerified!!.before(calendar.time)) {
|
||||
finishWithError(Activity.RESULT_CANCELED, getString(R.string.confirm_transaction_error_data_is_outdated))
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
val engineCoin = CoinEngineFactory.createCardano(ctx)
|
||||
|
||||
if (engineCoin!!.isNeedCheckNode && !nodeCheck) {
|
||||
Toast.makeText(context, getString(R.string.confirm_transaction_error_cannot_reach_node), Toast.LENGTH_LONG).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
val txFee = engineCoin.convertToAmount(etFee.text.toString(), tvCurrency2.text.toString())
|
||||
val txAmount = engineCoin.convertToAmount(etAmount.text.toString(), tvCurrency.text.toString())
|
||||
|
||||
// if (!engineCoin.hasBalanceInfo()) {
|
||||
// finishWithError(Activity.RESULT_CANCELED, getString(R.string.cannot_check_balance_no_connection_with_blockchain_nodes))
|
||||
// return@setOnClickListener
|
||||
//
|
||||
// } else if (!engineCoin.isBalanceNotZero) {
|
||||
// finishWithError(Activity.RESULT_CANCELED, getString(R.string.the_wallet_is_empty))
|
||||
// return@setOnClickListener
|
||||
//
|
||||
// } else if (!engineCoin.isExtractPossible) {
|
||||
// finishWithError(Activity.RESULT_CANCELED, getString(R.string.please_wait_for_confirmation_of_incoming_transaction))
|
||||
// return@setOnClickListener
|
||||
// }
|
||||
//
|
||||
// if (!engineCoin.checkNewTransactionAmountAndFee(txAmount, txFee, isIncludeFee)) {
|
||||
// finishWithError(Activity.RESULT_CANCELED, getString(R.string.not_enough_funds_on_your_card))
|
||||
// return@setOnClickListener
|
||||
// }
|
||||
|
||||
requestPIN2Count = 0
|
||||
// val intent = Intent(baseContext, PinRequestActivity::class.java)
|
||||
// intent.putExtra(Constant.EXTRA_MODE, PinRequestActivity.Mode.RequestPIN2.toString())
|
||||
// ctx.saveToIntent(intent)
|
||||
// intent.putExtra(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
|
||||
// startActivityForResult(intent, Constant.REQUEST_CODE_REQUEST_PIN2_)
|
||||
|
||||
//TODO - PIN2 workaround
|
||||
PINStorage.setPIN2(PINStorage.getDefaultPIN2())
|
||||
val data = Bundle()
|
||||
ctx.saveToBundle(data)
|
||||
data.putString(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
|
||||
data.putString(Constant.EXTRA_AMOUNT, etAmount.text.toString())
|
||||
data.putString(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
|
||||
data.putString(Constant.EXTRA_FEE, etFee.text.toString())
|
||||
data.putString(Constant.EXTRA_FEE_CURRENCY, tvCurrency2.text.toString())
|
||||
data.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
|
||||
navigateForResult(
|
||||
Constant.REQUEST_CODE_SIGN_TRANSACTION,
|
||||
R.id.action_confirmTransactionFragment_to_signTransactionFragment,
|
||||
data)
|
||||
} else
|
||||
Toast.makeText(context, getString(R.string.general_error_no_connection), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
progressBar.visibility = View.VISIBLE
|
||||
|
||||
if (!navigatedBack) requestFee(engine)
|
||||
}
|
||||
|
||||
private fun requestFee(engine: CoinEngine) {
|
||||
engine.requestFee(
|
||||
object : CoinEngine.BlockchainRequestsCallbacks {
|
||||
override fun onComplete(success: Boolean) {
|
||||
if (success) {
|
||||
onProgress()
|
||||
progressBar.visibility = View.INVISIBLE
|
||||
dtVerified = Date()
|
||||
} else {
|
||||
finishWithError(Activity.RESULT_CANCELED, ctx.error)
|
||||
}
|
||||
}
|
||||
override fun onProgress() {
|
||||
doSetFee(rgFee.checkedRadioButtonId)
|
||||
}
|
||||
override fun allowAdvance(): Boolean {
|
||||
return UtilHelper.isOnline(requireContext())
|
||||
}
|
||||
},
|
||||
etWallet.text.toString(),
|
||||
amount)
|
||||
}
|
||||
|
||||
override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
|
||||
if (requestCode == Constant.REQUEST_CODE_SIGN_TRANSACTION) {
|
||||
if (data != null) {
|
||||
if (data.containsKey(EXTRA_TANGEM_CARD_UID) && data.containsKey(EXTRA_TANGEM_CARD)) {
|
||||
val updatedCard = TangemCard(data.getString(EXTRA_TANGEM_CARD_UID))
|
||||
updatedCard.loadFromBundle(data.getBundle(EXTRA_TANGEM_CARD)!!)
|
||||
ctx.card = updatedCard
|
||||
}
|
||||
}
|
||||
if (resultCode == Constant.RESULT_INVALID_PIN_ && requestPIN2Count < 2) {
|
||||
requestPIN2Count++
|
||||
val bundle = Bundle()
|
||||
bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString())
|
||||
ctx.saveToBundle(bundle)
|
||||
bundle.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
|
||||
navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_,
|
||||
R.id.action_confirmTransactionFragment_to_pinRequestFragment,
|
||||
bundle)
|
||||
return
|
||||
|
||||
}
|
||||
navigateBackWithResult(resultCode, data)
|
||||
|
||||
} else if (requestCode == Constant.REQUEST_CODE_REQUEST_PIN2_) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val bundle = Bundle()
|
||||
ctx.saveToBundle(bundle)
|
||||
bundle.putString(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
|
||||
bundle.putString(Constant.EXTRA_AMOUNT, etAmount.text.toString())
|
||||
bundle.putString(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
|
||||
bundle.putString(Constant.EXTRA_FEE, etFee.text.toString())
|
||||
bundle.putString(Constant.EXTRA_FEE_CURRENCY, tvCurrency2.text.toString())
|
||||
bundle.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
|
||||
navigateForResult(Constant.REQUEST_CODE_SIGN_TRANSACTION,
|
||||
R.id.action_confirmTransactionFragment_to_signTransactionFragment,
|
||||
bundle)
|
||||
} else
|
||||
Toast.makeText(context, R.string.confirm_transaction_error_pin_2_is_required, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTagDiscovered(tag: Tag) {
|
||||
try {
|
||||
(activity as MainActivity).nfcManager.ignoreTag(tag)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun doSetFee(checkedRadioButtonId: Int) {
|
||||
var txtFee = ""
|
||||
when (checkedRadioButtonId) {
|
||||
R.id.rbMinimalFee ->
|
||||
if (ctx.coinData.minFee != null) {
|
||||
txtFee = ctx.coinData.minFee!!.toValueString()
|
||||
btnSend.visibility = View.VISIBLE
|
||||
} else
|
||||
btnSend.visibility = View.INVISIBLE
|
||||
|
||||
R.id.rbNormalFee ->
|
||||
if (ctx.coinData.normalFee != null) {
|
||||
txtFee = ctx.coinData.normalFee!!.toValueString()
|
||||
btnSend.visibility = View.VISIBLE
|
||||
} else
|
||||
btnSend.visibility = View.INVISIBLE
|
||||
|
||||
R.id.rbMaximumFee ->
|
||||
if (ctx.coinData.maxFee != null) {
|
||||
txtFee = ctx.coinData.maxFee!!.toValueString()
|
||||
btnSend.visibility = View.VISIBLE
|
||||
} else
|
||||
btnSend.visibility = View.INVISIBLE
|
||||
}
|
||||
etFee.setText(txtFee.replace(',', '.'))
|
||||
}
|
||||
|
||||
private fun finishWithError(errorCode: Int, message: String) {
|
||||
val transactionFinishWithError = TransactionFinishWithError()
|
||||
transactionFinishWithError.message = message
|
||||
EventBus.getDefault().post(transactionFinishWithError)
|
||||
|
||||
navigateBackWithResult(errorCode, bundleOf(Constant.EXTRA_MESSAGE to message))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
package com.tangem.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.View.OnTouchListener
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import com.tangem.Constant
|
||||
import com.tangem.data.dp.PrefsManager
|
||||
import com.tangem.ui.activity.MainActivity
|
||||
import com.tangem.ui.event.TransactionFinishWithError
|
||||
import com.tangem.ui.event.TransactionFinishWithSuccess
|
||||
import com.tangem.ui.fragment.BaseFragment
|
||||
import com.tangem.ui.navigation.NavigationResultListener
|
||||
import com.tangem.util.UtilHelper
|
||||
import com.tangem.wallet.CoinEngineFactory
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.TangemContext
|
||||
import kotlinx.android.synthetic.tangemCardano.fragment_prepare_transaction.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import java.io.IOException
|
||||
|
||||
class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback {
|
||||
|
||||
override val layoutId = R.layout.fragment_prepare_transaction
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val engine = CoinEngineFactory.createCardano(TangemContext())
|
||||
|
||||
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
Html.fromHtml(engine?.balanceHTML, Html.FROM_HTML_MODE_LEGACY)
|
||||
else
|
||||
Html.fromHtml(engine?.balanceHTML)
|
||||
tvBalance.text = html
|
||||
|
||||
if (!engine!!.allowSelectFeeInclusion()) {
|
||||
rgIncFee.visibility = View.INVISIBLE
|
||||
} else {
|
||||
rgIncFee.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
tvCurrency.text = engine.balance?.currency
|
||||
etAmount.setText(engine.balance?.toValueString())
|
||||
|
||||
// limit number of symbols after comma
|
||||
etAmount.filters = engine.amountInputFilters
|
||||
|
||||
etWallet.setText(PrefsManager.getInstance().lastWalletAddress)
|
||||
|
||||
// set listeners
|
||||
etAmount.setOnEditorActionListener { lv, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
val imm = lv.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(lv.windowToken, 0)
|
||||
lv.clearFocus()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
btnVerify.setOnClickListener {
|
||||
if (!UtilHelper.isOnline(requireContext())) {
|
||||
Toast.makeText(context, R.string.general_error_no_connection, Toast.LENGTH_LONG).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
// check on empty wallet address
|
||||
if (etWallet.text.toString() == "") {
|
||||
etWallet.error = getString(R.string.general_wallet_empty)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
// check on empty amount
|
||||
if (etAmount.text.toString() == "") {
|
||||
etAmount.error = getString(R.string.prepare_transaction_error_amount_empty)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
val strAmount: String = etAmount.text.toString().replace(",", ".")
|
||||
val amount = engine.convertToAmount(etAmount.text.toString(), tvCurrency.text.toString())
|
||||
|
||||
// check amount
|
||||
try {
|
||||
if (!engine.checkNewTransactionAmount(amount))
|
||||
etAmount.error = getString(R.string.prepare_transaction_error_not_enough_funds)
|
||||
else
|
||||
etAmount.error = null
|
||||
} catch (e: Exception) {
|
||||
etAmount.error = getString(R.string.prepare_transaction_error_unknown_amount_format)
|
||||
}
|
||||
|
||||
// check wallet address
|
||||
if (!engine.validateAddress(etWallet.text.toString())) {
|
||||
etWallet.error = getString(R.string.prepare_transaction_error_incorrect_destination)
|
||||
return@setOnClickListener
|
||||
} else
|
||||
etWallet.error = null
|
||||
|
||||
if (!etAmount.error.isNullOrEmpty() || !etWallet.error.isNullOrEmpty()) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
PrefsManager.getInstance().saveLastWalletAddress(etWallet.text.toString())
|
||||
|
||||
val data = Bundle()
|
||||
data.putString(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
|
||||
data.putBoolean(Constant.EXTRA_FEE_INCLUDED, (rgIncFee!!.checkedRadioButtonId == R.id.rbFeeIn))
|
||||
data.putString(Constant.EXTRA_AMOUNT, strAmount)
|
||||
data.putString(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
|
||||
navigateForResult(
|
||||
Constant.REQUEST_CODE_SEND_TRANSACTION__,
|
||||
R.id.action_prepareTransactionFragment_to_confirmTransactionFragment,
|
||||
data)
|
||||
}
|
||||
|
||||
ivCamera.setOnClickListener {
|
||||
etWallet.text.clear()
|
||||
navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareTransactionFragment_to_qrScanFragment)
|
||||
}
|
||||
|
||||
etWallet.setOnTouchListener(OnTouchListener { _, event ->
|
||||
if (event.action == MotionEvent.ACTION_UP) {
|
||||
if (event.rawX >= etWallet.right - etWallet.compoundDrawables[2].bounds.width()) {
|
||||
etWallet.text.clear()
|
||||
return@OnTouchListener true
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (!EventBus.getDefault().isRegistered(this))
|
||||
EventBus.getDefault().register(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
EventBus.getDefault().unregister(this)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onTransactionFinishWithSuccess(transactionFinishWithSuccess: TransactionFinishWithSuccess) {
|
||||
transactionFinishWithSuccess.message?.let { message ->
|
||||
(activity as MainActivity).toastHelper.showSnackbarSuccess(requireContext(), cl, message)
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onTransactionFinishWithError(transactionFinishWithError: TransactionFinishWithError) {
|
||||
transactionFinishWithError.message?.let { message ->
|
||||
(activity as MainActivity).toastHelper.showSnackbarError(requireContext(), cl, message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
|
||||
if (requestCode == Constant.REQUEST_CODE_SCAN_QR && resultCode == Activity.RESULT_OK && data != null && data.containsKey("QRCode")) {
|
||||
val code = data.getString("QRCode")
|
||||
// when (ctx.blockchain) {
|
||||
// Blockchain.Bitcoin -> {
|
||||
// if (code.contains("bitcoin:")) {
|
||||
// val tmp = code.split("bitcoin:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
// code = tmp[1]
|
||||
// }
|
||||
// }
|
||||
// Blockchain.Ethereum, Blockchain.Token -> {
|
||||
// if (code.contains("ethereum:")) {
|
||||
// val tmp = code.split("ethereum:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
// code = tmp[1]
|
||||
// } else if (code.contains("blockchain:")) {
|
||||
// val tmp = code.split("blockchain:".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
// code = tmp[1]
|
||||
// }
|
||||
// }
|
||||
// else -> {
|
||||
// }
|
||||
// }
|
||||
etWallet?.setText(code)
|
||||
// } else if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION__) {
|
||||
// setResult(resultCode, data)
|
||||
// finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTagDiscovered(tag: Tag) {
|
||||
try {
|
||||
(activity as MainActivity).nfcManager.ignoreTag(tag)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,362 +0,0 @@
|
|||
package com.tangem.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.media.MediaPlayer
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import com.tangem.App
|
||||
import com.tangem.Constant
|
||||
import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation
|
||||
import com.tangem.tangem_sdk.android.reader.NfcManager
|
||||
import com.tangem.tangem_sdk.android.reader.NfcReader
|
||||
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD
|
||||
import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID
|
||||
import com.tangem.tangem_sdk.data.asBundle
|
||||
import com.tangem.tangem_card.data.TangemCard
|
||||
import com.tangem.tangem_card.reader.CardCrypto
|
||||
import com.tangem.tangem_card.reader.CardProtocol
|
||||
import com.tangem.tangem_card.tasks.CustomReadCardTask
|
||||
import com.tangem.tangem_card.tasks.OneTouchSignTask
|
||||
import com.tangem.tangem_card.tasks.SignTask
|
||||
import com.tangem.tangem_card.util.Log
|
||||
import com.tangem.wallet.CoinEngine
|
||||
import com.tangem.wallet.CoinEngineFactory
|
||||
import com.tangem.wallet.TangemContext
|
||||
import com.tangem.ui.activity.MainActivity
|
||||
import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
|
||||
import com.tangem.ui.dialog.WaitSecurityDelayDialog
|
||||
import com.tangem.ui.fragment.BaseFragment
|
||||
import com.tangem.ui.navigation.NavigationResultListener
|
||||
import com.tangem.util.LOG
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.layout_progress_horizontal.*
|
||||
import kotlinx.android.synthetic.main.layout_touch_card.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class SignTransactionFragment : BaseFragment(), NavigationResultListener,
|
||||
NfcAdapter.ReaderCallback, CardProtocol.Notifications {
|
||||
|
||||
companion object {
|
||||
val TAG: String = SignTransactionFragment::class.java.simpleName
|
||||
}
|
||||
|
||||
override val layoutId = R.layout.fragment_sign_transaction
|
||||
|
||||
private lateinit var ctx: TangemContext
|
||||
private lateinit var mpFinishSignSound: MediaPlayer
|
||||
|
||||
private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation
|
||||
|
||||
private var task: CustomReadCardTask? = null
|
||||
|
||||
private lateinit var amount: CoinEngine.Amount
|
||||
private lateinit var fee: CoinEngine.Amount
|
||||
private var isIncludeFee = true
|
||||
private var outAddressStr: String? = null
|
||||
private var lastReadSuccess = true
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
ctx = TangemContext.loadFromBundle(context, arguments)
|
||||
|
||||
val callback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
navigateBackWithResult(Activity.RESULT_CANCELED)
|
||||
}
|
||||
}
|
||||
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
mpFinishSignSound = MediaPlayer.create(context, R.raw.scan_card_sound)
|
||||
|
||||
// init NFC Antenna
|
||||
nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
|
||||
nfcDeviceAntenna.init()
|
||||
|
||||
amount = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_AMOUNT), arguments?.getString(Constant.EXTRA_AMOUNT_CURRENCY))
|
||||
fee = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_FEE), arguments?.getString(Constant.EXTRA_FEE_CURRENCY))
|
||||
isIncludeFee = arguments?.getBoolean(Constant.EXTRA_FEE_INCLUDED, true) ?: true
|
||||
outAddressStr = arguments?.getString(Constant.EXTRA_TARGET_ADDRESS)
|
||||
|
||||
// tvCardID.text = ctx.card!!.cidDescription
|
||||
progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
|
||||
progressBar.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
task?.cancel(true)
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
task?.cancel(true)
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
|
||||
if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION_) {
|
||||
navigateBackWithResult(resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTagDiscovered(tag: Tag) {
|
||||
try {
|
||||
// get IsoDep handle and run cardReader thread
|
||||
val isoDep = IsoDep.get(tag)
|
||||
|
||||
isoDep.timeout = 65000
|
||||
|
||||
val coinEngine = CoinEngineFactory.createCardano(ctx)
|
||||
coinEngine?.setOnNeedSendTransaction { tx ->
|
||||
if (tx != null) {
|
||||
val data = Bundle()
|
||||
ctx.saveToBundle(data)
|
||||
data.putByteArray(Constant.EXTRA_TX, tx)
|
||||
navigateForResult(
|
||||
Constant.REQUEST_CODE_SEND_TRANSACTION_,
|
||||
R.id.action_signTransactionFragment_to_sendTransactionFragment,
|
||||
data)
|
||||
}
|
||||
}
|
||||
|
||||
val tx: OneTouchSignTask.TransactionToSign = object : OneTouchSignTask.TransactionToSign {
|
||||
var txToSign: SignTask.TransactionToSign? = null
|
||||
suspend fun requestBalanceAndUnspentTransactions(): Boolean = suspendCoroutine { cont ->
|
||||
coinEngine!!.requestBalanceAndUnspentTransactions(object : CoinEngine.BlockchainRequestsCallbacks {
|
||||
override fun onComplete(success: Boolean?) {
|
||||
cont.resume(success!!)
|
||||
}
|
||||
|
||||
override fun onProgress() {
|
||||
}
|
||||
|
||||
override fun allowAdvance(): Boolean {
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun initData(card: TangemCard) {
|
||||
if (ctx.card == null) {
|
||||
ctx.card = card
|
||||
}
|
||||
if (ctx.coinData == null) {
|
||||
ctx.coinData = CoinEngineFactory.createCardanoData()
|
||||
}
|
||||
if (ctx.coinData.wallet.isNullOrEmpty()) {
|
||||
coinEngine?.defineWallet()
|
||||
runBlocking { requestBalanceAndUnspentTransactions() }
|
||||
Log.e(MainActivity.TAG, "requestBalanceAndUnspentTransactions completed")
|
||||
}
|
||||
if (txToSign == null) txToSign = coinEngine!!.constructTransaction(amount, fee, isIncludeFee, outAddressStr)
|
||||
}
|
||||
|
||||
override fun isSigningOnCardSupported(card: TangemCard?): Boolean {
|
||||
initData(card!!)
|
||||
return CoinEngineFactory.isCardano(card?.blockchainID) and (txToSign!!.isSigningMethodSupported(card?.signingMethod))
|
||||
}
|
||||
|
||||
override fun isIssuerCanSignData(card: TangemCard?): Boolean {
|
||||
return try {
|
||||
card?.issuer?.privateDataKey!=null
|
||||
} catch (e: java.lang.Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun isIssuerCanSignTransaction(card: TangemCard?): Boolean {
|
||||
return try {
|
||||
card?.issuer?.privateTransactionKey!=null
|
||||
} catch (e: java.lang.Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getHashesToSign(card: TangemCard?): Array<ByteArray> {
|
||||
initData(card!!)
|
||||
return txToSign!!.hashesToSign
|
||||
}
|
||||
|
||||
override fun getRawDataToSign(card: TangemCard?): ByteArray {
|
||||
initData(card!!)
|
||||
return txToSign!!.rawDataToSign
|
||||
}
|
||||
|
||||
override fun getHashAlgToSign(card: TangemCard?): String {
|
||||
initData(card!!)
|
||||
return txToSign!!.hashAlgToSign
|
||||
}
|
||||
|
||||
override fun getIssuerTransactionSignature(card: TangemCard?, dataToSignByIssuer: ByteArray?): ByteArray {
|
||||
initData(card!!)
|
||||
if( card.issuer!=null && card.issuer.privateTransactionKey!=null)
|
||||
{
|
||||
return CardCrypto.Signature(card.issuer.privateTransactionKey, dataToSignByIssuer)
|
||||
}
|
||||
return txToSign!!.getIssuerTransactionSignature(dataToSignByIssuer)
|
||||
}
|
||||
|
||||
override fun onSignCompleted(card: TangemCard?, signature: ByteArray?) {
|
||||
initData(card!!)
|
||||
txToSign!!.onSignCompleted(signature)
|
||||
}
|
||||
|
||||
}
|
||||
task = OneTouchSignTask(NfcReader((activity as MainActivity).nfcManager, isoDep),
|
||||
App.localStorage, App.pinStorage, this, tx)
|
||||
task!!.start()
|
||||
|
||||
} catch (e: CardProtocol.TangemException_WrongAmount) {
|
||||
try {
|
||||
val data = Bundle()
|
||||
data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount))
|
||||
data.putString(EXTRA_TANGEM_CARD_UID, ctx.card.uid)
|
||||
data.putBundle(EXTRA_TANGEM_CARD, ctx.card.asBundle)
|
||||
navigateBackWithResult(Activity.RESULT_CANCELED, data)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReadStart(cardProtocol: CardProtocol) {
|
||||
rlProgressBar.post { rlProgressBar.visibility = View.VISIBLE }
|
||||
|
||||
progressBar?.post {
|
||||
progressBar?.visibility = View.VISIBLE
|
||||
progressBar?.progress = 5
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReadProgress(protocol: CardProtocol, progress: Int) {
|
||||
progressBar?.post { progressBar!!.progress = progress }
|
||||
}
|
||||
|
||||
override fun onReadFinish(cardProtocol: CardProtocol?) {
|
||||
task = null
|
||||
if (cardProtocol != null) {
|
||||
if (cardProtocol.error == null) {
|
||||
rlProgressBar.post { rlProgressBar.visibility = View.GONE }
|
||||
|
||||
progressBar?.post {
|
||||
progressBar?.progress = 100
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.GREEN)
|
||||
}
|
||||
|
||||
mpFinishSignSound.start()
|
||||
} else {
|
||||
lastReadSuccess = false
|
||||
if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) {
|
||||
progressBar?.post {
|
||||
progressBar?.progress = 100
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.RED)
|
||||
}
|
||||
progressBar?.postDelayed({
|
||||
try {
|
||||
progressBar?.progress = 0
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
|
||||
progressBar?.visibility = View.INVISIBLE
|
||||
val data = Bundle()
|
||||
data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_cannot_sign))
|
||||
data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid)
|
||||
data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle)
|
||||
navigateBackWithResult(Constant.RESULT_INVALID_PIN_, data)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, 500)
|
||||
} else {
|
||||
if (cardProtocol.error is CardProtocol.TangemException_WrongAmount) {
|
||||
try {
|
||||
val data = Bundle()
|
||||
data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount))
|
||||
data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid)
|
||||
data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle)
|
||||
navigateBackWithResult(Activity.RESULT_CANCELED, data)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
progressBar?.post {
|
||||
if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) {
|
||||
if (!NoExtendedLengthSupportDialog.allReadyShowed) {
|
||||
NoExtendedLengthSupportDialog.message = getText(R.string.dialog_the_nfc_adapter_length_apdu).toString() + "\n" + getText(R.string.dialog_the_nfc_adapter_length_apdu_advice).toString()
|
||||
NoExtendedLengthSupportDialog().show(requireFragmentManager(), NoExtendedLengthSupportDialog.TAG)
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(context, R.string.general_notification_scan_again, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
progressBar?.progress = 100
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.RED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rlProgressBar.postDelayed({
|
||||
try {
|
||||
rlProgressBar.visibility = View.GONE
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, 500)
|
||||
|
||||
progressBar?.postDelayed({
|
||||
try {
|
||||
progressBar?.progress = 0
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
|
||||
progressBar?.visibility = View.INVISIBLE
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
override fun onReadCancel() {
|
||||
task = null
|
||||
|
||||
progressBar?.postDelayed({
|
||||
try {
|
||||
progressBar?.progress = 0
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
|
||||
progressBar?.visibility = View.INVISIBLE
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
override fun onReadBeforeRequest(timeout: Int) {
|
||||
LOG.i(TAG, "onReadBeforeRequest timeout $timeout")
|
||||
WaitSecurityDelayDialog.onReadBeforeRequest(activity, timeout)
|
||||
}
|
||||
|
||||
override fun onReadAfterRequest() {
|
||||
LOG.i(TAG, "onReadAfterRequest")
|
||||
WaitSecurityDelayDialog.onReadAfterRequest(activity)
|
||||
}
|
||||
|
||||
override fun onReadWait(msec: Int) {
|
||||
LOG.i(TAG, "onReadWait msec $msec")
|
||||
WaitSecurityDelayDialog.onReadWait(activity, msec)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,344 +0,0 @@
|
|||
<?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="match_parent"
|
||||
tools:ignore="Autofill">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="@string/general_send_transaction"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTransaction"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/btn_light"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:paddingBottom="8dp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_from_banknote"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCardID"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
tools:text="BB00 0000 1210 0233" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_balance"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvBalance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
tools:text="4.51735 BTC" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_send_to_wallet"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etWallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:fontFamily="@font/maax"
|
||||
android:hint="@string/confirm_transaction_hint_target_address"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLines="2"
|
||||
android:padding="12dp"
|
||||
android:singleLine="false"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_1_small"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_amount"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvIncFee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginBottom="1dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/textView3"
|
||||
app:layout_constraintLeft_toRightOf="@+id/textView3"
|
||||
tools:text="(including fee)" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAmount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789.,"
|
||||
android:fontFamily="@font/maax"
|
||||
android:hint="@string/prepare_transaction_hint_enter_amount"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:textSize="@dimen/text_size_1_large"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tvCurrency"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_min="150dp"
|
||||
tools:text="1.34343434343443434343434" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCurrency"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_btc"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_1_large"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/etAmount"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llFee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintTop_toBottomOf="@+id/llTransaction">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView4"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/confirm_transaction_fee"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<info.hoang8f.android.segmented.SegmentedGroup
|
||||
android:id="@+id/rgFee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:checkedButton="@+id/rbNormalFee"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:sc_border_width="2dp"
|
||||
app:sc_corner_radius="10dp"
|
||||
app:sc_tint_color="@color/colorPrimary">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbMinimalFee"
|
||||
style="@style/RadioButton"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/confirm_transaction_btn_fee_minimal"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbNormalFee"
|
||||
style="@style/RadioButton"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/confirm_transaction_btn_fee_normal"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbMaximumFee"
|
||||
style="@style/RadioButton"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/confirm_transaction_btn_fee_priority"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
</info.hoang8f.android.segmented.SegmentedGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etFee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:enabled="false"
|
||||
android:fontFamily="@font/maax"
|
||||
android:hint="@string/confirm_transaction_hint_fee_amount"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:textColor="@color/black"
|
||||
android:textColorLink="@android:color/holo_blue_dark"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_min="100dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCurrency2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_btc"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="@+id/etFee"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFeeEquivalent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:textColor="@android:color/darker_gray"
|
||||
android:textSize="@dimen/text_size_1_small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="@+id/tvCurrency2"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSend"
|
||||
style="@style/AppTheme.RoundedCornerMaterialButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/send_1"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="@android:style/Widget.Holo.Light.ProgressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:elevation="1dp"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colorPrimary"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/llFee" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,267 +0,0 @@
|
|||
<?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:id="@+id/cl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:ignore="contentDescription">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="@string/general_send_transaction"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llFrom"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/btn_light"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="24dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_from_banknote"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCardID"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
tools:text="BB00 0000 1210 0233" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_balance"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvBalance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
tools:text="4.51735 Btc" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@color/card_state_loaded_with_zero"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="32dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/llFrom">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_send_to_wallet"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivCamera"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:src="@drawable/qr_scan"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etWallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:drawableEnd="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:fontFamily="@font/maax"
|
||||
android:hint="@string/prepare_transaction_hint_enter_address"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="text|textMultiLine|textNoSuggestions"
|
||||
android:padding="12dp"
|
||||
android:singleLine="false"
|
||||
android:textColor="@color/colorPrimaryDark"
|
||||
android:textColorLink="@android:color/holo_blue_dark"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toLeftOf="@+id/ivCamera"
|
||||
tools:layout_editor_absoluteY="2dp" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="7dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_amount"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAmount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789.,"
|
||||
android:fontFamily="@font/maax"
|
||||
android:hint="@string/prepare_transaction_hint_enter_amount"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="numberDecimal"
|
||||
android:padding="12dp"
|
||||
android:textColor="@color/colorPrimaryDark"
|
||||
android:textSize="@dimen/text_size_1_large"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tvCurrency"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_min="150dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCurrency"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/general_btc"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="@dimen/text_size_1_large"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/etAmount"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<info.hoang8f.android.segmented.SegmentedGroup
|
||||
android:id="@+id/rgIncFee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:checkedButton="@+id/rbFeeIn"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:sc_border_width="2dp"
|
||||
app:sc_corner_radius="10dp"
|
||||
app:sc_tint_color="@color/colorPrimary"
|
||||
tools:layout_editor_absoluteX="10dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbFeeIn"
|
||||
style="@style/RadioButton"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/confirm_transaction_btn_including_fee"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbFeeOut"
|
||||
style="@style/RadioButton"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/confirm_transaction_btn_not_including_fee"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
</info.hoang8f.android.segmented.SegmentedGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnVerify"
|
||||
style="@style/AppTheme.RoundedCornerMaterialButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:fontFamily="@font/saira_condensed_bold"
|
||||
android:text="@string/verify"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
<?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="match_parent">
|
||||
|
||||
<include
|
||||
layout="@layout/layout_touch_card"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="250dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.75">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/now_touch_the_banknote_with_id"
|
||||
android:textAlignment="center"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCardID"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:visibility="gone"
|
||||
tools:text="CB02 0000 0002 5000" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="@string/to_sign_the_transaction"
|
||||
android:textAlignment="center"
|
||||
android:textSize="@dimen/text_size_medium" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/layout_progress_horizontal" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name" translatable="false">Cardano</string>
|
||||
|
||||
<string name="verify">Confirm</string>
|
||||
<string name="send_1">Checkout</string>
|
||||
<string name="now_touch_the_banknote_with_id">Now touch the card</string>
|
||||
|
||||
</resources>
|
||||
1
blockchain-demo/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
63
blockchain-demo/build.gradle
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.2"
|
||||
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.example.blockchain_demo"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'lib/x86_64/darwin/libscrypt.dylib'
|
||||
exclude 'lib/x86_64/freebsd/libscrypt.so'
|
||||
exclude 'lib/x86_64/linux/libscrypt.so'
|
||||
}
|
||||
|
||||
viewBinding {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':tangem-core')
|
||||
implementation project(':tangem-sdk')
|
||||
implementation project(':blockchain')
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.3"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
}
|
||||
21
blockchain-demo/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.blockchain_demo
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.example.blockchain_demo", appContext.packageName)
|
||||
}
|
||||
}
|
||||
51
blockchain-demo/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.tangem.blockchain_demo">
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.nfc"
|
||||
android:required="true" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name="com.tangem.blockchain_demo.BlockchainDemoActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data
|
||||
android:host="www.tangem.com"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.tangem.com"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="tangem.com"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="tangem.com"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.TECH_DISCOVERED" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.nfc.action.TECH_DISCOVERED"
|
||||
android:resource="@xml/nfc_tech_filter" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
package com.tangem.blockchain_demo
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.extensions.Result
|
||||
import com.tangem.blockchain.common.extensions.Signer
|
||||
import com.tangem.blockchain.common.extensions.SimpleResult
|
||||
import com.tangem.blockchain_demo.databinding.ActivityBlockchainDemoBinding
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tasks.ScanEvent
|
||||
import com.tangem.tasks.TaskError
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import kotlinx.coroutines.*
|
||||
import java.math.BigDecimal
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class BlockchainDemoActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var cardManager: CardManager
|
||||
private lateinit var signer: TransactionSigner
|
||||
private lateinit var card: Card
|
||||
private lateinit var walletManager: WalletManager
|
||||
private var issuerDataCounter: Int = 1
|
||||
|
||||
private lateinit var fee: BigDecimal
|
||||
|
||||
private lateinit var binding: ActivityBlockchainDemoBinding
|
||||
|
||||
private val parentJob = Job()
|
||||
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||
handleError(throwable.localizedMessage)
|
||||
}
|
||||
private val coroutineContext: CoroutineContext
|
||||
get() = parentJob + Dispatchers.IO + exceptionHandler
|
||||
private val scope = CoroutineScope(coroutineContext)
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityBlockchainDemoBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
|
||||
cardManager = CardManager.init(this)
|
||||
signer = Signer(cardManager)
|
||||
|
||||
binding.btnScan.setOnClickListener { scan() }
|
||||
|
||||
binding.btnCheckFee.setOnClickListener { requestFee() }
|
||||
|
||||
binding.btnSend.setOnClickListener { send() }
|
||||
}
|
||||
|
||||
|
||||
private fun scan() {
|
||||
cardManager.scanCard { taskEvent ->
|
||||
when (taskEvent) {
|
||||
is TaskEvent.Event -> {
|
||||
when (taskEvent.data) {
|
||||
is ScanEvent.OnReadEvent -> {
|
||||
card = (taskEvent.data as ScanEvent.OnReadEvent).card
|
||||
walletManager = WalletManagerFactory.makeWalletManager(card)!!
|
||||
getInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
is TaskEvent.Completion -> {
|
||||
if (taskEvent.error != null) {
|
||||
if (taskEvent.error !is TaskError.UserCancelled) {
|
||||
handleError(taskEvent.error.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInfo() {
|
||||
scope.launch {
|
||||
walletManager.update()
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.tvBalance.text =
|
||||
"${walletManager.wallet.balances[AmountType.Coin]?.value
|
||||
?: "error"} ${walletManager.blockchain.currency}"
|
||||
val token = walletManager.wallet.balances[AmountType.Token]
|
||||
if (token != null) {
|
||||
binding.tvBalance.text = token.currencySymbol + " " + token.value
|
||||
binding.etSumToSend.text = Editable.Factory.getInstance().newEditable(
|
||||
token.value.toString()
|
||||
)
|
||||
} else {
|
||||
binding.etSumToSend.text =
|
||||
Editable.Factory.getInstance().newEditable(
|
||||
walletManager.wallet.balances[AmountType.Coin]?.value.toString()
|
||||
)
|
||||
}
|
||||
binding.btnCheckFee.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestFee() {
|
||||
if (binding.etReceiverAddress.text.isBlank()) {
|
||||
Toast.makeText(this, "Please enter receiver address", Toast.LENGTH_LONG).show()
|
||||
return
|
||||
} else if (binding.etSumToSend.text.isBlank()) {
|
||||
Toast.makeText(this, "Choose sum to send", Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
val feeResult = (walletManager as FeeProvider).getFee(
|
||||
walletManager.wallet.balances[AmountType.Token]
|
||||
?: walletManager.wallet.balances[AmountType.Coin]!!,
|
||||
binding.etReceiverAddress.text.toString())
|
||||
withContext(Dispatchers.Main) {
|
||||
when (feeResult) {
|
||||
is Result.Failure -> {
|
||||
handleError(feeResult.error?.localizedMessage ?: "Error")
|
||||
}
|
||||
is Result.Success -> {
|
||||
binding.btnSend.isEnabled = true
|
||||
val fees = feeResult.data
|
||||
if (fees.size == 1) {
|
||||
binding.tvFee.text = fees[0].value.toString()
|
||||
fee = fees[0].value ?: BigDecimal(0)
|
||||
} else {
|
||||
binding.tvFee.text = fees[0].value.toString() + "\n" +
|
||||
fees[1].value.toString() + "\n" +
|
||||
fees[2].value.toString()
|
||||
fee = fees[1].value ?: BigDecimal(0)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun send() {
|
||||
scope.launch {
|
||||
val result = (walletManager as TransactionSender).send(
|
||||
formTransactionData(),
|
||||
signer)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is SimpleResult.Failure -> {
|
||||
handleError(result.error?.localizedMessage ?: "Error")
|
||||
}
|
||||
is SimpleResult.Success -> {
|
||||
binding.tvFee.text = "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formTransactionData(): TransactionData {
|
||||
val amount = if (walletManager.wallet.balances[AmountType.Token] != null) {
|
||||
walletManager.wallet.balances[AmountType.Token]!!
|
||||
} else {
|
||||
walletManager.wallet.balances[AmountType.Coin]!!.copy(
|
||||
value = binding.etSumToSend.text.toString().toBigDecimal() - fee
|
||||
)
|
||||
}
|
||||
return TransactionData(
|
||||
amount,
|
||||
walletManager.wallet.balances[AmountType.Coin]!!.copy(value = fee),
|
||||
walletManager.wallet.balances[AmountType.Coin]!!.address!!,
|
||||
binding.etReceiverAddress.text.toString()
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleError(error: String?) {
|
||||
Toast.makeText(this, error, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
170
blockchain-demo/src/main/res/drawable/ic_launcher_background.xml
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#008577"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
<?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:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".BlockchainDemoActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_balance"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="48dp"
|
||||
android:layout_marginBottom="80dp"
|
||||
android:layout_weight="3"
|
||||
android:padding="16dp"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="26sp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="BTC 0.500000034" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_fee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="48dp"
|
||||
android:padding="16dp"
|
||||
android:paddingBottom="48dp"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||
app:layout_constraintBottom_toTopOf="@id/et_receiver_address"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
tools:text="MinFee: 200, Fee: 300, Priority: 400" />
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_receiver_address"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="60dp"
|
||||
android:hint="Send to:"
|
||||
app:layout_constraintBottom_toTopOf="@id/et_sum_to_send"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_sum_to_send"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:hint="Enter amount to send"
|
||||
app:layout_constraintBottom_toTopOf="@id/btn_scan"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_scan"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Scan card and get info"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.6" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_check_fee"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:enabled="false"
|
||||
android:text="Check fee"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btn_scan" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_send"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:enabled="false"
|
||||
android:text="Send transaction"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btn_check_fee" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
BIN
blockchain-demo/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
blockchain-demo/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
blockchain-demo/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
blockchain-demo/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
blockchain-demo/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
blockchain-demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
blockchain-demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
blockchain-demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
blockchain-demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 15 KiB |
7
blockchain-demo/src/main/res/values/colors.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#527Aff</color>
|
||||
<color name="colorPrimaryDark">#027AFF</color>
|
||||
<color name="colorAccent">#027AFF</color>
|
||||
<color name="fab">#4f98c0</color>
|
||||
</resources>
|
||||
3
blockchain-demo/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">Blockchain Demo</string>
|
||||
</resources>
|
||||
11
blockchain-demo/src/main/res/values/styles.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
8
blockchain-demo/src/main/res/xml/nfc_tech_filter.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<tech-list>
|
||||
<tech>android.nfc.tech.IsoDep</tech>
|
||||
<tech>android.nfc.tech.Ndef</tech>
|
||||
<tech>android.nfc.tech.NfcV</tech>
|
||||
</tech-list>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.blockchain_demo
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ apply plugin: 'com.android.library'
|
|||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.2"
|
||||
|
|
@ -24,6 +25,13 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
@ -33,11 +41,12 @@ dependencies {
|
|||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.2.0'
|
||||
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.7.0'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.6.0'
|
||||
implementation 'com.squareup.moshi:moshi:1.9.2'
|
||||
implementation "com.squareup.moshi:moshi-kotlin:1.9.2"
|
||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.9.2")
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
|
||||
|
||||
|
|
@ -45,21 +54,21 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.3"
|
||||
|
||||
implementation 'org.bitcoinj:bitcoinj-core:0.15.2'
|
||||
implementation 'com.github.stellar:java-stellar-sdk:0.13.0'
|
||||
implementation 'com.github.stellar:java-stellar-sdk:0.14.0'
|
||||
|
||||
implementation "com.madgag.spongycastle:core:1.58.0.0"
|
||||
implementation "com.madgag.spongycastle:prov:1.58.0.0"
|
||||
|
||||
ext.kethereum_version = '0.79.5'
|
||||
implementation "com.github.walleth.kethereum:functions:$kethereum_version"
|
||||
ext.kethereum_version = '0.81.2'
|
||||
implementation "com.github.walleth.kethereum:extensions_kotlin:$kethereum_version"
|
||||
implementation "com.github.walleth.kethereum:extensions_transactions:$kethereum_version"
|
||||
implementation "com.github.walleth.kethereum:erc55:$kethereum_version"
|
||||
implementation "com.github.walleth.kethereum:keccak_shortcut:$kethereum_version"
|
||||
implementation "com.github.walleth.kethereum:wallet:$kethereum_version"
|
||||
implementation "com.github.walleth.kethereum:crypto_impl_spongycastle:$kethereum_version"
|
||||
implementation "com.github.walleth.kethereum:crypto:$kethereum_version"
|
||||
implementation "com.github.walleth.kethereum:crypto_api:$kethereum_version"
|
||||
implementation "com.github.walleth.kethereum:model:$kethereum_version"
|
||||
implementation 'com.github.komputing.khex:core:1.0.0-RC6'
|
||||
implementation 'com.github.komputing.khex:extensions:1.0.0-RC6'
|
||||
|
||||
implementation 'co.nstant.in:cbor:0.8'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.blockchain.bitcoin
|
|||
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.extensions.Result
|
||||
import com.tangem.blockchain.common.extensions.toCanonicalised
|
||||
import org.bitcoinj.core.*
|
||||
import org.bitcoinj.crypto.TransactionSignature
|
||||
import org.bitcoinj.script.Script
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import java.math.BigDecimal
|
|||
class BitcoinWalletManager(
|
||||
private val cardId: String,
|
||||
private val walletPublicKey: ByteArray,
|
||||
walletConfig: WalletConfig,
|
||||
override var wallet: CurrencyWallet,
|
||||
isTestNet: Boolean = false
|
||||
) : WalletManager,
|
||||
TransactionSender,
|
||||
|
|
@ -23,11 +23,13 @@ class BitcoinWalletManager(
|
|||
|
||||
override val blockchain = if (isTestNet) Blockchain.BitcoinTestnet else Blockchain.Bitcoin
|
||||
private val address = blockchain.makeAddress(walletPublicKey)
|
||||
private val currencyWallet = CurrencyWallet(walletConfig, address)
|
||||
override var wallet: Wallet = currencyWallet
|
||||
private val transactionBuilder = BitcoinTransactionBuilder(isTestNet)
|
||||
private val networkManager = BitcoinNetworkManager(isTestNet)
|
||||
|
||||
init {
|
||||
wallet.balances[AmountType.Coin] = Amount(null, blockchain)
|
||||
}
|
||||
|
||||
override suspend fun update() {
|
||||
val response = networkManager.getInfo(address)
|
||||
when (response) {
|
||||
|
|
@ -38,23 +40,24 @@ class BitcoinWalletManager(
|
|||
|
||||
private fun updateWallet(response: BitcoinAddressResponse) {
|
||||
Log.d(this::class.java.simpleName, "Balance is ${response.balance}")
|
||||
currencyWallet.balances[AmountType.Coin]?.value = response.balance
|
||||
wallet.balances[AmountType.Coin]?.value = response.balance
|
||||
transactionBuilder.unspentOutputs = response.unspentTransactions
|
||||
if (response.hasUnconfirmed) {
|
||||
if (currencyWallet.pendingTransactions.isEmpty()) {
|
||||
currencyWallet.pendingTransactions.add(TransactionData(
|
||||
if (wallet.pendingTransactions.isEmpty()) {
|
||||
wallet.pendingTransactions.add(TransactionData(
|
||||
Amount(blockchain.currency, decimals = blockchain.decimals),
|
||||
null,
|
||||
"unknown",
|
||||
currencyWallet.address))
|
||||
wallet.address))
|
||||
}
|
||||
} else {
|
||||
currencyWallet.pendingTransactions.clear()
|
||||
wallet.pendingTransactions.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateError(error: Throwable?) {
|
||||
Log.e(this::class.java.simpleName, error?.message ?: "")
|
||||
if (error != null) throw error
|
||||
}
|
||||
|
||||
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
|
||||
|
|
@ -73,7 +76,7 @@ class BitcoinWalletManager(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>> {
|
||||
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
|
||||
when (val result = networkManager.getFee()) {
|
||||
is Result.Failure -> return result
|
||||
is Result.Success -> {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.blockchain.cardano
|
||||
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.tangem.blockchain.cardano.network.CardanoAddressResponse
|
||||
import com.tangem.blockchain.cardano.network.CardanoNetworkManager
|
||||
|
|
@ -10,20 +9,17 @@ import com.tangem.blockchain.common.extensions.SimpleResult
|
|||
import com.tangem.blockchain.common.extensions.encodeBase64NoWrap
|
||||
import com.tangem.blockchain.wallets.CurrencyWallet
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import java.math.BigDecimal
|
||||
|
||||
class CardanoWalletManager(
|
||||
private val cardId: String,
|
||||
private val walletPublicKey: ByteArray,
|
||||
walletConfig: WalletConfig
|
||||
override var wallet: CurrencyWallet
|
||||
) : WalletManager,
|
||||
TransactionSender,
|
||||
FeeProvider {
|
||||
|
||||
override val blockchain = Blockchain.Cardano
|
||||
private val address = blockchain.makeAddress(walletPublicKey)
|
||||
private val currencyWallet = CurrencyWallet(walletConfig, address)
|
||||
override var wallet: Wallet = currencyWallet
|
||||
private val transactionBuilder = CardanoTransactionBuilder()
|
||||
private val networkManager = CardanoNetworkManager()
|
||||
|
||||
|
|
@ -37,13 +33,14 @@ class CardanoWalletManager(
|
|||
|
||||
private fun updateWallet(response: CardanoAddressResponse) {
|
||||
Log.d(this::class.java.simpleName, "Balance is ${response.balance.toString()}")
|
||||
currencyWallet.balances[AmountType.Coin]?.value =
|
||||
wallet.balances[AmountType.Coin]?.value =
|
||||
response.balance.toBigDecimal().movePointLeft(blockchain.decimals.toInt())
|
||||
transactionBuilder.unspentOutputs = response.unspentOutputs
|
||||
}
|
||||
|
||||
private fun updateError(error: Throwable?) {
|
||||
Log.e(this::class.java.simpleName, error?.message ?: "")
|
||||
if (error != null) throw error
|
||||
}
|
||||
|
||||
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
|
||||
|
|
@ -58,13 +55,13 @@ class CardanoWalletManager(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>> {
|
||||
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
|
||||
val a = 0.155381
|
||||
val b = 0.000043946
|
||||
val size = transactionBuilder.getEstimateSize(
|
||||
TransactionData(amount, null, source, destination), walletPublicKey
|
||||
TransactionData(amount, null, address, destination), walletPublicKey
|
||||
)
|
||||
val fee = (a + b * size).toBigDecimal()
|
||||
return Result.Success(listOf(Amount(blockchain.currency, fee, source, blockchain.decimals)))
|
||||
return Result.Success(listOf(Amount(blockchain.currency, fee, address, blockchain.decimals)))
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,15 @@ package com.tangem.blockchain.common
|
|||
|
||||
import com.tangem.blockchain.bitcoin.BitcoinAddressFactory
|
||||
import com.tangem.blockchain.bitcoin.BitcoinAddressValidator
|
||||
import com.tangem.blockchain.ethereum.EthereumAddressFactory
|
||||
import com.tangem.blockchain.ethereum.EthereumAddressValidator
|
||||
import com.tangem.blockchain.cardano.CardanoAddressFactory
|
||||
import com.tangem.blockchain.cardano.CardanoAddressValidator
|
||||
import com.tangem.blockchain.ethereum.EthereumAddressFactory
|
||||
import com.tangem.blockchain.ethereum.EthereumAddressValidator
|
||||
|
||||
import com.tangem.blockchain.stellar.StellarAddressFactory
|
||||
import com.tangem.blockchain.xrp.XrpAddressFactory
|
||||
import com.tangem.blockchain.xrp.XrpAddressValidator
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
enum class Blockchain(
|
||||
|
|
@ -24,7 +26,7 @@ enum class Blockchain(
|
|||
Ethereum("ETH", "ETH", 18, "Ethereum", 0),
|
||||
Rootstock("", "", 18, "", 0),
|
||||
Cardano("CARDANO", "ADA", 6, "Cardano", 0),
|
||||
XRP("", "XRP", 6, "XRP Ledger", 0),
|
||||
XRP("XRP", "XRP", 6, "XRP Ledger", 0),
|
||||
Binance("", "", 8, "", 0),
|
||||
Stellar("XLM", "XLM", 7, "Stellar", 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ data class Amount(
|
|||
address: String? = null,
|
||||
type: AmountType = AmountType.Coin
|
||||
) : this(blockchain.currency, value, address, blockchain.decimals, type)
|
||||
|
||||
constructor(token: Token, value: BigDecimal? = null) :
|
||||
this(token.symbol, value, token.contractAddress, token.decimals, AmountType.Token)
|
||||
}
|
||||
|
||||
data class TransactionData(
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ package com.tangem.blockchain.common
|
|||
|
||||
import com.tangem.blockchain.common.extensions.Result
|
||||
import com.tangem.blockchain.common.extensions.SimpleResult
|
||||
import com.tangem.blockchain.wallets.CurrencyWallet
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface WalletManager {
|
||||
var wallet: Wallet
|
||||
var wallet: CurrencyWallet
|
||||
val blockchain: Blockchain
|
||||
|
||||
suspend fun update()
|
||||
|
|
@ -22,5 +22,5 @@ interface TransactionSigner {
|
|||
}
|
||||
|
||||
interface FeeProvider {
|
||||
suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>>
|
||||
suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>>
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.blockchain.common
|
||||
|
||||
import com.tangem.blockchain.bitcoin.BitcoinWalletManager
|
||||
import com.tangem.blockchain.cardano.CardanoWalletManager
|
||||
import com.tangem.blockchain.ethereum.Chain
|
||||
import com.tangem.blockchain.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.cardano.CardanoWalletManager
|
||||
|
||||
import com.tangem.blockchain.stellar.StellarWalletManager
|
||||
import com.tangem.blockchain.wallets.CurrencyWallet
|
||||
import com.tangem.blockchain.xrp.XrpWalletManager
|
||||
import com.tangem.commands.Card
|
||||
|
||||
|
|
@ -13,53 +15,56 @@ object WalletManagerFactory {
|
|||
fun makeWalletManager(card: Card): WalletManager? {
|
||||
val walletPublicKey: ByteArray = card.walletPublicKey ?: return null
|
||||
val blockchainName: String = card.cardData?.blockchainName ?: return null
|
||||
val blockchain = Blockchain.fromId(blockchainName)
|
||||
|
||||
when {
|
||||
blockchainName == Blockchain.Bitcoin.id -> {
|
||||
val token = getToken(card)
|
||||
val wallet = CurrencyWallet.newInstance(blockchain, blockchain.makeAddress(walletPublicKey), token)
|
||||
|
||||
|
||||
when (blockchain) {
|
||||
Blockchain.Bitcoin -> {
|
||||
return BitcoinWalletManager(
|
||||
cardId = card.cardId,
|
||||
walletPublicKey = walletPublicKey,
|
||||
walletConfig = WalletConfig(true, true)
|
||||
wallet = wallet
|
||||
)
|
||||
}
|
||||
blockchainName == Blockchain.BitcoinTestnet.id -> {
|
||||
Blockchain.BitcoinTestnet -> {
|
||||
return BitcoinWalletManager(
|
||||
cardId = card.cardId,
|
||||
walletPublicKey = walletPublicKey,
|
||||
walletConfig = WalletConfig(true, true),
|
||||
wallet = wallet,
|
||||
isTestNet = true
|
||||
)
|
||||
}
|
||||
blockchainName == Blockchain.Ethereum.id -> {
|
||||
Blockchain.Ethereum -> {
|
||||
val chain = Chain.Mainnet
|
||||
return EthereumWalletManager(
|
||||
cardId = card.cardId,
|
||||
walletPublicKey = walletPublicKey,
|
||||
walletConfig = WalletConfig(true, true),
|
||||
wallet = wallet,
|
||||
chain = chain
|
||||
)
|
||||
}
|
||||
blockchainName == Blockchain.Stellar.id -> {
|
||||
val token = getToken(card)
|
||||
Blockchain.Stellar -> {
|
||||
return StellarWalletManager(
|
||||
cardId = card.cardId,
|
||||
walletPublicKey = walletPublicKey,
|
||||
walletConfig = WalletConfig(true, token == null),
|
||||
token = token
|
||||
wallet = wallet
|
||||
)
|
||||
}
|
||||
blockchainName == Blockchain.Cardano.id -> {
|
||||
Blockchain.Cardano -> {
|
||||
return CardanoWalletManager(
|
||||
cardId = card.cardId,
|
||||
walletPublicKey = walletPublicKey,
|
||||
walletConfig = WalletConfig(false, true)
|
||||
wallet = wallet
|
||||
)
|
||||
}
|
||||
blockchainName == Blockchain.XRP.id -> {
|
||||
Blockchain.XRP -> {
|
||||
return XrpWalletManager(
|
||||
cardId = card.cardId,
|
||||
walletPublicKey = walletPublicKey,
|
||||
walletConfig = WalletConfig(true, true)
|
||||
wallet = wallet
|
||||
)
|
||||
}
|
||||
else -> return null
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@ import com.tangem.CardManager
|
|||
import com.tangem.blockchain.common.TransactionSigner
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import java.io.IOException
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
suspend fun <T> retryIO(
|
||||
times: Int = 3,
|
||||
|
|
@ -56,11 +55,11 @@ sealed class SimpleResult {
|
|||
}
|
||||
|
||||
class Signer(private val cardManager: CardManager) : TransactionSigner {
|
||||
override suspend fun sign(hashes: Array<ByteArray>, cardId: String): TaskEvent<SignResponse> = coroutineScope {
|
||||
async {
|
||||
suspendCancellableCoroutine<TaskEvent<SignResponse>> { continuation ->
|
||||
cardManager.sign(hashes, cardId) { if (continuation.isActive) continuation.resume(it) }
|
||||
override suspend fun sign(hashes: Array<ByteArray>, cardId: String): TaskEvent<SignResponse> =
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
cardManager.sign(hashes, cardId) { result ->
|
||||
if (continuation.isActive) continuation.resume(result)
|
||||
}
|
||||
}
|
||||
}.await()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.blockchain.common.network
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import com.tangem.blockchain.BuildConfig
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
|
|
@ -18,11 +20,16 @@ private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor {
|
|||
return logging
|
||||
}
|
||||
|
||||
private val moshi: Moshi by lazy {
|
||||
Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
}
|
||||
|
||||
fun createRetrofitInstance(baseUrl: String): Retrofit =
|
||||
Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.addConverterFactory(MoshiConverterFactory.create())
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.client(okHttpClient)
|
||||
.build()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.blockchain.ethereum
|
||||
|
||||
import org.kethereum.crypto.toAddress
|
||||
import org.kethereum.functions.isValid
|
||||
import org.kethereum.erc55.isValid
|
||||
import org.kethereum.model.Address
|
||||
import org.kethereum.model.PublicKey
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.blockchain.ethereum
|
||||
|
||||
import android.util.Log
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.extensions.Result
|
||||
import com.tangem.blockchain.common.extensions.SimpleResult
|
||||
import com.tangem.blockchain.ethereum.network.EthereumNetworkManager
|
||||
|
|
@ -12,7 +14,7 @@ import org.kethereum.DEFAULT_GAS_LIMIT
|
|||
import org.kethereum.crypto.api.ec.ECDSASignature
|
||||
import org.kethereum.crypto.determineRecId
|
||||
import org.kethereum.crypto.impl.ec.canonicalise
|
||||
import org.kethereum.functions.encodeRLP
|
||||
import org.kethereum.extensions.transactions.encodeRLP
|
||||
import org.kethereum.keccakshortcut.keccak
|
||||
import org.kethereum.model.*
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -22,22 +24,32 @@ class EthereumWalletManager(
|
|||
private val cardId: String,
|
||||
private val walletPublicKey: ByteArray,
|
||||
chain: Chain,
|
||||
walletConfig: WalletConfig
|
||||
token: Token? = null,
|
||||
override var wallet: CurrencyWallet
|
||||
) : WalletManager,
|
||||
TransactionSender,
|
||||
FeeProvider {
|
||||
|
||||
override val blockchain: Blockchain = Blockchain.Ethereum
|
||||
private val address = blockchain.makeAddress(walletPublicKey)
|
||||
private val currencyWallet = CurrencyWallet(walletConfig, address)
|
||||
override var wallet: Wallet = currencyWallet
|
||||
private val builder = EthereumTransactionBuilder(chain)
|
||||
private val networkManager = EthereumNetworkManager()
|
||||
private var pendingTxCount = -1L
|
||||
private var txCount = -1L
|
||||
|
||||
init {
|
||||
if (token != null) wallet.balances[AmountType.Token] =
|
||||
Amount(
|
||||
token.symbol,
|
||||
null,
|
||||
token.contractAddress,
|
||||
token.decimals,
|
||||
AmountType.Token)
|
||||
wallet.balances[AmountType.Coin] = Amount(null, blockchain)
|
||||
}
|
||||
|
||||
override suspend fun update() {
|
||||
val result = networkManager.getInfo(address, currencyWallet.balances[AmountType.Token]?.address)
|
||||
val result = networkManager.getInfo(address, wallet.balances[AmountType.Token]?.address)
|
||||
when (result) {
|
||||
is Result.Failure -> updateError(result.error)
|
||||
is Result.Success -> updateWallet(result.data)
|
||||
|
|
@ -45,23 +57,24 @@ class EthereumWalletManager(
|
|||
}
|
||||
|
||||
private fun updateWallet(data: EthereumResponse) {
|
||||
currencyWallet.balances[AmountType.Coin]?.value = data.balance
|
||||
currencyWallet.balances[AmountType.Token]?.value = data.tokenBalance
|
||||
wallet.balances[AmountType.Coin]?.value = data.balance
|
||||
wallet.balances[AmountType.Token]?.value = data.tokenBalance
|
||||
txCount = data.txCount
|
||||
pendingTxCount = data.pendingTxCount
|
||||
if (txCount == pendingTxCount) {
|
||||
currencyWallet.pendingTransactions.forEach { it.status = TransactionStatus.Confirmed }
|
||||
} else if (currencyWallet.pendingTransactions.isEmpty()) {
|
||||
currencyWallet.pendingTransactions.add(TransactionData(
|
||||
wallet.pendingTransactions.forEach { it.status = TransactionStatus.Confirmed }
|
||||
} else if (wallet.pendingTransactions.isEmpty()) {
|
||||
wallet.pendingTransactions.add(TransactionData(
|
||||
Amount(blockchain.currency, decimals = blockchain.decimals),
|
||||
null,
|
||||
"unknown",
|
||||
currencyWallet.address))
|
||||
wallet.address))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateError(error: Throwable?) {
|
||||
|
||||
Log.e(this::class.java.simpleName, error?.message ?: "")
|
||||
if (error != null) throw error
|
||||
}
|
||||
|
||||
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
|
||||
|
|
@ -76,7 +89,7 @@ class EthereumWalletManager(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>> {
|
||||
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
|
||||
val result = networkManager.getFee(getGasLimit(amount).value)
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import kotlinx.coroutines.coroutineScope
|
|||
import org.kethereum.ETH_IN_WEI
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.math.RoundingMode
|
||||
|
||||
|
||||
class EthereumNetworkManager {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class StellarNetworkManager(isTestNet: Boolean) {
|
|||
coroutineScope {
|
||||
val accountResponseDefered = async(Dispatchers.IO) { stellarServer.accounts().account(accountId) }
|
||||
val ledgerResponseDeferred = async(Dispatchers.IO) {
|
||||
val latestLedger: Int = stellarServer.root().coreLatestLedger
|
||||
val latestLedger: Int = stellarServer.root().historyLatestLedger
|
||||
stellarServer.ledgers().ledger(latestLedger.toLong())
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ class StellarNetworkManager(isTestNet: Boolean) {
|
|||
null
|
||||
} else {
|
||||
accountResponse.balances
|
||||
.find { it.assetType != "native" && it.assetCode == assetCode }
|
||||
.find { it.assetType != "native" && it.assetIssuer == assetCode }
|
||||
?.balance?.toBigDecimal()
|
||||
?: return@coroutineScope Result.Failure(Exception("Stellar Balance not found"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.blockchain.stellar
|
||||
|
||||
import android.util.Log
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.extensions.Result
|
||||
import com.tangem.blockchain.common.extensions.SimpleResult
|
||||
|
|
@ -11,8 +12,7 @@ import java.util.*
|
|||
class StellarWalletManager(
|
||||
private val cardId: String,
|
||||
walletPublicKey: ByteArray,
|
||||
walletConfig: WalletConfig,
|
||||
token: Token? = null,
|
||||
override var wallet: CurrencyWallet,
|
||||
isTestNet: Boolean = false
|
||||
) : WalletManager,
|
||||
TransactionSender,
|
||||
|
|
@ -20,26 +20,14 @@ class StellarWalletManager(
|
|||
|
||||
override val blockchain: Blockchain = Blockchain.Stellar
|
||||
private val address = blockchain.makeAddress(walletPublicKey)
|
||||
private val currencyWallet = CurrencyWallet(walletConfig, address)
|
||||
override var wallet: Wallet = currencyWallet
|
||||
private val networkManager = StellarNetworkManager(isTestNet)
|
||||
private val builder = StellarTransactionBuilder(networkManager, walletPublicKey)
|
||||
private var baseFee = BASE_FEE
|
||||
private var baseReserve = BASE_RESERVE
|
||||
private var sequence = 0L
|
||||
|
||||
init {
|
||||
if (token != null) currencyWallet.balances[AmountType.Token] =
|
||||
Amount(
|
||||
token.symbol,
|
||||
null,
|
||||
token.contractAddress,
|
||||
token.decimals,
|
||||
AmountType.Token)
|
||||
}
|
||||
|
||||
override suspend fun update() {
|
||||
val result = networkManager.getInfo(address, currencyWallet.balances[AmountType.Token]?.address)
|
||||
val result = networkManager.getInfo(address, wallet.balances[AmountType.Token]?.address)
|
||||
when (result) {
|
||||
is Result.Failure -> updateError(result.error)
|
||||
is Result.Success -> updateWallet(result.data)
|
||||
|
|
@ -47,15 +35,15 @@ class StellarWalletManager(
|
|||
}
|
||||
|
||||
private fun updateWallet(data: StellarResponse) {
|
||||
currencyWallet.balances[AmountType.Coin]?.value = data.balance
|
||||
currencyWallet.balances[AmountType.Token]?.value = data.assetBalance
|
||||
currencyWallet.balances[AmountType.Reserve]?.value = data.baseReserve
|
||||
wallet.balances[AmountType.Coin]?.value = data.balance
|
||||
wallet.balances[AmountType.Token]?.value = data.assetBalance
|
||||
wallet.balances[AmountType.Reserve]?.value = data.baseReserve
|
||||
sequence = data.sequence
|
||||
baseFee = data.baseFee
|
||||
baseReserve = data.baseReserve
|
||||
|
||||
val currentTime = Calendar.getInstance().timeInMillis
|
||||
currencyWallet.pendingTransactions.forEach { transaction ->
|
||||
wallet.pendingTransactions.forEach { transaction ->
|
||||
if (transaction.date?.timeInMillis ?: 0 - currentTime > 10) {
|
||||
transaction.status = TransactionStatus.Confirmed
|
||||
}
|
||||
|
|
@ -63,7 +51,8 @@ class StellarWalletManager(
|
|||
}
|
||||
|
||||
private fun updateError(error: Throwable?) {
|
||||
|
||||
Log.e(this::class.java.simpleName, error?.message ?: "")
|
||||
if (error != null) throw error
|
||||
}
|
||||
|
||||
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
|
||||
|
|
@ -77,7 +66,7 @@ class StellarWalletManager(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>> {
|
||||
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
|
||||
return Result.Success(listOf(
|
||||
Amount(baseFee, blockchain)
|
||||
))
|
||||
|
|
|
|||
|
|
@ -4,17 +4,63 @@ import com.tangem.blockchain.common.*
|
|||
import java.util.*
|
||||
|
||||
class CurrencyWallet(
|
||||
override val config: WalletConfig,
|
||||
override val address: String,
|
||||
override val exploreUrl: String? = null,
|
||||
override val shareUrl: String? = null,
|
||||
val pendingTransactions: MutableList<TransactionData> = mutableListOf(),
|
||||
val balances: MutableMap<AmountType, Amount> = mutableMapOf(),
|
||||
val isTestnet: Boolean = false
|
||||
val blockchain: Blockchain,
|
||||
address: String,
|
||||
override val config: WalletConfig
|
||||
) : Wallet, TransactionValidator {
|
||||
|
||||
override val exploreUrl: String? = null
|
||||
override val shareUrl: String? = null
|
||||
override val address: String
|
||||
get() = balances[AmountType.Coin]!!.address!!
|
||||
|
||||
val pendingTransactions: MutableList<TransactionData> = mutableListOf()
|
||||
val balances: MutableMap<AmountType, Amount> = mutableMapOf()
|
||||
|
||||
init {
|
||||
addAmount(Amount(null, blockchain, address))
|
||||
}
|
||||
|
||||
override fun validateTransaction(amount: Amount, fee: Amount?): EnumSet<ValidationError> {
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
fun addAmount(amount: Amount) {
|
||||
balances[amount.type] = amount
|
||||
}
|
||||
|
||||
fun addPendingTransaction(transaction: TransactionData) {
|
||||
pendingTransactions.add(transaction.copy(date = Calendar.getInstance()))
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(blockchain: Blockchain, address: String, token: Token?): CurrencyWallet {
|
||||
|
||||
return when (blockchain) {
|
||||
Blockchain.Bitcoin, Blockchain.XRP -> {
|
||||
val config = WalletConfig(true, true)
|
||||
CurrencyWallet(blockchain, address, config)
|
||||
}
|
||||
Blockchain.Cardano -> {
|
||||
val config = WalletConfig(false, true)
|
||||
CurrencyWallet(blockchain, address, config)
|
||||
}
|
||||
Blockchain.Ethereum -> {
|
||||
val config = WalletConfig(true, token == null)
|
||||
val wallet = CurrencyWallet(blockchain, address, config)
|
||||
if (token != null) wallet.addAmount(Amount(token))
|
||||
wallet
|
||||
}
|
||||
Blockchain.Stellar -> {
|
||||
val config = WalletConfig(false, token == null)
|
||||
val wallet = CurrencyWallet(blockchain, address, config)
|
||||
if (token != null) wallet.addAmount(Amount(token))
|
||||
wallet.addAmount(Amount(null, blockchain, address, AmountType.Reserve))
|
||||
wallet
|
||||
}
|
||||
else -> throw Exception("Unsupported blockchain")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,18 +12,21 @@ import com.tangem.tasks.TaskEvent
|
|||
class XrpWalletManager(
|
||||
private val cardId: String,
|
||||
private val walletPublicKey: ByteArray,
|
||||
walletConfig: WalletConfig
|
||||
override var wallet: CurrencyWallet
|
||||
) : WalletManager,
|
||||
TransactionSender,
|
||||
FeeProvider {
|
||||
|
||||
override val blockchain = Blockchain.XRP
|
||||
private val address = blockchain.makeAddress(walletPublicKey)
|
||||
private val currencyWallet = CurrencyWallet(walletConfig, address)
|
||||
override var wallet: Wallet = currencyWallet
|
||||
private val transactionBuilder = XrpTransactionBuilder(walletPublicKey)
|
||||
private val networkManager = XrpNetworkManager()
|
||||
|
||||
init {
|
||||
wallet.balances[AmountType.Coin] = Amount(null, blockchain)
|
||||
wallet.balances[AmountType.Reserve] = Amount(null, blockchain, type = AmountType.Reserve)
|
||||
}
|
||||
|
||||
override suspend fun update() {
|
||||
val result = networkManager.getInfo(address)
|
||||
when (result) {
|
||||
|
|
@ -34,30 +37,31 @@ class XrpWalletManager(
|
|||
|
||||
private fun updateWallet(response: XrpInfoResponse) {
|
||||
Log.d(this::class.java.simpleName, "Balance is ${response.balance}")
|
||||
currencyWallet.balances[AmountType.Reserve]?.value = response.reserveBase
|
||||
wallet.balances[AmountType.Reserve]?.value = response.reserveBase
|
||||
|
||||
if (!response.accountFound) {
|
||||
updateError(Exception("Account not found")) //TODO rework, add reserve
|
||||
return
|
||||
}
|
||||
currencyWallet.balances[AmountType.Coin]?.value = response.balance - response.reserveBase
|
||||
wallet.balances[AmountType.Coin]?.value = response.balance - response.reserveBase
|
||||
transactionBuilder.sequence = response.sequence
|
||||
|
||||
if (response.hasUnconfirmed) {
|
||||
if (currencyWallet.pendingTransactions.isEmpty()) {
|
||||
currencyWallet.pendingTransactions.add(TransactionData(
|
||||
if (wallet.pendingTransactions.isEmpty()) {
|
||||
wallet.pendingTransactions.add(TransactionData(
|
||||
Amount(blockchain.currency, decimals = blockchain.decimals),
|
||||
null,
|
||||
"unknown",
|
||||
currencyWallet.address))
|
||||
wallet.address))
|
||||
}
|
||||
} else {
|
||||
currencyWallet.pendingTransactions.clear()
|
||||
wallet.pendingTransactions.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateError(error: Throwable?) {
|
||||
Log.e(this::class.java.simpleName, error?.message ?: "")
|
||||
if (error != null) throw error
|
||||
}
|
||||
|
||||
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
|
||||
|
|
@ -72,7 +76,7 @@ class XrpWalletManager(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>> {
|
||||
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
|
||||
val result = networkManager.getFee()
|
||||
when (result) {
|
||||
is Result.Failure -> return result
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.blockchain.xrp.network
|
||||
|
||||
import com.tangem.blockchain.bitcoin.network.BitcoinFee
|
||||
import com.tangem.blockchain.common.extensions.Result
|
||||
import com.tangem.blockchain.common.extensions.SimpleResult
|
||||
import com.tangem.blockchain.common.network.API_RIPPLED
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.tangem.blockchain.xrp.network.XrpFeeResponse
|
|||
import com.tangem.blockchain.xrp.network.XrpInfoResponse
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import java.math.BigDecimal
|
||||
|
||||
class RippledProvider(private val api: RippledApi) {
|
||||
private val decimals = Blockchain.XRP.decimals.toInt()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ buildscript {
|
|||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$versions.build_gradle"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
|
||||
classpath "com.github.dcendents:android-maven-gradle-plugin:2.1"
|
||||
classpath "io.fabric.tools:gradle:1.31.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ext.versions = [
|
||||
kotlin : '1.3.61',
|
||||
kotlin : '1.3.70',
|
||||
build_gradle: '3.6.1',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@ platform :android do
|
|||
desc "Runs all the tests"
|
||||
lane :test do
|
||||
gradle(
|
||||
task: "test",
|
||||
flavor: "tangemAccess",
|
||||
build_type: "DebugUnitTest")
|
||||
task: "test")
|
||||
end
|
||||
|
||||
desc "Build a signed release APK"
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class LocalStorage
|
|||
artworks = HashMap()
|
||||
}
|
||||
|
||||
if (artworks.count() < 39) {
|
||||
if (artworks.count() < 43) {
|
||||
// forceSave=true only on the last one
|
||||
putResourceArtworkToCatalog(R.drawable.card_default, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_default_nft, false)
|
||||
|
|
@ -78,9 +78,13 @@ class LocalStorage
|
|||
putResourceArtworkToCatalog(R.drawable.card_ru043, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg044, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg046, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg049, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg051, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg052, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg054, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg055, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg057, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg058, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg060, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg061, false)
|
||||
putResourceArtworkToCatalog(R.drawable.card_tg062, false)
|
||||
|
|
@ -281,8 +285,12 @@ class LocalStorage
|
|||
card.batch == "0029" -> R.drawable.card_ru041
|
||||
card.batch == "0031" -> R.drawable.card_ru042
|
||||
card.batch == "0034" -> R.drawable.card_tg046
|
||||
card.batch == "0037" -> R.drawable.card_tg049
|
||||
card.batch == "0038" -> R.drawable.card_tg051
|
||||
card.batch == "0039" -> R.drawable.card_tg052
|
||||
card.batch == "0041" -> R.drawable.card_tg054
|
||||
card.batch == "0042" -> R.drawable.card_tg055
|
||||
card.batch == "0044" -> R.drawable.card_tg058
|
||||
card.batch == "0046" -> R.drawable.card_tg054
|
||||
card.batch == "0047" -> R.drawable.card_tg055
|
||||
card.batch == "0045" -> R.drawable.card_tg057
|
||||
|
|
|
|||
BIN
server-android/src/main/res/drawable/card_tg049.png
Normal file
|
After Width: | Height: | Size: 180 KiB |
BIN
server-android/src/main/res/drawable/card_tg051.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
server-android/src/main/res/drawable/card_tg052.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
server-android/src/main/res/drawable/card_tg058.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
|
|
@ -1 +1 @@
|
|||
include ':app', ':tangem-sdk-old', ':server-android', ':tangem-card-old', ':tangem-core', ':tangem-sdk', ':tangem-demo', ':blockchain'
|
||||
include ':app', ':tangem-sdk-old', ':server-android', ':tangem-card-old', ':tangem-core', ':tangem-sdk', ':tangem-demo', ':blockchain', ':blockchain-demo'
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
apply plugin: "kotlin"
|
||||
apply plugin: 'org.jetbrains.dokka'
|
||||
apply plugin: 'com.github.dcendents.android-maven'
|
||||
apply from: '../dependencies.gradle'
|
||||
apply from: '../jitpack.gradle'
|
||||
|
||||
|
|
@ -7,14 +8,13 @@ group = "$jitpackSdk.group"
|
|||
version "$jitpackSdk.version"
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
|
||||
implementation "com.madgag.spongycastle:core:1.58.0.0"
|
||||
implementation "com.madgag.spongycastle:prov:1.58.0.0"
|
||||
implementation 'net.i2p.crypto:eddsa:0.3.0'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
|
||||
testImplementation "com.google.truth:truth:1.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin"
|
||||
}
|
||||
|
||||
sourceCompatibility = "8"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
|
|||
apply plugin: 'kotlin-android-extensions'
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.0"
|
||||
buildToolsVersion "29.0.2"
|
||||
|
||||
|
||||
defaultConfig {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'com.github.dcendents.android-maven'
|
||||
apply from: '../dependencies.gradle'
|
||||
apply from: '../jitpack.gradle'
|
||||
|
||||
|
|
@ -9,7 +10,7 @@ version "$jitpackSdk.version"
|
|||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.0"
|
||||
buildToolsVersion "29.0.2"
|
||||
|
||||
|
||||
defaultConfig {
|
||||
|
|
|
|||