Updated on 2026-08-14
This commit is contained in:
parent
161bf4ab5c
commit
d5c411471b
14 changed files with 966 additions and 15 deletions
|
|
@ -7,10 +7,13 @@ import android.net.Uri
|
|||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.nfc.tech.Ndef
|
||||
import android.nfc.tech.NfcV
|
||||
import android.os.Bundle
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
|
|
@ -20,9 +23,13 @@ import androidx.core.os.bundleOf
|
|||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.tangem.App
|
||||
import com.tangem.Constant
|
||||
import com.tangem.data.Blockchain
|
||||
import com.tangem.data.Logger
|
||||
import com.tangem.tangem_card.data.TangemCard
|
||||
import com.tangem.tangem_card.reader.CardProtocol
|
||||
import com.tangem.tangem_card.reader.TLV
|
||||
import com.tangem.tangem_card.reader.TLVException
|
||||
import com.tangem.tangem_card.reader.TLVList
|
||||
import com.tangem.tangem_card.tasks.CustomReadCardTask
|
||||
import com.tangem.tangem_card.tasks.ReadCardInfoTask
|
||||
import com.tangem.tangem_sdk.android.data.PINStorage
|
||||
|
|
@ -45,10 +52,16 @@ import com.tangem.wallet.BuildConfig
|
|||
import com.tangem.wallet.CoinEngineFactory
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.TangemContext
|
||||
import com.tangem.wallet.xlmtag.XlmTagEngine
|
||||
import kotlinx.android.synthetic.main.fragment_main.*
|
||||
import kotlinx.android.synthetic.main.layout_touch_card.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback,
|
||||
CardProtocol.Notifications, androidx.appcompat.widget.PopupMenu.OnMenuItemClickListener,
|
||||
|
|
@ -68,6 +81,11 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
|
|||
private var zipFile: File? = null
|
||||
private var unknownBlockchain = false
|
||||
|
||||
private val parentJob = Job()
|
||||
private val coroutineContext: CoroutineContext
|
||||
get() = parentJob + Dispatchers.IO
|
||||
private val scope = CoroutineScope(coroutineContext)
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
setHasOptionsMenu(true)
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
|
@ -152,6 +170,8 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
|
|||
return
|
||||
}
|
||||
|
||||
parseNfcvTag(tag)
|
||||
|
||||
try {
|
||||
// get IsoDep handle and run cardReader thread
|
||||
val isoDep = IsoDep.get(tag)
|
||||
|
|
@ -175,6 +195,77 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
|
|||
}
|
||||
}
|
||||
|
||||
private fun parseNfcvTag(tag: Tag) {
|
||||
if (NfcV.get(tag) != null) {
|
||||
if (Ndef.get(tag) != null) {
|
||||
try {
|
||||
onNdefDiscovered(Ndef.get(tag), tag.id)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
(activity as MainActivity).nfcManager.notifyReadResult(false)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
(activity as MainActivity).nfcManager.notifyReadResult(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onNdefDiscovered(ndef: Ndef, uid: ByteArray) {
|
||||
scope.launch {
|
||||
|
||||
|
||||
try {
|
||||
ndef.connect()
|
||||
val records = ndef.ndefMessage.records
|
||||
for (record in records) {
|
||||
if (record.toUri() != null) {
|
||||
when (record.toUri().toString()) {
|
||||
"vnd.android.nfc://ext/tangem.com:wallet" -> {
|
||||
//mConsole.write(Util.bytesToHex(record.getPayload()), MessageAdapter.MSG_OKAY, "", null, false);
|
||||
val payload = record.payload
|
||||
Log.v(TAG, "tangem.com:wallet[${payload.size} bytes]:")
|
||||
try {
|
||||
val tlvNDEF: TLVList = TLVList.fromBytes(Arrays.copyOfRange(payload, 2, payload.size))
|
||||
val cardDataTlv = TLVList.fromBytes((tlvNDEF.getTLV(TLV.Tag.TAG_CardData)).Value)
|
||||
Log.v(TAG, "\n" + tlvNDEF.getParsedTLVs(""))
|
||||
val card = TangemCard(uid.toString())
|
||||
card.batch = cardDataTlv.getTLV(TLV.Tag.TAG_Batch).asHexString
|
||||
card.setIssuer(cardDataTlv.getTLV(TLV.Tag.TAG_Issuer_ID).Value.toString(), null)
|
||||
card.blockchainID = Blockchain.StellarTag.id
|
||||
card.walletPublicKey = tlvNDEF.getTLV(TLV.Tag.TAG_Wallet_PublicKey).Value
|
||||
card.status = TangemCard.Status.Loaded
|
||||
card.tagSignature = tlvNDEF.getTLV(TLV.Tag.TAG_Signature).Value
|
||||
|
||||
val ctx = TangemContext(card)
|
||||
val engineCoin = XlmTagEngine(ctx)
|
||||
engineCoin.defineWallet()
|
||||
launch(Dispatchers.Main) {
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag)
|
||||
ctx.saveToBundle(bundle)
|
||||
navigateForResult(Constant.REQUEST_CODE_SHOW_CARD_ACTIVITY,
|
||||
R.id.action_main_to_tagFragment, bundle)
|
||||
}
|
||||
} catch (e: TLVException) {
|
||||
e.printStackTrace()
|
||||
Log.v(TAG, e.message)
|
||||
}
|
||||
}
|
||||
else -> Log.v(TAG, record.toUri().toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
(activity as MainActivity).nfcManager.notifyReadResult(false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReadStart(cardProtocol: CardProtocol) {
|
||||
rlProgressBar?.post { rlProgressBar?.visibility = View.VISIBLE }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,214 @@
|
|||
package com.tangem.ui.fragment.additional
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.tangem.data.network.ServerApiCommon
|
||||
import com.tangem.server_android.ServerApiTangem
|
||||
import com.tangem.ui.fragment.BaseFragment
|
||||
import com.tangem.ui.fragment.wallet.LoadedWalletFragment
|
||||
import com.tangem.ui.fragment.wallet.LoadedWalletViewModel
|
||||
import com.tangem.ui.navigation.NavigationResultListener
|
||||
import com.tangem.util.LOG
|
||||
import com.tangem.util.UtilHelper
|
||||
import com.tangem.wallet.*
|
||||
import com.tangem.wallet.xlmtag.XlmTagEngine
|
||||
import kotlinx.android.synthetic.main.fr_loaded_wallet.*
|
||||
import kotlinx.android.synthetic.main.layout_btn_details.*
|
||||
import kotlinx.android.synthetic.main.layout_tangem_card.*
|
||||
|
||||
|
||||
class TagFragment : BaseFragment(), NavigationResultListener {
|
||||
override val layoutId = R.layout.fragment_tag
|
||||
private lateinit var viewModel: LoadedWalletViewModel
|
||||
private lateinit var ctx: TangemContext
|
||||
private var serverApiCommon: ServerApiCommon = ServerApiCommon()
|
||||
private var serverApiTangem: ServerApiTangem = ServerApiTangem()
|
||||
|
||||
private var requestCounter: Int = 0
|
||||
set(value) {
|
||||
field = value
|
||||
LOG.i(LoadedWalletFragment.TAG, "requestCounter, set $field")
|
||||
if (field <= 0) {
|
||||
LOG.e(LoadedWalletFragment.TAG, "+++++++++++ FINISH REFRESH")
|
||||
if (srl != null && srl.isRefreshing)
|
||||
srl.isRefreshing = false
|
||||
} else if (srl != null && !srl.isRefreshing)
|
||||
srl.isRefreshing = true
|
||||
}
|
||||
|
||||
override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
ctx = TangemContext.loadFromBundle(context, arguments)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val engine = XlmTagEngine(ctx)
|
||||
|
||||
|
||||
btnLoad.visibility = View.GONE
|
||||
btnDetails.visibility = View.GONE
|
||||
btnExtract.text = getString(R.string.tag_claim)
|
||||
btnExtract.isEnabled = false //TODO: enable when we implement extraction
|
||||
btnExtract.backgroundTintList =
|
||||
ContextCompat.getColorStateList(requireContext(), R.color.btn_dark)
|
||||
|
||||
ivTangemCard.setImageResource(R.drawable.card_default_nft)
|
||||
|
||||
tvBalance.setSingleLine(!engine.needMultipleLinesForBalance())
|
||||
tvWallet.text = ctx.coinData.wallet
|
||||
tvWallet.setOnClickListener { shareWallet() }
|
||||
btnExplore.setOnClickListener { startActivity(Intent(Intent.ACTION_VIEW, engine.walletExplorerUri)) }
|
||||
btnCopy.setOnClickListener { shareWallet() }
|
||||
btnNewScan.setOnClickListener { navigateUp() }
|
||||
srl?.setOnRefreshListener { refresh(true) }
|
||||
|
||||
requestBalanceAndUnspentTransactions()
|
||||
// update()
|
||||
}
|
||||
|
||||
|
||||
private fun shareWallet() {
|
||||
val txtShare = ctx.coinData.wallet
|
||||
val clipboard = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.primaryClip = ClipData.newPlainText(txtShare, txtShare)
|
||||
Toast.makeText(activity, R.string.loaded_wallet_toast_copied, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
|
||||
private fun update() {
|
||||
ctx.coinData.setIsBalanceEqual(true)
|
||||
|
||||
if (srl.isRefreshing) {
|
||||
tvBalanceLine1.setTextColor(resources.getColor(R.color.primary))
|
||||
tvBalanceLine1.text = getString(R.string.loaded_wallet_verifying_in_blockchain)
|
||||
tvBalanceLine2.text = ""
|
||||
tvBalance.text = ""
|
||||
tvBalanceEquivalent.text = ""
|
||||
} else {
|
||||
val validator = BalanceValidator()
|
||||
validator.check(ctx, false)
|
||||
context?.let { ContextCompat.getColor(it, validator.color) }?.let { tvBalanceLine1?.setTextColor(it) }
|
||||
tvBalanceLine1?.text = getString(validator.firstLine)
|
||||
tvBalanceLine2?.text = getString(validator.getSecondLine(false))
|
||||
}
|
||||
|
||||
val engine = CoinEngineFactory.create(ctx)
|
||||
when {
|
||||
engine!!.hasBalanceInfo() -> {
|
||||
@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
|
||||
tvBalanceEquivalent.text = engine.balanceEquivalent
|
||||
}
|
||||
|
||||
ctx.card?.offlineBalance != null -> {
|
||||
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
Html.fromHtml(engine.offlineBalanceHTML, Html.FROM_HTML_MODE_LEGACY)
|
||||
else
|
||||
Html.fromHtml(engine.offlineBalanceHTML)
|
||||
tvBalance.text = html
|
||||
}
|
||||
|
||||
else -> tvBalance.text = ""
|
||||
}
|
||||
|
||||
if (ctx.card!!.tokenSymbol.length > 1) {
|
||||
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
Html.fromHtml(ctx.blockchainName, Html.FROM_HTML_MODE_LEGACY)
|
||||
else
|
||||
Html.fromHtml(ctx.blockchainName)
|
||||
tvBlockchain.text = html
|
||||
} else
|
||||
tvBlockchain.text = ctx.blockchainName
|
||||
|
||||
}
|
||||
|
||||
private fun requestBalanceAndUnspentTransactions() {
|
||||
if (UtilHelper.isOnline(context as Activity)) {
|
||||
val coinEngine = CoinEngineFactory.create(ctx)
|
||||
requestCounter++
|
||||
coinEngine!!.requestBalanceAndUnspentTransactions(
|
||||
object : CoinEngine.BlockchainRequestsCallbacks {
|
||||
|
||||
override fun onComplete(success: Boolean) {
|
||||
LOG.i(TAG, "requestBalanceAndUnspentTransactions onComplete: $success, request counter $requestCounter")
|
||||
if (activity == null) return
|
||||
requestCounter--
|
||||
if (!success) {
|
||||
LOG.e(TAG, "requestBalanceAndUnspentTransactions ctx.error: " + ctx.error)
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
||||
override fun onProgress() {
|
||||
if (activity == null) return
|
||||
LOG.i(TAG, "requestBalanceAndUnspentTransactions onProgress")
|
||||
// update()
|
||||
}
|
||||
|
||||
override fun allowAdvance(): Boolean {
|
||||
return try {
|
||||
context?.let { UtilHelper.isOnline(it) }!!
|
||||
} catch (e: KotlinNullPointerException) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
ctx.error = getString(R.string.general_error_no_connection)
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refresh(clearData: Boolean = true) {
|
||||
if (ctx.card == null) return
|
||||
|
||||
// clear all card data and request again
|
||||
ctx.coinData.clearInfo()
|
||||
|
||||
if (clearData) {
|
||||
ctx.error = null
|
||||
ctx.message = null
|
||||
}
|
||||
|
||||
LOG.w(TAG, "============= START REFRESH")
|
||||
requestCounter = 0
|
||||
srl?.isRefreshing = true
|
||||
|
||||
update()
|
||||
|
||||
ctx.coinData.setIsBalanceEqual(true)
|
||||
|
||||
|
||||
requestBalanceAndUnspentTransactions()
|
||||
|
||||
if (requestCounter == 0) {
|
||||
// if no connection and no requests posted
|
||||
srl?.isRefreshing = false
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = TagFragment::class.java.simpleName
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue