Updated on 2026-08-14
This commit is contained in:
parent
3e26e9ee19
commit
a1e6201915
11 changed files with 224 additions and 29 deletions
|
|
@ -206,4 +206,6 @@ class CardManager(
|
|||
terminalKeys = terminalKeys
|
||||
)
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -13,13 +13,13 @@ interface CardManagerDelegate {
|
|||
/**
|
||||
* It is called when user is expected to scan a Tangem Card with an Android device.
|
||||
*/
|
||||
fun onNfcSessionStarted()
|
||||
fun onNfcSessionStarted(cardId: String?)
|
||||
|
||||
/**
|
||||
* It is called when security delay is triggered by the card.
|
||||
* A user is expected to hold the card until the security delay is over.
|
||||
*/
|
||||
fun onSecurityDelay(ms: Int)
|
||||
fun onSecurityDelay(ms: Int, totalDurationSeconds: Int)
|
||||
|
||||
/**
|
||||
* It is called when user takes the card away from the Android device during the scanning
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ sealed class TaskError(description: String? = null) : Exception(description) {
|
|||
class Busy() : TaskError()
|
||||
class TagLost() : TaskError()
|
||||
|
||||
class ErrorProcessingCommand : TaskError()
|
||||
class ErrorProcessingCommand(description: String? = null) : TaskError(description)
|
||||
class InvalidState : TaskError()
|
||||
class InsNotSupported : TaskError()
|
||||
class InvalidParams : TaskError()
|
||||
|
|
@ -73,6 +73,7 @@ abstract class Task<T> {
|
|||
var delegate: CardManagerDelegate? = null
|
||||
var reader: CardReader? = null
|
||||
var performPreflightRead: Boolean = true
|
||||
var securityDelayDuration: Int = 0
|
||||
|
||||
/**
|
||||
* This method should be called to run the [Task] and perform all its operations.
|
||||
|
|
@ -82,7 +83,7 @@ abstract class Task<T> {
|
|||
*/
|
||||
fun run(cardEnvironment: CardEnvironment,
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
delegate?.onNfcSessionStarted()
|
||||
delegate?.onNfcSessionStarted(cardEnvironment.cardId)
|
||||
reader?.openSession()
|
||||
Log.i(this::class.simpleName!!, "Nfc task is started")
|
||||
|
||||
|
|
@ -161,7 +162,7 @@ abstract class Task<T> {
|
|||
StatusWord.NeedPause -> {
|
||||
// When NeedPause is returned from the card whenever security delay is triggered.
|
||||
val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment)
|
||||
if (remainingTime != null) delegate?.onSecurityDelay(remainingTime)
|
||||
if (remainingTime != null) delegate?.onSecurityDelay(remainingTime, securityDelayDuration)
|
||||
Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} triggered security delay of $remainingTime milliseconds")
|
||||
sendRequest(command, commandApdu, cardEnvironment, callback)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#008577</color>
|
||||
<color name="colorPrimaryDark">#00574B</color>
|
||||
<color name="colorAccent">#D81B60</color>
|
||||
<color name="colorPrimary">#027aff</color>
|
||||
<color name="colorPrimaryDark">#027AFF</color>
|
||||
<color name="colorAccent">#027AFF</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
package com.tangem.tangem_sdk_new
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.view.View
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.CardManagerDelegate
|
||||
import com.tangem.Log
|
||||
import com.tangem.LoggerInterface
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tangem_sdk_new.extensions.hide
|
||||
import com.tangem.tangem_sdk_new.extensions.show
|
||||
import com.tangem.tangem_sdk_new.nfc.NfcReader
|
||||
import com.tangem.tangem_sdk_new.ui.NfcEnableDialog
|
||||
import com.tangem.tangem_sdk_new.ui.TouchCardAnimation
|
||||
|
|
@ -28,13 +32,13 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
setLogger()
|
||||
}
|
||||
|
||||
override fun onNfcSessionStarted() {
|
||||
override fun onNfcSessionStarted(cardId: String?) {
|
||||
reader.readingCancelled = false
|
||||
postUI { showReadingDialog(activity) }
|
||||
postUI { showReadingDialog(activity, cardId) }
|
||||
if (!reader.nfcEnabled) showNFCEnableDialog()
|
||||
}
|
||||
|
||||
private fun showReadingDialog(activity: FragmentActivity) {
|
||||
private fun showReadingDialog(activity: FragmentActivity, cardId: String?) {
|
||||
val dialogView = activity.getLayoutInflater().inflate(R.layout.nfc_bottom_sheet, null)
|
||||
readingDialog = BottomSheetDialog(activity)
|
||||
readingDialog?.setContentView(dialogView)
|
||||
|
|
@ -46,6 +50,11 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
activity, readingDialog!!.ivHandCardHorizontal,
|
||||
readingDialog!!.ivHandCardVertical, readingDialog!!.llHand, readingDialog!!.llNfc)
|
||||
nfcDeviceAntenna.init()
|
||||
if (cardId != null) {
|
||||
readingDialog?.tvCard?.visibility = View.VISIBLE
|
||||
readingDialog?.tvCardId?.visibility = View.VISIBLE
|
||||
readingDialog?.tvCardId?.text = cardId
|
||||
}
|
||||
}
|
||||
readingDialog?.setOnCancelListener {
|
||||
reader.readingCancelled = true
|
||||
|
|
@ -60,21 +69,36 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
activity.supportFragmentManager.let { nfcEnableDialog?.show(it, NfcEnableDialog.TAG) }
|
||||
}
|
||||
|
||||
override fun onSecurityDelay(ms: Int) {
|
||||
override fun onSecurityDelay(ms: Int, totalDurationSeconds: Int) {
|
||||
postUI {
|
||||
readingDialog?.lTouchCard?.visibility = View.GONE
|
||||
readingDialog?.lTouchCard?.hide()
|
||||
readingDialog?.tvRemainingTime?.text = ms.div(100).toString()
|
||||
readingDialog?.flSecurityDelay?.visibility = View.VISIBLE
|
||||
readingDialog?.flSecurityDelay?.show()
|
||||
readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_security_delay)
|
||||
readingDialog?.tvTaskText?.text =
|
||||
activity.getText(R.string.dialog_security_delay_description)
|
||||
// readingDialog?.pbSecurityDelay?.isIndeterminate = false
|
||||
|
||||
if (readingDialog?.pbSecurityDelay?.max != totalDurationSeconds) {
|
||||
readingDialog?.pbSecurityDelay?.max = totalDurationSeconds
|
||||
}
|
||||
readingDialog?.pbSecurityDelay?.progress = totalDurationSeconds - ms + 100
|
||||
|
||||
val animation = ObjectAnimator.ofInt(
|
||||
readingDialog?.pbSecurityDelay,
|
||||
"progress",
|
||||
totalDurationSeconds - ms,
|
||||
totalDurationSeconds - ms + 100)
|
||||
animation.duration = 500
|
||||
animation.interpolator = DecelerateInterpolator()
|
||||
animation.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTagLost() {
|
||||
postUI {
|
||||
readingDialog?.lTouchCard?.visibility = View.VISIBLE
|
||||
readingDialog?.flSecurityDelay?.visibility = View.GONE
|
||||
readingDialog?.lTouchCard?.show()
|
||||
readingDialog?.flSecurityDelay?.hide()
|
||||
readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_ready_to_scan)
|
||||
readingDialog?.tvTaskText?.text = activity.getText(R.string.dialog_scan_text)
|
||||
}
|
||||
|
|
@ -82,9 +106,9 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
|
||||
override fun onNfcSessionCompleted() {
|
||||
postUI {
|
||||
readingDialog?.lTouchCard?.visibility = View.GONE
|
||||
readingDialog?.flSecurityDelay?.visibility = View.GONE
|
||||
readingDialog?.flCompletion?.visibility = View.VISIBLE
|
||||
readingDialog?.lTouchCard?.hide()
|
||||
readingDialog?.flSecurityDelay?.hide()
|
||||
readingDialog?.flCompletion?.show()
|
||||
readingDialog?.ivCompletion?.setImageDrawable(activity.getDrawable(R.drawable.ic_done_135dp))
|
||||
}
|
||||
postUI(300) { readingDialog?.dismiss() }
|
||||
|
|
@ -92,10 +116,10 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
|
||||
override fun onError(error: TaskError?) {
|
||||
postUI {
|
||||
readingDialog?.lTouchCard?.visibility = View.GONE
|
||||
readingDialog?.flSecurityDelay?.visibility = View.GONE
|
||||
readingDialog?.flCompletion?.visibility = View.VISIBLE
|
||||
readingDialog?.ivCompletion?.setImageDrawable(activity.getDrawable(R.drawable.ic_error_outline_135dp))
|
||||
readingDialog?.lTouchCard?.hide()
|
||||
readingDialog?.flSecurityDelay?.hide()
|
||||
readingDialog?.flCompletion?.hide()
|
||||
readingDialog?.flError?.show()
|
||||
readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_error)
|
||||
readingDialog?.tvTaskText?.text = if (error != null) error::class.simpleName else ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.tangem_sdk_new.extensions
|
||||
|
||||
import android.view.View
|
||||
|
||||
internal fun View.show() {
|
||||
this.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
internal fun View.hide() {
|
||||
this.visibility = View.GONE
|
||||
}
|
||||
6
tangem-sdk/src/main/res/drawable/ic_error.xml
Normal file
6
tangem-sdk/src/main/res/drawable/ic_error.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<vector android:height="100dp" android:tint="#FF9D30"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="100dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M12,19m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M10,3h4v12h-4z"/>
|
||||
</vector>
|
||||
10
tangem-sdk/src/main/res/drawable/ic_nfc.xml
Normal file
10
tangem-sdk/src/main/res/drawable/ic_nfc.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M10,0C4.48,0 0,4.48 0,10s4.48,10 10,10 10,-4.48 10,-10S15.52,0 10,0zM6.46,12.45l-1.36,-0.62c0.28,-0.61 0.41,-1.24 0.4,-1.86a4.42,4.42 0,0 0,-0.4 -1.8l1.36,-0.63c0.35,0.75 0.53,1.56 0.54,2.4 0.01,0.86 -0.17,1.7 -0.54,2.51zM9.53,14.01l-1.3,-0.74c0.52,-0.92 0.78,-1.98 0.78,-3.15 0,-1.19 -0.27,-2.33 -0.8,-3.4l1.34,-0.67c0.64,1.28 0.96,2.65 0.96,4.07 0,1.43 -0.33,2.74 -0.98,3.89zM12.67,15.33l-1.35,-0.66c0.78,-1.6 1.18,-3.18 1.18,-4.69 0,-1.51 -0.4,-3.07 -1.18,-4.64l1.34,-0.67C13.56,6.45 14,8.23 14,9.98c0,1.74 -0.44,3.54 -1.33,5.35z"
|
||||
android:fillColor="#027AFF"
|
||||
android:fillType="nonZero"/>
|
||||
</vector>
|
||||
45
tangem-sdk/src/main/res/drawable/pb_circle.xml
Normal file
45
tangem-sdk/src/main/res/drawable/pb_circle.xml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/secondaryProgress">
|
||||
<shape
|
||||
android:shape="ring"
|
||||
android:thickness="5dp"
|
||||
android:useLevel="true">
|
||||
|
||||
<gradient
|
||||
android:centerColor="#eae9e9"
|
||||
android:endColor="#eae9e9"
|
||||
android:startColor="#eae9e9"
|
||||
android:type="sweep" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:id="@android:id/progress">
|
||||
<rotate
|
||||
android:fromDegrees="270"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toDegrees="270">
|
||||
|
||||
<shape
|
||||
android:shape="ring"
|
||||
android:thickness="5dp"
|
||||
android:useLevel="true">
|
||||
|
||||
<rotate
|
||||
android:fromDegrees="0"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toDegrees="360" />
|
||||
|
||||
<gradient
|
||||
android:centerColor="@color/colorAccent"
|
||||
android:endColor="@color/colorAccent"
|
||||
android:startColor="@color/colorAccent"
|
||||
android:type="sweep" />
|
||||
|
||||
</shape>
|
||||
</rotate>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
@ -1,11 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_behavior="@string/bottom_sheet_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="#e2e5e6"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="14dp"
|
||||
android:src="@drawable/ic_nfc" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCard"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:backgroundTint="#f3f5f6"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:letterSpacing="0.02"
|
||||
android:text="@string/header_card"
|
||||
android:textColor="#000000"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="normal"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCardId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:gravity="center"
|
||||
android:letterSpacing="0.02"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/colorAccent"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="normal"
|
||||
tools:text="cb22000000027374"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTaskTitle"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -34,13 +83,20 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="40dp" />
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:textColor="@color/colorAccent"
|
||||
android:textSize="60dp"
|
||||
android:textStyle="normal" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbSecurityDelay"
|
||||
android:layout_width="135dp"
|
||||
android:layout_height="135dp"
|
||||
android:layout_gravity="center" />
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="false"
|
||||
android:progressDrawable="@drawable/pb_circle"
|
||||
android:secondaryProgress="100" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
|
@ -55,8 +111,47 @@
|
|||
android:id="@+id/ivCompletion"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_done_135dp"
|
||||
android:layout_gravity="center"/>
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_done_135dp" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbCompletion"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="false"
|
||||
android:progress="100"
|
||||
android:progressDrawable="@drawable/pb_circle"
|
||||
android:secondaryProgress="100" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/flError"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivError"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_error" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbError"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="false"
|
||||
android:progress="100"
|
||||
android:progressDrawable="@drawable/pb_circle"
|
||||
android:progressTint="#FF9D30"
|
||||
android:secondaryProgress="100" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@
|
|||
<string name="dialog_error">Error</string>
|
||||
<string name="dialog_ready_to_scan">Ready to Scan</string>
|
||||
<string name="dialog_scan_text">Scan card with your phone as shown above</string>
|
||||
<string name="header_card">Card</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue