Updated on 2026-08-14

This commit is contained in:
Tangem 2020-02-11 10:38:12 +03:00
parent 3e26e9ee19
commit a1e6201915
11 changed files with 224 additions and 29 deletions

View file

@ -206,4 +206,6 @@ class CardManager(
terminalKeys = terminalKeys
)
}
companion object
}

View file

@ -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

View file

@ -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)
}

View file

@ -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>

View file

@ -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 ""
}

View file

@ -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
}

View 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>

View 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>

View 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>

View file

@ -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>

View file

@ -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>