Updated on 2026-08-14
This commit is contained in:
parent
5105528ddc
commit
0131e08b68
201 changed files with 600 additions and 693 deletions
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.devkit
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.tangem.devkit._arch.structure.ILog
|
||||
import com.tangem.devkit._arch.structure.ItemLogger
|
||||
import com.tangem.devkit.commons.TangemLogger
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AppTangemDemo : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
AppTangemDemo.appInstance = this
|
||||
setupLoggers()
|
||||
}
|
||||
|
||||
private fun setupLoggers() {
|
||||
Log.setLogger(TangemLogger())
|
||||
ILog.setLogger(ItemLogger())
|
||||
}
|
||||
|
||||
fun sharedPreferences(name: String = "DevKitApp", mode: Int = Context.MODE_PRIVATE): SharedPreferences {
|
||||
return getSharedPreferences(name, mode)
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var appInstance: AppTangemDemo
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
package com.tangem.devkit
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import kotlinx.android.synthetic.main.old_activity_main.*
|
||||
|
||||
class Old_MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var tangemSdk: TangemSdk
|
||||
private lateinit var cardId: String
|
||||
private lateinit var issuerData: ByteArray
|
||||
private lateinit var issuerDataSignature: ByteArray
|
||||
private var issuerDataCounter: Int = 1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.old_activity_main)
|
||||
|
||||
tangemSdk = TangemSdk.init(this)
|
||||
|
||||
btn_scan?.setOnClickListener { _ ->
|
||||
tangemSdk.scanCard { taskEvent ->
|
||||
when (taskEvent) {
|
||||
is CompletionResult.Success -> {
|
||||
// Handle returned card data
|
||||
val card = taskEvent.data
|
||||
cardId = card.cardId
|
||||
runOnUiThread {
|
||||
tv_card_cid?.text = cardId
|
||||
btn_create_wallet.isEnabled = true
|
||||
tv_card_cid?.text = cardId
|
||||
btn_sign.isEnabled = true
|
||||
btn_read_issuer_data.isEnabled = true
|
||||
btn_read_issuer_extra_data.isEnabled = true
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
btn_purge_wallet.isEnabled = true
|
||||
btn_create_wallet.isEnabled = true
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_sign?.setOnClickListener { _ ->
|
||||
tangemSdk.sign(
|
||||
createSampleHashes(),
|
||||
cardId) {
|
||||
when (it) {
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." }
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_read_issuer_data?.setOnClickListener { _ ->
|
||||
tangemSdk.readIssuerData(cardId) {
|
||||
when (it) {
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is CompletionResult.Success -> runOnUiThread {
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
tv_card_cid?.text = it.data.issuerData.contentToString()
|
||||
issuerData = it.data.issuerData
|
||||
issuerDataSignature = it.data.issuerDataSignature
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_write_issuer_data?.setOnClickListener { _ ->
|
||||
tangemSdk.writeIssuerData(
|
||||
cardId,
|
||||
issuerData,
|
||||
issuerDataSignature) {
|
||||
when (it) {
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is CompletionResult.Success -> runOnUiThread {
|
||||
tv_card_cid?.text = it.data.cardId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_read_issuer_extra_data?.setOnClickListener { _ ->
|
||||
tangemSdk.readIssuerExtraData(cardId) {
|
||||
when (it) {
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is CompletionResult.Success -> runOnUiThread {
|
||||
issuerDataCounter = (it.data.issuerDataCounter ?: 0) + 1
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
tv_card_cid?.text = "Read ${it.data.issuerData.size} bytes of data."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_purge_wallet?.setOnClickListener { _ ->
|
||||
tangemSdk.purgeWallet(
|
||||
cardId) {
|
||||
when (it) {
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is CompletionResult.Success -> runOnUiThread {
|
||||
tv_card_cid?.text = it.data.status.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_create_wallet?.setOnClickListener { _ ->
|
||||
tangemSdk.createWallet(
|
||||
cardId) {
|
||||
when (it) {
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is CompletionResult.Success -> runOnUiThread {
|
||||
tv_card_cid?.text = it.data.status.name
|
||||
btn_sign.isEnabled = true
|
||||
btn_read_issuer_data.isEnabled = true
|
||||
btn_purge_wallet.isEnabled = true
|
||||
btn_create_wallet.isEnabled = false
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_read_write_user_data?.setOnClickListener { startActivity(Intent(this, TestUserDataActivity::class.java)) }
|
||||
}
|
||||
|
||||
private fun createSampleHashes(): Array<ByteArray> {
|
||||
val hash1 = ByteArray(32) { 1 }
|
||||
val hash2 = ByteArray(32) { 2 }
|
||||
return arrayOf(hash1, hash2)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
package com.tangem.devkit
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.SessionError
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import kotlinx.android.synthetic.main.activity_test_user_data.*
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TestUserDataActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var tangemSdk: TangemSdk
|
||||
private lateinit var writeOptions: WriteOptions
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_test_user_data)
|
||||
|
||||
init()
|
||||
initWriteOptions()
|
||||
}
|
||||
|
||||
private fun init() {
|
||||
tangemSdk = TangemSdk.init(this)
|
||||
|
||||
btn_scan?.setOnClickListener { _ ->
|
||||
tangemSdk.scanCard { taskEvent ->
|
||||
when (taskEvent) {
|
||||
is CompletionResult.Success -> {
|
||||
// Handle returned card data
|
||||
writeOptions.cardId = taskEvent.data.cardId
|
||||
runOnUiThread { showReadWriteSection(true) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
btn_write.setOnClickListener {
|
||||
if (writeOptions.cardId == null) return@setOnClickListener
|
||||
|
||||
tangemSdk.writeUserData(
|
||||
writeOptions.cardId!!,
|
||||
writeOptions.userData,
|
||||
writeOptions.userProtectedData,
|
||||
writeOptions.userCounter,
|
||||
writeOptions.userProtectedCounter
|
||||
) {
|
||||
when (it) {
|
||||
is CompletionResult.Failure -> handleError(tv_write_result, it.error)
|
||||
is CompletionResult.Success -> {
|
||||
runOnUiThread { tv_write_result?.text = "Success" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
btn_read.setOnClickListener {
|
||||
if (writeOptions.cardId == null) return@setOnClickListener
|
||||
|
||||
tangemSdk.readUserData(writeOptions.cardId!!) {
|
||||
when (it) {
|
||||
is CompletionResult.Failure -> handleError(tv_write_result, it.error)
|
||||
is CompletionResult.Success -> {
|
||||
runOnUiThread {
|
||||
|
||||
tv_read_result?.text = "Success"
|
||||
|
||||
writeOptions.userData = it.data.userData
|
||||
writeOptions.userProtectedData = it.data.userProtectedData
|
||||
writeOptions.userCounter = it.data.userCounter
|
||||
writeOptions.userProtectedCounter = it.data.userProtectedCounter
|
||||
|
||||
tv_card_cid.text = it.data.cardId
|
||||
tv_data.text = String(it.data.userData, StandardCharsets.US_ASCII)
|
||||
tv_protected_data.text = String(it.data.userProtectedData, StandardCharsets.US_ASCII)
|
||||
tv_counter.text = it.data.userCounter.toString()
|
||||
tv_protected_counter.text = it.data.userProtectedCounter.toString()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleError(tv: TextView, error: SessionError) {
|
||||
if (error is SessionError.UserCancelled) return
|
||||
|
||||
runOnUiThread { tv.text = error::class.simpleName }
|
||||
}
|
||||
|
||||
private fun initWriteOptions() {
|
||||
writeOptions = WriteOptions()
|
||||
|
||||
chb_with_ud.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateData(buttonView) }
|
||||
chb_with_ud_protected.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedData(buttonView) }
|
||||
chb_with_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateCounter(buttonView) }
|
||||
chb_with_protected_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedCounter(buttonView) }
|
||||
chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) }
|
||||
}
|
||||
|
||||
private fun showReadWriteSection(show: Boolean) {
|
||||
val state = if (show) View.VISIBLE else View.GONE
|
||||
cl_read_write.visibility = state
|
||||
}
|
||||
}
|
||||
|
||||
class WriteOptions {
|
||||
var cardId: String? = null
|
||||
var userData: ByteArray? = null
|
||||
var userProtectedData: ByteArray? = null
|
||||
var userCounter: Int? = null
|
||||
var userProtectedCounter: Int? = null
|
||||
var pin2: String? = null
|
||||
|
||||
fun updateData(chbx: CompoundButton) {
|
||||
val value = "simple user data".toByteArray()
|
||||
userData = if (chbx.isChecked) value else null
|
||||
}
|
||||
|
||||
fun updateProtectedData(chbx: CompoundButton) {
|
||||
val value = "protected user data".toByteArray()
|
||||
userProtectedData = if (chbx.isChecked) value else null
|
||||
}
|
||||
|
||||
fun updateCounter(chbx: CompoundButton) {
|
||||
val value = if (userCounter == null) 0 else userCounter!! + 1
|
||||
userCounter = if (chbx.isChecked) value else null
|
||||
}
|
||||
|
||||
fun updateProtectedCounter(chbx: CompoundButton) {
|
||||
val value = if (userProtectedCounter == null) 0 else userProtectedCounter!! + 1
|
||||
userProtectedCounter = if (chbx.isChecked) value else null
|
||||
}
|
||||
|
||||
fun updatePin2(chbx: CompoundButton) {
|
||||
val value = SessionEnvironment.DEFAULT_PIN2
|
||||
pin2 = if (chbx.isChecked) value else null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.devkit._arch
|
||||
|
||||
import android.util.Log
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.Nullable
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
private val mPending: AtomicBoolean = AtomicBoolean(false)
|
||||
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
|
||||
if (hasActiveObservers()) {
|
||||
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
|
||||
}
|
||||
|
||||
// Observe the internal MutableLiveData
|
||||
super.observe(owner, Observer {
|
||||
if (mPending.compareAndSet(true, false)) {
|
||||
observer.onChanged(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun setValue(@Nullable t: T?) {
|
||||
mPending.set(true)
|
||||
super.setValue(t)
|
||||
}
|
||||
|
||||
@MainThread
|
||||
fun call() {
|
||||
setValue(null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SingleLiveEvent"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.devkit._arch.structure
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
typealias Payload = MutableMap<String, Any?>
|
||||
|
||||
interface PayloadHolder {
|
||||
val payload: Payload
|
||||
|
||||
fun get(key: String): Any? = payload[key]
|
||||
fun remove(key: String): Any? = payload.remove(key)
|
||||
fun set(key: String, value: Any?) {
|
||||
payload[key] = value
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.devkit._arch.structure
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Id
|
||||
|
||||
class StringId(val value: String) : Id
|
||||
|
||||
class StringResId(val value: Int) : Id
|
||||
|
||||
enum class Additional : Id {
|
||||
UNDEFINED,
|
||||
JSON_INCOMING,
|
||||
JSON_OUTGOING,
|
||||
JSON_TAILS,
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.devkit._arch.structure
|
||||
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Logger
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.TagLogger
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.profiling.SimpleLogProfiler
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object ILog {
|
||||
|
||||
private var logger: Logger? = null
|
||||
|
||||
fun setLogger(logger: Logger) {
|
||||
ILog.logger = logger
|
||||
}
|
||||
|
||||
fun d(from: Any, msg: Any?, value: Any? = null) {
|
||||
logger?.d(from, stringOf(msg), value)
|
||||
}
|
||||
|
||||
fun w(from: Any, msg: Any?, value: Any? = null) {
|
||||
logger?.w(from, stringOf(msg), value)
|
||||
}
|
||||
|
||||
fun e(from: Any, msg: Any?, value: Any? = null) {
|
||||
logger?.e(from, stringOf(msg), value)
|
||||
}
|
||||
}
|
||||
|
||||
class ItemLogger : TagLogger("ITEM") {
|
||||
init {
|
||||
msProfiler = SimpleLogProfiler()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.devkit._arch.structure.abstraction
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun List<Item>.findItem(id: Id): Item? {
|
||||
var foundItem: Item? = null
|
||||
iterate {
|
||||
if (it.id == id) {
|
||||
foundItem = it
|
||||
return@iterate
|
||||
}
|
||||
}
|
||||
return foundItem
|
||||
}
|
||||
|
||||
fun List<Item>.iterate(func: (Item) -> Unit) {
|
||||
forEach {
|
||||
when (it) {
|
||||
is BaseItem -> func(it)
|
||||
is ItemGroup -> {
|
||||
func(it)
|
||||
it.itemList.iterate(func)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.devkit._arch.structure.abstraction
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
interface UpdateBy<B>{
|
||||
fun update(value: B)
|
||||
}
|
||||
|
||||
interface Item: UpdateBy<Item> {
|
||||
val id: Id
|
||||
var parent: Item?
|
||||
var viewModel: ItemViewModel
|
||||
|
||||
fun added(parent: Item) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
fun removed(parent: Item) {
|
||||
this.parent = null
|
||||
}
|
||||
|
||||
fun <D> getData(): D? = viewModel.data as? D
|
||||
|
||||
fun setData(value: Any?) {
|
||||
viewModel.data = value
|
||||
}
|
||||
|
||||
fun restoreDefaultData() {
|
||||
setData(viewModel.defaultData)
|
||||
}
|
||||
}
|
||||
|
||||
open class BaseItem(
|
||||
override val id: Id,
|
||||
override var viewModel: ItemViewModel
|
||||
) : Item {
|
||||
|
||||
override var parent: Item? = null
|
||||
|
||||
override fun update(value: Item) {
|
||||
viewModel.update(value.viewModel)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.devkit._arch.structure.abstraction
|
||||
|
||||
import com.tangem.devkit._arch.structure.ILog
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ItemGroup : Item {
|
||||
val itemList: MutableList<Item>
|
||||
|
||||
fun setItems(list: MutableList<Item>)
|
||||
fun getItems(): MutableList<Item>
|
||||
fun addItem(item: Item)
|
||||
fun removeItem(item: Item)
|
||||
fun clear()
|
||||
}
|
||||
|
||||
open class SimpleItemGroup(
|
||||
override val id: Id,
|
||||
override var viewModel: ItemViewModel = BaseItemViewModel()
|
||||
) : ItemGroup {
|
||||
|
||||
override var parent: Item? = null
|
||||
override val itemList: MutableList<Item> = mutableListOf()
|
||||
|
||||
override fun setItems(list: MutableList<Item>) {
|
||||
ILog.d(this, "setItems into: $id, count: ${list.size}")
|
||||
itemList.forEach { it.removed(this) }
|
||||
itemList.clear()
|
||||
list.forEach { addItem(it) }
|
||||
}
|
||||
|
||||
override fun getItems(): MutableList<Item> = itemList
|
||||
|
||||
override fun addItem(item: Item) {
|
||||
ILog.d(this, "addItem into: $id, who: ${item.id}")
|
||||
itemList.add(item)
|
||||
item.added(this)
|
||||
}
|
||||
|
||||
override fun removeItem(item: Item) {
|
||||
ILog.d(this, "removeItem from: $id, which: ${item.id}")
|
||||
itemList.remove(item)
|
||||
item.removed(this)
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
ILog.d(this, "clear $id")
|
||||
itemList.clear()
|
||||
}
|
||||
|
||||
override fun update(value: Item) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.tangem.devkit._arch.structure.abstraction
|
||||
|
||||
import com.tangem.devkit._arch.structure.ILog
|
||||
import com.tangem.devkit._arch.structure.Payload
|
||||
import com.tangem.devkit._arch.structure.PayloadHolder
|
||||
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
typealias ValueChanged<V> = (V?) -> Unit
|
||||
typealias SafeValueChanged<V> = (V) -> Unit
|
||||
|
||||
class KeyValue(val key: String, val value: Any)
|
||||
|
||||
class ViewState(
|
||||
isVisible: Boolean? = null,
|
||||
bgColor: Int? = -1
|
||||
) : UpdateBy<ViewState> {
|
||||
|
||||
class State<T>(
|
||||
stateValue: T,
|
||||
var onValueChanged: SafeValueChanged<T>? = null
|
||||
) {
|
||||
var value = stateValue
|
||||
set(value) {
|
||||
if (preventSameChanges && field == value) return
|
||||
|
||||
field = value
|
||||
onValueChanged?.invoke(value)
|
||||
}
|
||||
|
||||
internal var preventSameChanges = true
|
||||
}
|
||||
|
||||
var isVisibleState = State(isVisible)
|
||||
var backgroundColor = State(bgColor)
|
||||
var descriptionVisibility = State(0x00000008)
|
||||
|
||||
internal fun preventSameChanges(isPrevented: Boolean) {
|
||||
val states = listOf(isVisibleState, backgroundColor, descriptionVisibility)
|
||||
states.forEach { it.preventSameChanges = isPrevented }
|
||||
}
|
||||
|
||||
override fun update(value: ViewState) {
|
||||
// isVisibleState.update(value.isVisibleState)
|
||||
// backgroundColor.update(value.backgroundColor)
|
||||
// descriptionVisibility.update(value.descriptionVisibility)
|
||||
}
|
||||
}
|
||||
|
||||
interface ItemViewModel : PayloadHolder, UpdateBy<ItemViewModel> {
|
||||
val viewState: ViewState
|
||||
var data: Any?
|
||||
var defaultData: Any?
|
||||
var onDataUpdated: ValueChanged<Any?>?
|
||||
|
||||
fun updateDataByView(data: Any?)
|
||||
}
|
||||
|
||||
open class BaseItemViewModel(
|
||||
value: Any? = null,
|
||||
override val viewState: ViewState = ViewState()
|
||||
) : ItemViewModel {
|
||||
|
||||
override val payload: Payload = mutableMapOf()
|
||||
|
||||
// Don't update it directly from a View. Use for it updateDataByView()
|
||||
override var data: Any? = value
|
||||
set(value) {
|
||||
if (handleDataUpdates(value)) field = value
|
||||
}
|
||||
|
||||
// Data for restoring initial value
|
||||
override var defaultData: Any? = value
|
||||
set(value) {
|
||||
field = value
|
||||
data = value
|
||||
}
|
||||
|
||||
// Use it for handling data updates in View
|
||||
override var onDataUpdated: ValueChanged<Any?>? = null
|
||||
|
||||
// When data updates directly it invokes onDataUpdated
|
||||
// return true = data will update
|
||||
// return false = data won't update
|
||||
protected open fun handleDataUpdates(value: Any?): Boolean {
|
||||
ILog.d(this, "handleDateUpdates: $value")
|
||||
onDataUpdated?.invoke(value)
|
||||
return true
|
||||
}
|
||||
|
||||
// Use it to update the data from a View. It disables onDataUpdated to prevent a callback loop
|
||||
override fun updateDataByView(data: Any?) {
|
||||
ILog.d(this, "data changed: $data")
|
||||
val callback = onDataUpdated
|
||||
onDataUpdated = null
|
||||
this.data = data
|
||||
onDataUpdated = callback
|
||||
}
|
||||
|
||||
override fun update(value: ItemViewModel) {
|
||||
viewState.update(value.viewState)
|
||||
defaultData = value.defaultData
|
||||
data = value.data
|
||||
payload.clear()
|
||||
payload.putAll(value.payload)
|
||||
}
|
||||
}
|
||||
|
||||
class ListViewModel(
|
||||
val itemList: List<KeyValue>,
|
||||
var selectedItem: Any?,
|
||||
override val viewState: ViewState = ViewState()
|
||||
) : BaseItemViewModel(selectedItem)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.devkit._arch.structure.abstraction
|
||||
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.common.Converter
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface DefaultConverter<A, B> {
|
||||
fun convert(from: A, default: B): B
|
||||
}
|
||||
|
||||
interface ItemsToModel<M> : DefaultConverter<List<Item>, M>
|
||||
interface ModelToItems<M> : Converter<M, List<Item>>
|
||||
|
||||
interface ModelConverter<M> : DefaultConverter<List<Item>, M>, Converter<M, List<Item>>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.devkit._arch.structure.abstraction
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
typealias PluginCallBackResult = (Any?) -> Item
|
||||
|
||||
interface Plugin : Item {
|
||||
fun invoke(data: Any?, asyncCallback: PluginCallBackResult? = null): Any?
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.devkit._arch.structure.impl
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.abstraction.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
open class TypedItem<D>(id: Id, viewModel: ItemViewModel) : BaseItem(id, viewModel) {
|
||||
open fun getTypedData(): D? = viewModel.data as? D
|
||||
}
|
||||
|
||||
open class TextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
|
||||
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
|
||||
: this(id, BaseItemViewModel(value, viewState))
|
||||
}
|
||||
|
||||
open class NumberItem(id: Id, viewModel: ItemViewModel) : TypedItem<Number>(id, viewModel) {
|
||||
constructor(id: Id, value: Number? = null, viewState: ViewState = ViewState())
|
||||
: this(id, BaseItemViewModel(value, viewState))
|
||||
}
|
||||
|
||||
open class BoolItem(id: Id, viewModel: ItemViewModel) : TypedItem<Boolean>(id, viewModel) {
|
||||
constructor(id: Id, value: Boolean? = null, viewState: ViewState = ViewState())
|
||||
: this(id, BaseItemViewModel(value, viewState))
|
||||
}
|
||||
|
||||
|
||||
open class EditTextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
|
||||
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
|
||||
: this(id, BaseItemViewModel(value, viewState))
|
||||
}
|
||||
|
||||
|
||||
open class SpinnerItem(id: Id, viewModel: ListViewModel) : TypedItem<ListViewModel>(id, viewModel) {
|
||||
constructor(id: Id, list: List<KeyValue>, selectedValue: Any?, viewState: ViewState = ViewState())
|
||||
: this(id, ListViewModel(list, selectedValue, viewState))
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.devkit._arch.widget
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.devkit._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.devkit._arch.widget.abstraction.ViewWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ItemWidgetBuilder {
|
||||
fun build(item: BaseItem, parent: ViewGroup): ViewWidget?
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.devkit._arch.widget
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.devkit._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.abstraction.ItemGroup
|
||||
import com.tangem.devkit._arch.widget.abstraction.ViewWidget
|
||||
import com.tangem.devkit._arch.widget.impl.LinearGroupWidget
|
||||
import com.tangem.devkit._arch.widget.impl.StubWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WidgetBuilder(
|
||||
private val itemBuilder: ItemWidgetBuilder
|
||||
) {
|
||||
|
||||
fun build(item: Item, parent: ViewGroup): ViewWidget? {
|
||||
return when (item) {
|
||||
is ItemGroup -> buildBlock(item, parent)
|
||||
is BaseItem -> itemBuilder.build(item, parent)
|
||||
else -> StubWidget(item.id, parent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildBlock(itemGroup: ItemGroup, parent: ViewGroup): ViewWidget {
|
||||
return when (itemGroup) {
|
||||
is ItemGroup -> {
|
||||
val linearBlock = LinearGroupWidget(parent, itemGroup)
|
||||
itemGroup.getItems().forEach { build(it, linearBlock.view as ViewGroup) }
|
||||
linearBlock
|
||||
}
|
||||
else -> StubWidget(itemGroup.id, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tangem.devkit._arch.widget.abstraction
|
||||
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.ColorRes
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.StringId
|
||||
import com.tangem.devkit._arch.structure.StringResId
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.abstraction.ViewState
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.colorFrom
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ViewWidget : LayoutHolder {
|
||||
val view: View
|
||||
var item: Item
|
||||
|
||||
fun getName(): String
|
||||
fun setBackgroundColor(@ColorRes colorId: Int?)
|
||||
}
|
||||
|
||||
abstract class BaseViewWidget(
|
||||
parent: ViewGroup,
|
||||
override var item: Item
|
||||
) : ViewWidget {
|
||||
|
||||
override val view: View = inflate(getLayoutId(), parent)
|
||||
|
||||
private var defaultBackground: Drawable? = view.background
|
||||
|
||||
init {
|
||||
subscribeToViewStateChanges(item.viewModel.viewState)
|
||||
initViewState(item.viewModel.viewState)
|
||||
}
|
||||
|
||||
protected open fun subscribeToViewStateChanges(viewState: ViewState) {
|
||||
viewState.isVisibleState.onValueChanged = { state ->
|
||||
state?.let { view.visibility = if (it) View.VISIBLE else View.GONE }
|
||||
}
|
||||
viewState.backgroundColor.onValueChanged = { setBackgroundColor(it) }
|
||||
}
|
||||
|
||||
protected open fun initViewState(viewState: ViewState) {
|
||||
viewState.preventSameChanges(false)
|
||||
viewState.isVisibleState.value = viewState.isVisibleState.value
|
||||
if (viewState.backgroundColor.value != -1) setBackgroundColor(viewState.backgroundColor.value)
|
||||
viewState.preventSameChanges(true)
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return when (val id = item.id) {
|
||||
is StringId -> id.value
|
||||
is StringResId -> view.resources.getString(id.value)
|
||||
else -> view.resources.getString(R.string.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setBackgroundColor(colorId: Int?) {
|
||||
val background = when {
|
||||
colorId == null -> null
|
||||
colorId == -1 -> defaultBackground
|
||||
else -> ColorDrawable(view.colorFrom(colorId))
|
||||
}
|
||||
view.background = background
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inflate(id: Int, parent: ViewGroup): View {
|
||||
val layoutId = if (id <= 0) R.layout.w_empty else id
|
||||
val inflatedView = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
|
||||
parent.addView(inflatedView)
|
||||
return inflatedView
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.devkit._arch.widget.impl
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.abstraction.ItemGroup
|
||||
import com.tangem.devkit._arch.widget.abstraction.BaseViewWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class LinearGroupWidget(
|
||||
parent: ViewGroup,
|
||||
itemGroup: ItemGroup
|
||||
) : BaseViewWidget(parent, itemGroup) {
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.w_personilize_block
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.devkit._arch.widget.impl
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.devkit._arch.structure.abstraction.BaseItemViewModel
|
||||
import com.tangem.devkit._arch.widget.abstraction.BaseViewWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class StubWidget(id: Id, parent: ViewGroup) : BaseViewWidget(parent, BaseItem(id, BaseItemViewModel())) {
|
||||
override fun getLayoutId(): Int = R.layout.w_empty
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.devkit._main
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.navigateUp
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.tangem.devkit.R
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private val mainVM: MainViewModel by viewModels<MainViewModel>()
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.a_main)
|
||||
|
||||
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
val host: NavHostFragment = supportFragmentManager
|
||||
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment? ?: return
|
||||
|
||||
val navController = host.navController
|
||||
appBarConfiguration = AppBarConfiguration(navController.graph)
|
||||
toolbar.setupWithNavController(navController, appBarConfiguration)
|
||||
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
val dest: String = try {
|
||||
resources.getResourceName(destination.id)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
destination.id.toString()
|
||||
}
|
||||
Log.d(this, "Navigated to $dest")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
return findNavController(R.id.nav_host_fragment).navigateUp(appBarConfiguration)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_activity_main, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
val switchItem = menu.findItem(R.id.action_toggle_description_visibility)
|
||||
switchItem.isChecked = mainVM.descriptionSwitchState
|
||||
return super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val result = when (item.itemId) {
|
||||
R.id.action_toggle_description_visibility -> {
|
||||
item.isChecked = !item.isChecked
|
||||
mainVM.switchToggled(item.isChecked)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
return if (result == null) super.onOptionsItemSelected(item) else true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.devkit._main
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.commands.CommandResponse
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class MainViewModel : ViewModel() {
|
||||
val ldDescriptionSwitch = MutableLiveData<Boolean>(false)
|
||||
var descriptionSwitchState = false
|
||||
|
||||
var commandResponse: CommandResponse? = null
|
||||
|
||||
fun switchToggled(state: Boolean) {
|
||||
descriptionSwitchState = state
|
||||
ldDescriptionSwitch.postValue(state)
|
||||
}
|
||||
|
||||
fun changeResponseEvent(commandResponse: CommandResponse?) {
|
||||
Log.d(this, "changeResponseEvent")
|
||||
val response = commandResponse ?: return
|
||||
|
||||
this.commandResponse = response
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.tangem.devkit._main.entryPoint
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._main.MainViewModel
|
||||
import com.tangem.devkit.extensions.view.beginDelayedTransition
|
||||
import com.tangem.devkit.ucase.getDefaultNavigationOptions
|
||||
import com.tangem.devkit.ucase.resources.ActionType
|
||||
import com.tangem.devkit.ucase.resources.MainResourceHolder
|
||||
import com.tangem.devkit.ucase.ui.BaseFragment
|
||||
import kotlinx.android.synthetic.main.fg_entry_point.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ActionListFragment : BaseFragment() {
|
||||
|
||||
private lateinit var rvActions: RecyclerView
|
||||
|
||||
private val mainActivityVM: MainViewModel by activityViewModels()
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_entry_point
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initRecyclerView()
|
||||
}
|
||||
|
||||
private fun initRecyclerView() {
|
||||
val layoutManager = LinearLayoutManager(activity)
|
||||
rvActions = rv_actions
|
||||
rvActions.layoutManager = layoutManager
|
||||
rvActions.addItemDecoration(DividerItemDecoration(activity, layoutManager.orientation))
|
||||
|
||||
val vhDataWrapper = VhExDataWrapper(MainResourceHolder, false)
|
||||
|
||||
rvActions.adapter = RvActionsAdapter(vhDataWrapper) { type, position, data ->
|
||||
navigateTo(data, options = getDefaultNavigationOptions())
|
||||
}
|
||||
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
|
||||
vhDataWrapper.descriptionIsVisible = it
|
||||
rvActions.beginDelayedTransition()
|
||||
rvActions.adapter?.notifyDataSetChanged()
|
||||
})
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val adapter: RvActionsAdapter = rvActions.adapter as? RvActionsAdapter ?: return
|
||||
|
||||
adapter.setItemList(getNavigateOptions())
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun getNavigateOptions(): MutableList<ActionType> {
|
||||
return mutableListOf(
|
||||
ActionType.Scan,
|
||||
ActionType.Sign,
|
||||
ActionType.Personalize,
|
||||
ActionType.Depersonalize
|
||||
// ActionType.CreateWallet,
|
||||
// ActionType.PurgeWallet,
|
||||
// ActionType.ReadIssuerData,
|
||||
// ActionType.WriteIssuerData,
|
||||
// ActionType.ReadIssuerExData,
|
||||
// ActionType.WriteIssuerExData,
|
||||
// ActionType.ReadUserData,
|
||||
// ActionType.WriteUserData
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.devkit._main.entryPoint
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit.ucase.resources.ActionRes
|
||||
import com.tangem.devkit.ucase.resources.ActionType
|
||||
import com.tangem.devkit.ucase.resources.MainResourceHolder
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseAdapter
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseVH
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvCallback
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class RvActionsAdapter(
|
||||
private val wrapper: VhExDataWrapper,
|
||||
private val callback: RvCallback<Int>
|
||||
) : RvBaseAdapter<RvActionsVH, ActionType>() {
|
||||
|
||||
override fun onBindViewHolder(holder: RvActionsVH, position: Int) {
|
||||
holder.bindData(itemList[position])
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RvActionsVH {
|
||||
val view = parent.inflate<View>(R.layout.vh_actions, false)
|
||||
return RvActionsVH(view, wrapper, callback)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class VhExDataWrapper(
|
||||
val resHolder: MainResourceHolder,
|
||||
var descriptionIsVisible: Boolean
|
||||
)
|
||||
|
||||
class RvActionsVH(
|
||||
itemView: View,
|
||||
private val wrapper: VhExDataWrapper,
|
||||
private val callback: RvCallback<Int>?
|
||||
) : RvBaseVH<ActionType>(itemView) {
|
||||
|
||||
private val tvAction = itemView.findViewById<TextView>(R.id.tv_title)
|
||||
private val containerDescription = itemView.findViewById<ViewGroup>(R.id.container_description)
|
||||
private val tvDescription = itemView.findViewById<TextView>(R.id.tv_description)
|
||||
|
||||
override fun bindData(data: ActionType) {
|
||||
val res: ActionRes = wrapper.resHolder.safeGet(data)
|
||||
tvAction.text = getString(res.resName)
|
||||
tvDescription.text = getString(res.resDescription)
|
||||
|
||||
itemView.setOnClickListener {
|
||||
res.resNavigation?.let { callback?.invoke(itemViewType, adapterPosition, it) }
|
||||
}
|
||||
|
||||
containerDescription.visibility = if (wrapper.descriptionIsVisible) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
fun RecyclerView.ViewHolder.getString(@StringRes id: Int?, ifNull: String = ""): String {
|
||||
val reqId = id ?: return ifNull
|
||||
return itemView.context.getString(reqId)
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.devkit.commons
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Store<M> {
|
||||
fun save(value: M)
|
||||
fun restore(): M
|
||||
}
|
||||
|
||||
interface KeyedStore<M> {
|
||||
fun save(key: String, value: M)
|
||||
fun restore(key: String): M
|
||||
fun restoreAll(): MutableMap<String, M>
|
||||
fun delete(key: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.tangem.devkit.commons
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
|
||||
class DialogController {
|
||||
var onDismissCallback: (() -> Unit)? = null
|
||||
var onShowCallback: (() -> Unit)? = null
|
||||
var view: View? = null
|
||||
|
||||
private var rawDialog: Dialog? = null
|
||||
|
||||
private var inShowingProcess = false
|
||||
private var inDismissingProcess = false
|
||||
private var autoReleaseOnDismiss = false
|
||||
|
||||
fun createAlert(context: Activity, resLayout: Int): AlertDialog {
|
||||
view = LayoutInflater.from(context).inflate(resLayout, null)
|
||||
rawDialog = AlertDialog.Builder(context).setView(view).create().apply {
|
||||
setOnShowListener { onShow() }
|
||||
setOnDismissListener { onDismiss() }
|
||||
}
|
||||
return rawDialog as AlertDialog
|
||||
}
|
||||
|
||||
fun set(dialog: Dialog) {
|
||||
rawDialog = dialog
|
||||
dialog.setOnShowListener { onShow() }
|
||||
dialog.setOnDismissListener { onDismiss() }
|
||||
}
|
||||
|
||||
private fun onShow() {
|
||||
inShowingProcess = false
|
||||
onShowCallback?.invoke()
|
||||
}
|
||||
|
||||
private fun onDismiss() {
|
||||
inDismissingProcess = false
|
||||
if (autoReleaseOnDismiss) release()
|
||||
onDismissCallback?.invoke()
|
||||
}
|
||||
|
||||
fun show() {
|
||||
val dialog = rawDialog ?: return
|
||||
if (inShowingProcess) return
|
||||
if (dialog.isShowing) return
|
||||
|
||||
inShowingProcess = true
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
fun dismiss(autoRelease: Boolean = true) {
|
||||
val dialog = rawDialog ?: return
|
||||
if (inDismissingProcess) return
|
||||
if (!dialog.isShowing) return
|
||||
|
||||
autoReleaseOnDismiss = autoRelease
|
||||
inDismissingProcess = true
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
fun release() {
|
||||
onDismissCallback = null
|
||||
onShowCallback = null
|
||||
|
||||
inShowingProcess = false
|
||||
inDismissingProcess = false
|
||||
autoReleaseOnDismiss = false
|
||||
|
||||
view = null
|
||||
rawDialog = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.devkit.commons
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun <A, B> performAction(a: A?, b: B?, action: (A, B) -> Unit, onFail: (() -> Unit)? = null) {
|
||||
if (a != null && b != null) action(a, b)
|
||||
else onFail?.invoke()
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.devkit.commons
|
||||
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.TagLogger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TangemLogger : TagLogger("TangemDemo")
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package com.tangem.devkit.commons.view
|
||||
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.StringId
|
||||
import com.tangem.devkit._arch.structure.abstraction.SafeValueChanged
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
open class MultiActionView<V : TextView>(
|
||||
stateActionList: MutableList<ViewAction>,
|
||||
val child: V
|
||||
) {
|
||||
|
||||
interface State {
|
||||
val id: Id
|
||||
|
||||
fun getAction(): SimpleFunction
|
||||
fun getResNameId(): Int
|
||||
}
|
||||
|
||||
var afterAction: SafeValueChanged<Id>? = null
|
||||
|
||||
var state: Id = DefaultId.default
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
|
||||
field = value
|
||||
Log.d(this, "state changed to: ${getKey(value)}")
|
||||
btnState = stateHolder[getKey(value)]
|
||||
}
|
||||
|
||||
init {
|
||||
child.setOnClickListener {
|
||||
val state = btnState ?: return@setOnClickListener
|
||||
|
||||
Log.d(this, "child handled OnClick for: ${getKey(state.id)}")
|
||||
state.getAction().invoke()
|
||||
afterAction?.invoke(state.id)
|
||||
}
|
||||
}
|
||||
|
||||
protected var btnState: State? = null
|
||||
set(value) {
|
||||
if (value == null) return
|
||||
|
||||
field = value
|
||||
Log.d(this, "btnState changed to: ${getKey(value.id)}")
|
||||
child.setText(value.getResNameId())
|
||||
}
|
||||
|
||||
protected val stateHolder: MutableMap<String, State> = stateActionList.associateBy { getKey(it.id) }.toMutableMap()
|
||||
|
||||
fun performAction(id: Id) {
|
||||
Log.d(this, "performAction ${getKey(id)}")
|
||||
state = id
|
||||
child.performClick()
|
||||
}
|
||||
|
||||
protected open fun getKey(id: Id): String {
|
||||
return when (id) {
|
||||
is StringId -> id.value
|
||||
else -> stringOf(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class DefaultId : Id { default }
|
||||
|
||||
typealias SimpleFunction = () -> Unit
|
||||
|
||||
class ViewAction(
|
||||
override val id: Id,
|
||||
@StringRes val name: Int,
|
||||
private val action: SimpleFunction
|
||||
) : MultiActionView.State {
|
||||
|
||||
override fun getAction(): SimpleFunction = action
|
||||
|
||||
override fun getResNameId(): Int = name
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.devkit.extensions
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun Fragment.shareText(text: String) {
|
||||
requireActivity().shareText(text)
|
||||
}
|
||||
|
||||
fun ComponentActivity.shareText(text: String) {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, text)
|
||||
type = "text/plain"
|
||||
}
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package com.tangem.devkit.extensions
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.personalization.entities.CardConfig
|
||||
import com.tangem.commands.personalization.entities.NdefRecord
|
||||
import java.util.*
|
||||
|
||||
fun CardConfig.Companion.create(application: Application): CardConfig {
|
||||
|
||||
val preferences = application.getSharedPreferences("prefs", Context.MODE_PRIVATE)
|
||||
|
||||
val signingMethodMaskBuilder = SigningMethodMaskBuilder()
|
||||
if (preferences.getBoolean("personalization_SigningMethod_0", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHash)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_1", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRaw)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_2", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuer)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_3", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuer)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_4", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuerWriteIssuerData)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_5", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuerWriteIssuerData)
|
||||
}
|
||||
if (preferences.getBoolean("personalization_SigningMethod_6", false)) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHash)
|
||||
}
|
||||
val signingMethod = signingMethodMaskBuilder.build()
|
||||
|
||||
val isNote = preferences.getBoolean("personalization_ProductMask_IsNote", true)
|
||||
val isTag = preferences.getBoolean("personalization_ProductMask_IsTag", false)
|
||||
val isIdCard = preferences.getBoolean("personalization_ProductMask_IsIDCard", false)
|
||||
|
||||
val productMaskBuilder = ProductMaskBuilder()
|
||||
if (isNote) productMaskBuilder.add(Product.Note)
|
||||
if (isTag) productMaskBuilder.add(Product.Tag)
|
||||
if (isIdCard) productMaskBuilder.add(Product.IdCard)
|
||||
val productMask = productMaskBuilder.build()
|
||||
|
||||
var tokenSymbol: String? = null
|
||||
var tokenContractAddress: String? = null
|
||||
var tokenDecimal: Int? = null
|
||||
if (preferences.getBoolean("personalization_isToken", false)) {
|
||||
tokenSymbol = preferences.getString("personalization_token_symbol", "")
|
||||
tokenContractAddress = preferences.getString("personalization_token_contract_address", "")
|
||||
tokenDecimal = preferences.getString("personalization_token_decimal", "")!!.toInt()
|
||||
}
|
||||
|
||||
val cardData = CardData(
|
||||
blockchainName = preferences.getString("personalization_Blockchain", "BTC"),
|
||||
batchId = preferences.getString("personalization_card_batch", "FFFF"),
|
||||
productMask = productMask,
|
||||
tokenSymbol = tokenSymbol,
|
||||
tokenContractAddress = tokenContractAddress,
|
||||
tokenDecimal = tokenDecimal,
|
||||
issuerName = null,
|
||||
manufactureDateTime = Calendar.getInstance().time,
|
||||
manufacturerSignature = null)
|
||||
|
||||
|
||||
val ndefAar = preferences.getString("personalization_NDEF_AAR", "Release APP")
|
||||
val ndefUri = preferences.getString("personalization_NDEF_URI", "https://tangem.com")
|
||||
|
||||
val ndefs = mutableListOf<NdefRecord>()
|
||||
if (!ndefUri.isNullOrEmpty()) {
|
||||
ndefs.add(NdefRecord(NdefRecord.Type.URI, ndefUri))
|
||||
}
|
||||
if (ndefAar != "None") {
|
||||
val type = NdefRecord.Type.AAR
|
||||
val value = when (ndefAar) {
|
||||
"Debug APP" -> {
|
||||
"com.tangem.wallet.debug"
|
||||
}
|
||||
"Release APP" -> {
|
||||
"com.tangem.wallet"
|
||||
}
|
||||
"--- CUSTOM ---" -> {
|
||||
preferences.getString("personalization_NDEF_CUSTOM_AAR", "com.tangem.wallet")!!
|
||||
}
|
||||
else -> ""
|
||||
}
|
||||
ndefs.add(NdefRecord(type, value))
|
||||
}
|
||||
|
||||
return CardConfig(
|
||||
cardData = cardData,
|
||||
curveID = EllipticCurve.byName(preferences.getString("personalization_CurveId", "secp256k1")!!)
|
||||
?: EllipticCurve.Secp256k1,
|
||||
signingMethods = signingMethod,
|
||||
createWallet = preferences.getBoolean("personalization_CreateWallet", true),
|
||||
maxSignatures = preferences.getString("personalization_MaxSignatures", "1000")!!.toInt(),
|
||||
isReusable = preferences.getBoolean("personalization_SettingsMask_IsReusable", true),
|
||||
protocolAllowUnencrypted = preferences.getBoolean("personalization_SettingsMask_AllowEncryption_None", true),
|
||||
protocolAllowStaticEncryption = preferences.getBoolean("personalization_SettingsMask_AllowEncryption_Fast", true),
|
||||
useActivation = preferences.getBoolean("personalization_SettingsMask_NeedActivation", false),
|
||||
|
||||
useOneCommandAtTime = preferences.getBoolean("personalization_SettingsMask_OneApduAtOnce", false),
|
||||
useCvc = preferences.getBoolean("personalization_SettingsMask_UseCVC", false),
|
||||
useBlock = preferences.getBoolean("personalization_SettingsMask_UseBlock", false),
|
||||
allowSwapPin = preferences.getBoolean("personalization_SettingsMask_AllowSwapPIN", true),
|
||||
allowSwapPin2 = preferences.getBoolean("personalization_SettingsMask_AllowSwapPIN2", true),
|
||||
useNdef = preferences.getBoolean("personalization_SettingsMask_UseNDEF", true),
|
||||
useDynamicNdef = preferences.getBoolean("personalization_SettingsMask_UseDynamicNDEF", true),
|
||||
protectIssuerDataAgainstReplay = preferences.getBoolean("personalization_SettingsMask_ProtectIssuerDataAgainstReplay", true),
|
||||
forbidDefaultPin = preferences.getBoolean("personalization_SettingsMask_ForbidDefaultPIN", false),
|
||||
smartSecurityDelay = preferences.getBoolean("personalization_SettingsMask_SmartSecurityDelay", false),
|
||||
pauseBeforePin2 = preferences.getString("personalization_PauseBeforePIN2", "15")!!.toInt() * 1000,
|
||||
allowSelectBlockchain = preferences.getBoolean("personalization_SettingsMask_AllowSelectBlockchain", false),
|
||||
forbidPurgeWallet = preferences.getBoolean("personalization_SettingsMask_ForbidPurgeWallet", false)
|
||||
?: false,
|
||||
disablePrecomputedNdef = preferences.getBoolean("personalization_SettingsMask_DisablePrecomputedNDEF", false)
|
||||
?: false,
|
||||
skipSecurityDelayIfValidatedByIssuer = preferences.getBoolean("personalization_SettingsMask_SkipSecurityDelayIfValidatedByIssuer", true),
|
||||
skipCheckPIN2andCVCIfValidatedByIssuer = preferences.getBoolean("personalization_SettingsMask_SkipCheckPIN2andCVCIfValidatedByIssuer", true),
|
||||
|
||||
skipSecurityDelayIfValidatedByLinkedTerminal = preferences.getBoolean("personalization_SettingsMask_SkipSecurityDelayIfValidatedByLinkedTerminal", true),
|
||||
restrictOverwriteIssuerDataEx = preferences.getBoolean("personalization_SettingsMask_RestrictOverwriteIssuerDataEx", true),
|
||||
|
||||
requireTerminalTxSignature = preferences.getBoolean("personalization_SettingsMask_RequireTerminalTxSignature", false),
|
||||
requireTerminalCertSignature = preferences.getBoolean("personalization_SettingsMask_RequireTerminalCertSignature", false),
|
||||
checkPin3onCard = preferences.getBoolean("personalization_SettingsMask_CheckPIN3onCard", true),
|
||||
|
||||
cvc = preferences.getString("personalization_cvc", "000") ?: "000",
|
||||
pin = preferences.getString("personalization_pin", "000000") ?: "000000",
|
||||
pin2 = preferences.getString("personalization_pin2", "000") ?: "000",
|
||||
pin3 = preferences.getString("personalization_pin3", "123") ?: "123",
|
||||
hexCrExKey = preferences.getString("personalization_CrEx_Key", "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"),
|
||||
|
||||
ndefRecords = ndefs
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.devkit.extensions
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun <T> List<T>.print(delimiter: String = ", ", wrap: Boolean = true): String {
|
||||
val builder = StringBuilder()
|
||||
forEach { builder.append(it).append(delimiter) }
|
||||
val length = builder.length
|
||||
if (length > delimiter.length) {
|
||||
builder.delete(length - delimiter.length, length)
|
||||
}
|
||||
val result = builder.toString()
|
||||
|
||||
return if (wrap) "[$result]" else result
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.devkit.extensions.view
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.Transition
|
||||
import androidx.transition.TransitionManager
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||
TransitionManager.beginDelayedTransition(this, transition)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.devkit.ucase
|
||||
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.navOptions
|
||||
import com.tangem.devkit.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun getDefaultNavigationOptions(): NavOptions {
|
||||
return navOptions {
|
||||
anim {
|
||||
enter = R.anim.slide_in_right
|
||||
exit = R.anim.slide_out_left
|
||||
popEnter = R.anim.slide_in_left
|
||||
popExit = R.anim.slide_out_right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.devkit.ucase.domain.actions
|
||||
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.Payload
|
||||
import com.tangem.devkit._arch.structure.PayloadHolder
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.triggers.afterAction.AfterActionModification
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*
|
||||
* The Card Action class family is designed for calling Card Manager functions
|
||||
* and then processing the response. It also allows you to extract the main action as a lambda expression
|
||||
*/
|
||||
data class AttrForAction(
|
||||
val tangemSdk: TangemSdk,
|
||||
val itemList: List<Item>,
|
||||
val payload: Payload,
|
||||
val consequence: ItemsChangeConsequence?
|
||||
)
|
||||
|
||||
interface Action {
|
||||
fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback)
|
||||
fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? = null
|
||||
}
|
||||
|
||||
abstract class BaseAction : Action {
|
||||
protected open fun handleResult(
|
||||
payload: PayloadHolder,
|
||||
commandResult: CompletionResult<*>,
|
||||
modifier: AfterActionModification?,
|
||||
attrs: AttrForAction,
|
||||
callback: ActionCallback
|
||||
) {
|
||||
val modifiedItems = mutableListOf<Item>()
|
||||
modifier?.modify(payload, commandResult, attrs.itemList)?.forEach { item ->
|
||||
modifiedItems.add(item)
|
||||
attrs.consequence?.affectChanges(payload, item, attrs.itemList)?.let { modifiedItems.addAll(it) }
|
||||
}
|
||||
callback(commandResult, modifiedItems)
|
||||
}
|
||||
|
||||
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? = null
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.devkit.ucase.domain.actions
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.PayloadHolder
|
||||
import com.tangem.devkit._arch.structure.abstraction.findItem
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback
|
||||
import com.tangem.devkit.ucase.variants.TlvId
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class DepersonalizeAction : BaseAction() {
|
||||
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
|
||||
val item = attrs.itemList.findItem(TlvId.CardId) ?: return
|
||||
val cardId = item.viewModel.data as? String ?: return
|
||||
|
||||
attrs.tangemSdk.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) }
|
||||
}
|
||||
|
||||
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {
|
||||
return when (id) {
|
||||
TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) }
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.devkit.ucase.domain.actions
|
||||
|
||||
import com.tangem.devkit._arch.structure.PayloadHolder
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.abstraction.findItem
|
||||
import com.tangem.devkit._arch.structure.impl.EditTextItem
|
||||
import com.tangem.devkit._arch.structure.impl.NumberItem
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.PayloadKey
|
||||
import com.tangem.devkit.ucase.tunnel.ActionView
|
||||
import com.tangem.devkit.ucase.tunnel.ItemError
|
||||
import com.tangem.devkit.ucase.variants.personalize.CardNumber
|
||||
import com.tangem.devkit.ucase.variants.personalize.converter.PersonalizationConfigConverter
|
||||
import com.tangem.devkit.ucase.variants.personalize.converter.PersonalizationConfigToCardConfig
|
||||
import com.tangem.devkit.ucase.variants.personalize.dto.DefaultPersonalizationParams
|
||||
import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PersonalizeAction : BaseAction() {
|
||||
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
|
||||
val itemList = attrs.payload[PayloadKey.itemList] as? List<Item> ?: return
|
||||
val actionView = attrs.payload[PayloadKey.actionView] as? ActionView ?: return
|
||||
|
||||
if (!checkSeries(itemList)) {
|
||||
actionView.showSnackbar(ItemError.BadSeries)
|
||||
return
|
||||
}
|
||||
if (!checkNumber(itemList)) {
|
||||
actionView.showSnackbar(ItemError.BadCardNumber)
|
||||
return
|
||||
}
|
||||
|
||||
val issuer = DefaultPersonalizationParams.issuer()
|
||||
val acquirer = DefaultPersonalizationParams.acquirer()
|
||||
val manufacturer = DefaultPersonalizationParams.manufacturer()
|
||||
|
||||
val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig.default())
|
||||
val cardConfig = PersonalizationConfigToCardConfig().convert(personalizeConfig)
|
||||
|
||||
attrs.tangemSdk.personalize(cardConfig, issuer, manufacturer, acquirer) {
|
||||
handleResult(payload, it, null, attrs, callback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSeries(itemList: List<Item>): Boolean {
|
||||
val seriesItem = itemList.findItem(CardNumber.Series) as? EditTextItem ?: return false
|
||||
val data = seriesItem.getData() as? String ?: return false
|
||||
|
||||
seriesItem.setData(data.toUpperCase())
|
||||
return data.length == 2 || data.length == 4
|
||||
}
|
||||
|
||||
private fun checkNumber(itemList: List<Item>): Boolean {
|
||||
val seriesItem = itemList.findItem(CardNumber.Series) as? EditTextItem ?: return false
|
||||
val numberItem = itemList.findItem(CardNumber.Number) as? NumberItem ?: return false
|
||||
val seriesData = seriesItem.getData() as? String ?: return false
|
||||
val numberData = stringOf(numberItem.getData() as? Number)
|
||||
|
||||
return if (seriesData.length == 2 && numberData.length > 13) false
|
||||
else !(seriesData.length == 4 && numberData.length > 11)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.devkit.ucase.domain.actions
|
||||
|
||||
import com.tangem.devkit._arch.structure.PayloadHolder
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.triggers.afterAction.AfterScanModifier
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ScanAction : BaseAction() {
|
||||
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
|
||||
attrs.tangemSdk.scanCard { handleResult(payload, it, AfterScanModifier(), attrs, callback) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.devkit.ucase.domain.actions
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.PayloadHolder
|
||||
import com.tangem.devkit._arch.structure.abstraction.findItem
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback
|
||||
import com.tangem.devkit.ucase.variants.TlvId
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SignAction : BaseAction() {
|
||||
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
|
||||
val dataForHashing = attrs.itemList.findItem(TlvId.TransactionOutHash) ?: return
|
||||
val hash = dataForHashing.getData() as? String ?: return
|
||||
val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return
|
||||
|
||||
attrs.tangemSdk.sign(arrayOf(hash.toByteArray()), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
|
||||
}
|
||||
|
||||
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {
|
||||
return when (id) {
|
||||
TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) }
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.devkit.ucase.domain.paramsManager
|
||||
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.Payload
|
||||
import com.tangem.devkit._arch.structure.PayloadHolder
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence
|
||||
|
||||
typealias ActionResponse = CompletionResult<*>
|
||||
typealias AffectedList = List<Item>
|
||||
typealias AffectedItemsCallback = (AffectedList) -> Unit
|
||||
typealias ActionCallback = (ActionResponse, AffectedList) -> Unit
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ItemsManager : PayloadHolder {
|
||||
|
||||
fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback? = null)
|
||||
fun setItems(items: List<Item>)
|
||||
fun getItems(): List<Item>
|
||||
fun setItemChangeConsequences(consequence: ItemsChangeConsequence?)
|
||||
fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback)
|
||||
fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)?
|
||||
fun attachPayload(payload: Payload)
|
||||
fun updateByItemList(list: List<Item>)
|
||||
}
|
||||
|
||||
interface PayloadKey {
|
||||
companion object {
|
||||
val cardConfig: String = "cardConfig"
|
||||
val incomingJson: String = "incomingJson"
|
||||
val card = "card"
|
||||
val actionView = "actionView"
|
||||
val itemList = "itemList"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.devkit.ucase.domain.paramsManager.managers
|
||||
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.Payload
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.abstraction.findItem
|
||||
import com.tangem.devkit._arch.structure.abstraction.iterate
|
||||
import com.tangem.devkit.ucase.domain.actions.Action
|
||||
import com.tangem.devkit.ucase.domain.actions.AttrForAction
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.AffectedItemsCallback
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
open class BaseItemsManager(protected val action: Action) : ItemsManager, LifecycleObserver {
|
||||
|
||||
init {
|
||||
Log.d(this, "new instance created")
|
||||
}
|
||||
|
||||
override val payload: MutableMap<String, Any?> = mutableMapOf()
|
||||
|
||||
protected var changeConsequence: ItemsChangeConsequence? = null
|
||||
protected val itemList: MutableList<Item> = mutableListOf()
|
||||
|
||||
override fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback?) {
|
||||
if (itemList.isEmpty()) return
|
||||
val foundItem = itemList.findItem(id) ?: return
|
||||
|
||||
foundItem.setData(value)
|
||||
applyChangesByAffectedItems(foundItem, callback)
|
||||
}
|
||||
|
||||
override fun setItems(items: List<Item>) {
|
||||
itemList.clear()
|
||||
itemList.addAll(items)
|
||||
}
|
||||
|
||||
override fun getItems(): List<Item> = itemList
|
||||
|
||||
override fun setItemChangeConsequences(consequence: ItemsChangeConsequence?) {
|
||||
this.changeConsequence = consequence
|
||||
}
|
||||
|
||||
override fun updateByItemList(list: List<Item>) {
|
||||
list.iterate { itemList.findItem(it.id)?.update(it) }
|
||||
}
|
||||
|
||||
override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) {
|
||||
action.executeMainAction(this, getAttrsForAction(tangemSdk), callback)
|
||||
}
|
||||
|
||||
override fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)? {
|
||||
return action.getActionByTag(this, id, getAttrsForAction(tangemSdk))
|
||||
}
|
||||
|
||||
override fun attachPayload(payload: Payload) {
|
||||
payload.forEach { this.payload[it.key] = it.value }
|
||||
}
|
||||
|
||||
// Use it if current item needs to be affect any other items
|
||||
protected open fun applyChangesByAffectedItems(param: Item, callback: AffectedItemsCallback?) {
|
||||
changeConsequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) }
|
||||
}
|
||||
|
||||
protected fun getAttrsForAction(tangemSdk: TangemSdk)
|
||||
: AttrForAction = AttrForAction(tangemSdk, itemList, payload, changeConsequence)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.devkit.ucase.domain.paramsManager.managers
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.devkit.commons.Store
|
||||
import com.tangem.devkit.ucase.domain.actions.PersonalizeAction
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback
|
||||
import com.tangem.devkit.ucase.variants.personalize.converter.PersonalizationConfigConverter
|
||||
import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PersonalizationItemsManager(
|
||||
private val store: Store<PersonalizationConfig>
|
||||
) : BaseItemsManager(PersonalizeAction()) {
|
||||
|
||||
private val converter = PersonalizationConfigConverter()
|
||||
|
||||
init {
|
||||
val config = store.restore()
|
||||
setItems(converter.convert(config))
|
||||
}
|
||||
|
||||
override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) {
|
||||
action.executeMainAction(this, getAttrsForAction(tangemSdk), callback)
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
fun onDestroy() {
|
||||
val config = converter.convert(itemList, PersonalizationConfig.default())
|
||||
store.save(config)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.devkit.ucase.domain.paramsManager.managers
|
||||
|
||||
import com.tangem.devkit._arch.structure.impl.EditTextItem
|
||||
import com.tangem.devkit.ucase.domain.actions.DepersonalizeAction
|
||||
import com.tangem.devkit.ucase.domain.actions.ScanAction
|
||||
import com.tangem.devkit.ucase.domain.actions.SignAction
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.triggers.changeConsequence.SignScanConsequence
|
||||
import com.tangem.devkit.ucase.variants.TlvId
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ScanItemsManager : BaseItemsManager(ScanAction())
|
||||
|
||||
class DepersonalizeItemsManager : BaseItemsManager(DepersonalizeAction()) {
|
||||
|
||||
init {
|
||||
setItems(listOf(EditTextItem(TlvId.CardId, null)))
|
||||
}
|
||||
}
|
||||
|
||||
class SignItemsManager : BaseItemsManager(SignAction()) {
|
||||
|
||||
init {
|
||||
setItemChangeConsequences(SignScanConsequence())
|
||||
setItems(listOf(
|
||||
EditTextItem(TlvId.CardId, null),
|
||||
EditTextItem(TlvId.TransactionOutHash, "Data used for hashing")
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.devkit.ucase.domain.paramsManager.triggers.afterAction
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.devkit._arch.structure.PayloadHolder
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*
|
||||
* The After Action Modification class family is intended for modifying items (if necessary)
|
||||
* after calling CardManager.anyAction.
|
||||
* Returns a list of items that have been modified
|
||||
*/
|
||||
interface AfterActionModification {
|
||||
fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List<Item>): List<Item>
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.devkit.ucase.domain.paramsManager.triggers.afterAction
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CardStatus
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.devkit._arch.structure.PayloadHolder
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.abstraction.findItem
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.PayloadKey
|
||||
import com.tangem.devkit.ucase.tunnel.ActionView
|
||||
import com.tangem.devkit.ucase.tunnel.CardError
|
||||
import com.tangem.devkit.ucase.variants.TlvId
|
||||
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AfterScanModifier : AfterActionModification {
|
||||
override fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List<Item>): List<Item> {
|
||||
val foundItem = itemList.findItem(TlvId.CardId) ?: return listOf()
|
||||
val card = smartCast(commandResult) ?: return listOf()
|
||||
val actionView = payload.get(PayloadKey.actionView) as? ActionView ?: return listOf()
|
||||
|
||||
return if (isNotPersonalized(card)) {
|
||||
actionView.showSnackbar(CardError.NotPersonalized)
|
||||
postUI { actionView.enableActionFab(false) }
|
||||
listOf()
|
||||
} else {
|
||||
payload.set(PayloadKey.card, card)
|
||||
foundItem.setData(card.cardId)
|
||||
postUI { actionView.enableActionFab(true) }
|
||||
listOf(foundItem)
|
||||
}
|
||||
}
|
||||
|
||||
private fun smartCast(commandResult: CompletionResult<*>): Card? {
|
||||
return (commandResult as? CompletionResult.Success<Card>)?.data
|
||||
}
|
||||
|
||||
private fun isNotPersonalized(card: Card): Boolean = card.status == CardStatus.NotPersonalized
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.devkit.ucase.domain.paramsManager.triggers.changeConsequence
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.calculateSha512
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.devkit._arch.structure.PayloadHolder
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.abstraction.findItem
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.PayloadKey
|
||||
import com.tangem.devkit.ucase.tunnel.ActionView
|
||||
import com.tangem.devkit.ucase.variants.TlvId
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*
|
||||
* The ParamsChangeConsequence class family modifies parameters depending on the state
|
||||
* of the incoming parameter
|
||||
*/
|
||||
interface ItemsChangeConsequence {
|
||||
fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List<Item>): List<Item>?
|
||||
}
|
||||
|
||||
class SignScanConsequence : ItemsChangeConsequence {
|
||||
|
||||
override fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List<Item>): List<Item>? {
|
||||
if (changedItem.id != TlvId.CardId) return null
|
||||
|
||||
val hashItem = itemList.findItem(TlvId.TransactionOutHash) ?: return null
|
||||
val affectedItems = mutableListOf(hashItem)
|
||||
val card = payload.remove(PayloadKey.card) as Card?
|
||||
if (card == null) {
|
||||
hashItem.restoreDefaultData()
|
||||
postUI { (payload.get(PayloadKey.actionView) as? ActionView)?.enableActionFab(false) }
|
||||
} else {
|
||||
val dataForHashing = hashItem.getData() as? String ?: "Any data mother...s"
|
||||
val hashedData = when (card.curve) {
|
||||
EllipticCurve.Secp256k1 -> dataForHashing.calculateSha256()
|
||||
EllipticCurve.Ed25519 -> dataForHashing.calculateSha512()
|
||||
else -> return null
|
||||
}
|
||||
hashItem.setData(hashedData.toHexString())
|
||||
}
|
||||
return affectedItems.toList()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.tangem.devkit.ucase.domain.responses
|
||||
|
||||
import com.google.gson.*
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.devkit.extensions.print
|
||||
import java.lang.reflect.Type
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ResponseJsonConverter {
|
||||
|
||||
val gson: Gson by lazy { init() }
|
||||
|
||||
private val fieldConverter = ResponseFieldConverter()
|
||||
|
||||
private fun init(): Gson {
|
||||
val builder = GsonBuilder().apply {
|
||||
registerTypeAdapter(ByteArray::class.java, ByteTypeAdapter(fieldConverter))
|
||||
registerTypeAdapter(SigningMethodMask::class.java, SigningMethodTypeAdapter(fieldConverter))
|
||||
registerTypeAdapter(SettingsMask::class.java, SettingsMaskTypeAdapter(fieldConverter))
|
||||
registerTypeAdapter(ProductMask::class.java, ProductMaskTypeAdapter(fieldConverter))
|
||||
registerTypeAdapter(Date::class.java, DateTypeAdapter())
|
||||
}
|
||||
builder.setPrettyPrinting()
|
||||
return builder.create()
|
||||
}
|
||||
|
||||
fun convertResponse(response: CommandResponse?): String = gson.toJson(response)
|
||||
}
|
||||
|
||||
class ByteTypeAdapter(
|
||||
private val fieldConverter: ResponseFieldConverter
|
||||
) : JsonSerializer<ByteArray> {
|
||||
override fun serialize(src: ByteArray, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
return JsonPrimitive(fieldConverter.byteArray(src))
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsMaskTypeAdapter(
|
||||
private val fieldConverter: ResponseFieldConverter
|
||||
) : JsonSerializer<SettingsMask> {
|
||||
override fun serialize(src: SettingsMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
return JsonArray().apply {
|
||||
fieldConverter.settingsMaskList(src).forEach { add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ProductMaskTypeAdapter(
|
||||
private val fieldConverter: ResponseFieldConverter
|
||||
) : JsonSerializer<ProductMask> {
|
||||
override fun serialize(src: ProductMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
return JsonArray().apply {
|
||||
fieldConverter.productMaskList(src).forEach { add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SigningMethodTypeAdapter(
|
||||
private val fieldConverter: ResponseFieldConverter
|
||||
) : JsonSerializer<SigningMethodMask> {
|
||||
override fun serialize(src: SigningMethodMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
return JsonArray().apply {
|
||||
fieldConverter.signingMethodList(src).forEach { add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DateTypeAdapter : JsonSerializer<Date> {
|
||||
override fun serialize(src: Date, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
val formatter = DateFormat.getDateInstance(DateFormat.DEFAULT, Locale("en_US"))
|
||||
return JsonPrimitive(formatter.format(src).toString())
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseFieldConverter {
|
||||
|
||||
fun productMask(productMask: ProductMask?): String {
|
||||
return productMaskList(productMask).print(wrap = false)
|
||||
}
|
||||
|
||||
fun productMaskList(productMask: ProductMask?): List<String> {
|
||||
val mask = productMask ?: return emptyList()
|
||||
|
||||
return Product.values().filter { mask.contains(it) }.map { it.name }
|
||||
}
|
||||
|
||||
fun signingMethod(signingMask: SigningMethodMask?): String {
|
||||
return signingMethodList(signingMask).print(wrap = false)
|
||||
}
|
||||
|
||||
fun signingMethodList(signingMask: SigningMethodMask?): List<String> {
|
||||
val mask = signingMask ?: return emptyList()
|
||||
|
||||
return SigningMethod.values().filter { mask.contains(it) }.map { it.name }
|
||||
}
|
||||
|
||||
fun settingsMask(settingsMask: SettingsMask?): String {
|
||||
return settingsMaskList(settingsMask).print(wrap = false)
|
||||
}
|
||||
|
||||
fun settingsMaskList(settingsMask: SettingsMask?): List<String> {
|
||||
val masks = settingsMask ?: return emptyList()
|
||||
|
||||
return Settings.values().filter { masks.contains(it) }.map { it.name }
|
||||
}
|
||||
|
||||
fun byteArray(byteArray: ByteArray?): String? {
|
||||
return byteArray?.toHexString()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.devkit.ucase.resources
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
enum class ActionType : Id {
|
||||
Scan,
|
||||
Sign,
|
||||
CreateWallet,
|
||||
PurgeWallet,
|
||||
ReadIssuerData,
|
||||
WriteIssuerData,
|
||||
ReadIssuerExData,
|
||||
WriteIssuerExData,
|
||||
ReadUserData,
|
||||
WriteUserData,
|
||||
Personalize,
|
||||
Depersonalize,
|
||||
Unknown,
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.devkit.ucase.resources
|
||||
|
||||
import com.tangem.devkit.AppTangemDemo
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.StringId
|
||||
import com.tangem.devkit._arch.structure.StringResId
|
||||
import com.tangem.devkit.ucase.resources.initializers.ActionResources
|
||||
import com.tangem.devkit.ucase.resources.initializers.PersonalizationResources
|
||||
import com.tangem.devkit.ucase.resources.initializers.ResponseResources
|
||||
import com.tangem.devkit.ucase.resources.initializers.TlvResources
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
open class ResourceHolder<T> : BaseTypedHolder<T, Resources>()
|
||||
|
||||
object MainResourceHolder : ResourceHolder<Id>() {
|
||||
init {
|
||||
PersonalizationResources().init(this)
|
||||
ActionResources().init(this)
|
||||
TlvResources().init(this)
|
||||
ResponseResources().init(this)
|
||||
}
|
||||
|
||||
override fun get(type: Id): Resources? {
|
||||
return when(type) {
|
||||
is StringId -> getResourcesFromName(type.value)
|
||||
is StringResId -> Resources(type.value)
|
||||
else -> super.get(type)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getResourcesFromName(resName: String): Resources? {
|
||||
val context = AppTangemDemo.appInstance.applicationContext
|
||||
val resNameId = context.resources.getIdentifier(resName, "string", context.packageName)
|
||||
val resInfoId = context.resources.getIdentifier("info_$resName", "string", context.packageName)
|
||||
if (resNameId == 0) return null
|
||||
|
||||
return Resources(resNameId, if (resInfoId == 0) null else resInfoId)
|
||||
}
|
||||
|
||||
inline fun <reified Res : Resources> safeGet(id: Id): Res {
|
||||
val result = super.get(id) ?: Resources(R.string.unknown, R.string.unknown)
|
||||
return result as Res
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.devkit.ucase.resources
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
open class Resources(
|
||||
val resName: Int,
|
||||
val resDescription: Int? = null
|
||||
)
|
||||
|
||||
class ActionRes(
|
||||
resName: Int,
|
||||
resDescription: Int? = null,
|
||||
val resNavigation: Int? = null
|
||||
) : Resources(resName, resDescription)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.devkit.ucase.resources.initializers
|
||||
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit.ucase.resources.ActionRes
|
||||
import com.tangem.devkit.ucase.resources.ActionType
|
||||
import com.tangem.devkit.ucase.resources.ResourceHolder
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ActionResources {
|
||||
|
||||
fun init(holder: ResourceHolder<Id>) {
|
||||
holder.register(ActionType.Scan, ActionRes(R.string.action_card_scan, R.string.info_action_card_scan, R.id.action_nav_entry_point_to_nav_scan))
|
||||
holder.register(ActionType.Sign, ActionRes(R.string.action_card_sign, R.string.info_action_card_sign, R.id.action_nav_entry_point_to_nav_sign))
|
||||
holder.register(ActionType.Personalize, ActionRes(R.string.action_personalize, R.string.info_action_personalize, R.id.action_nav_entry_point_to_nav_personalize))
|
||||
holder.register(ActionType.Depersonalize, ActionRes(R.string.action_depersonalize, R.string.info_action_depersonalize, R.id.action_nav_entry_point_to_nav_depersonalize))
|
||||
holder.register(ActionType.CreateWallet, ActionRes(R.string.action_wallet_create, R.string.info_action_wallet_create, R.id.action_nav_entry_point_to_nav_wallet_create))
|
||||
holder.register(ActionType.PurgeWallet, ActionRes(R.string.action_wallet_purge, R.string.info_action_wallet_purge, R.id.action_nav_entry_point_to_nav_wallet_purge))
|
||||
holder.register(ActionType.ReadIssuerData, ActionRes(R.string.action_issuer_read_data, R.string.info_action_issuer_read_data, R.id.action_nav_entry_point_to_nav_issuer_read_data))
|
||||
holder.register(ActionType.WriteIssuerData, ActionRes(R.string.action_issuer_write_data, R.string.info_action_issuer_write_data, R.id.action_nav_entry_point_to_nav_issuer_write_data))
|
||||
holder.register(ActionType.ReadIssuerExData, ActionRes(R.string.action_issuer_read_ex_data, R.string.info_action_issuer_read_ex_data, R.id.action_nav_entry_point_to_nav_issuer_read_ex_data))
|
||||
holder.register(ActionType.WriteIssuerExData, ActionRes(R.string.action_issuer_write_ex_data, R.string.info_action_issuer_write_ex_data, R.id.action_nav_entry_point_to_nav_issuer_write_ex_data))
|
||||
holder.register(ActionType.ReadUserData, ActionRes(R.string.action_user_read_data, R.string.info_action_user_read_data, R.id.action_nav_entry_point_to_nav_user_read_data))
|
||||
holder.register(ActionType.WriteUserData, ActionRes(R.string.action_user_write_data, R.string.info_action_user_write_data, R.id.action_nav_entry_point_to_nav_user_write_data))
|
||||
// holder.register(ActionType.Unknown, getIfNotContains())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
package com.tangem.devkit.ucase.resources.initializers
|
||||
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit.ucase.resources.ResourceHolder
|
||||
import com.tangem.devkit.ucase.resources.Resources
|
||||
import com.tangem.devkit.ucase.variants.personalize.*
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.common.TypedHolder
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PersonalizationResources() {
|
||||
fun init(holder: ResourceHolder<Id>) {
|
||||
initBlock(holder)
|
||||
initCardNumber(holder)
|
||||
initCommon(holder)
|
||||
initSigningMethod(holder)
|
||||
initSignHashExProp(holder)
|
||||
initDenomination(holder)
|
||||
initToken(holder)
|
||||
initProductMask(holder)
|
||||
initSettingsMask(holder)
|
||||
initSettingsMaskProtocolEnc(holder)
|
||||
initSettingsMaskNdef(holder)
|
||||
initPins(holder)
|
||||
}
|
||||
|
||||
private fun initBlock(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(BlockId.CardNumber, Resources(R.string.pers_block_card_number, R.string.info_pers_block_card_number))
|
||||
holder.register(BlockId.Common, Resources(R.string.pers_block_common, R.string.info_pers_block_common))
|
||||
holder.register(BlockId.SigningMethod, Resources(R.string.pers_block_signing_method, R.string.info_pers_block_signing_method))
|
||||
holder.register(BlockId.SignHashExProp, Resources(R.string.pers_block_sign_hash_ex_prop, R.string.info_pers_block_sign_hash_ex_prop))
|
||||
holder.register(BlockId.Denomination, Resources(R.string.pers_block_denomination, R.string.info_pers_block_denomination))
|
||||
holder.register(BlockId.Token, Resources(R.string.pers_block_token, R.string.info_pers_block_token))
|
||||
holder.register(BlockId.ProdMask, Resources(R.string.pers_block_product_mask, R.string.info_pers_block_product_mask))
|
||||
holder.register(BlockId.SettingsMask, Resources(R.string.pers_block_settings_mask, R.string.info_pers_block_settings_mask))
|
||||
holder.register(BlockId.SettingsMaskProtocolEnc, Resources(R.string.pers_block_settings_mask_protocol_enc, R.string.info_pers_block_settings_mask_protocol_enc))
|
||||
holder.register(BlockId.SettingsMaskNdef, Resources(R.string.pers_block_settings_mask_ndef, R.string.info_pers_block_settings_mask_ndef))
|
||||
holder.register(BlockId.Pins, Resources(R.string.pers_block_pins, R.string.info_pers_block_pins))
|
||||
}
|
||||
|
||||
private fun initCardNumber(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(CardNumber.Series, Resources(R.string.pers_item_series, R.string.info_pers_item_series))
|
||||
holder.register(CardNumber.Number, Resources(R.string.pers_item_number, R.string.info_pers_item_number))
|
||||
holder.register(CardNumber.BatchId, Resources(R.string.pers_item_batch_id, R.string.info_pers_item_batch_id))
|
||||
}
|
||||
|
||||
private fun initCommon(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(Common.Curve, Resources(R.string.pers_item_curve, R.string.info_pers_item_curve))
|
||||
holder.register(Common.Blockchain, Resources(R.string.pers_item_blockchain, R.string.info_pers_item_blockchain))
|
||||
holder.register(Common.BlockchainCustom, Resources(R.string.pers_item_custom_blockchain, R.string.info_pers_item_custom_blockchain))
|
||||
holder.register(Common.MaxSignatures, Resources(R.string.pers_item_max_signatures, R.string.info_pers_item_max_signatures))
|
||||
holder.register(Common.CreateWallet, Resources(R.string.pers_item_create_wallet, R.string.info_pers_item_create_wallet))
|
||||
}
|
||||
|
||||
private fun initSigningMethod(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(SigningMethod.SignTx, Resources(R.string.pers_item_sign_tx_hashes, R.string.info_pers_item_sign_tx_hashes))
|
||||
holder.register(SigningMethod.SignTxRaw, Resources(R.string.pers_item_sign_raw_tx, R.string.info_pers_item_sign_raw_tx))
|
||||
holder.register(SigningMethod.SignValidatedTx, Resources(R.string.pers_item_sign_validated_tx_hashes, R.string.info_pers_item_sign_validated_tx_hashes))
|
||||
holder.register(SigningMethod.SignValidatedTxRaw, Resources(R.string.pers_item_sign_validated_raw_tx, R.string.info_pers_item_sign_validated_raw_tx))
|
||||
holder.register(SigningMethod.SignValidatedTxIssuer, Resources(R.string.pers_item_sign_validated_tx_hashes_with_iss_data, R.string.info_pers_item_sign_validated_tx_hashes_with_iss_data))
|
||||
holder.register(SigningMethod.SignValidatedTxRawIssuer, Resources(R.string.pers_item_sign_validated_raw_tx_with_iss_data, R.string.info_pers_item_sign_validated_raw_tx_with_iss_data))
|
||||
holder.register(SigningMethod.SignExternal, Resources(R.string.pers_item_sign_hash_ex, R.string.info_pers_item_sign_hash_ex))
|
||||
}
|
||||
|
||||
private fun initSignHashExProp(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(SignHashExProp.PinLessFloorLimit, Resources(R.string.pers_item_pin_less_floor_limit, R.string.info_pers_item_pin_less_floor_limit))
|
||||
holder.register(SignHashExProp.CryptoExKey, Resources(R.string.pers_item_cr_ex_key, R.string.info_pers_item_cr_ex_key))
|
||||
holder.register(SignHashExProp.RequireTerminalCertSig, Resources(R.string.pers_item_require_terminal_cert_sig, R.string.info_pers_item_require_terminal_cert_sig))
|
||||
holder.register(SignHashExProp.RequireTerminalTxSig, Resources(R.string.pers_item_require_terminal_tx_sig, R.string.info_pers_item_require_terminal_tx_sig))
|
||||
holder.register(SignHashExProp.CheckPin3, Resources(R.string.pers_item_check_pin3_on_card, R.string.info_pers_item_check_pin3_on_card))
|
||||
}
|
||||
|
||||
private fun initDenomination(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(Denomination.WriteOnPersonalize, Resources(R.string.pers_item_write_on_personalize, R.string.info_pers_item_write_on_personalize))
|
||||
holder.register(Denomination.Denomination, Resources(R.string.pers_item_denomination, R.string.info_pers_item_denomination))
|
||||
}
|
||||
|
||||
private fun initToken(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(Token.ItsToken, Resources(R.string.pers_item_its_token, R.string.info_pers_item_its_token))
|
||||
holder.register(Token.Symbol, Resources(R.string.pers_item_symbol, R.string.info_pers_item_symbol))
|
||||
holder.register(Token.ContractAddress, Resources(R.string.pers_item_contract_address, R.string.info_pers_item_contract_address))
|
||||
holder.register(Token.Decimal, Resources(R.string.pers_item_decimal, R.string.info_pers_item_decimal))
|
||||
}
|
||||
|
||||
private fun initProductMask(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(ProductMask.Note, Resources(R.string.pers_item_note, R.string.info_pers_item_note))
|
||||
holder.register(ProductMask.Tag, Resources(R.string.pers_item_tag, R.string.info_pers_item_tag))
|
||||
holder.register(ProductMask.IdCard, Resources(R.string.pers_item_id_card, R.string.info_pers_item_id_card))
|
||||
holder.register(ProductMask.IdIssuerCard, Resources(R.string.pers_item_id_issuer_card, R.string.info_pers_item_id_issuer_card))
|
||||
}
|
||||
|
||||
private fun initSettingsMask(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(SettingsMask.IsReusable, Resources(R.string.pers_item_is_reusable, R.string.info_pers_item_is_reusable))
|
||||
holder.register(SettingsMask.NeedActivation, Resources(R.string.pers_item_need_activation, R.string.info_pers_item_need_activation))
|
||||
holder.register(SettingsMask.ForbidPurge, Resources(R.string.pers_item_forbid_purge, R.string.info_pers_item_forbid_purge))
|
||||
holder.register(SettingsMask.AllowSelectBlockchain, Resources(R.string.pers_item_allow_select_blockchain, R.string.info_pers_item_allow_select_blockchain))
|
||||
holder.register(SettingsMask.UseBlock, Resources(R.string.pers_item_use_block, R.string.info_pers_item_use_block))
|
||||
holder.register(SettingsMask.OneApdu, Resources(R.string.pers_item_one_apdu_at_once, R.string.info_pers_item_one_apdu_at_once))
|
||||
holder.register(SettingsMask.UseCvc, Resources(R.string.pers_item_use_cvc, R.string.info_pers_item_use_cvc))
|
||||
holder.register(SettingsMask.AllowSwapPin, Resources(R.string.pers_item_allow_swap_pin, R.string.info_pers_item_allow_swap_pin))
|
||||
holder.register(SettingsMask.AllowSwapPin2, Resources(R.string.pers_item_allow_swap_pin2, R.string.info_pers_item_allow_swap_pin2))
|
||||
holder.register(SettingsMask.ForbidDefaultPin, Resources(R.string.pers_item_forbid_default_pin, R.string.info_pers_item_forbid_default_pin))
|
||||
holder.register(SettingsMask.SmartSecurityDelay, Resources(R.string.pers_item_smart_security_delay, R.string.info_pers_item_smart_security_delay))
|
||||
holder.register(SettingsMask.ProtectIssuerDataAgainstReplay, Resources(R.string.pers_item_protect_issuer_data_against_replay, R.string.info_pers_item_protect_issuer_data_against_replay))
|
||||
holder.register(SettingsMask.SkipSecurityDelayIfValidated, Resources(R.string.pers_item_skip_security_delay_if_validated, R.string.info_pers_item_skip_security_delay_if_validated))
|
||||
holder.register(SettingsMask.SkipPin2CvcIfValidated, Resources(R.string.pers_item_skip_pin2_and_cvc_if_validated, R.string.info_pers_item_skip_pin2_and_cvc_if_validated))
|
||||
holder.register(SettingsMask.SkipSecurityDelayOnLinkedTerminal, Resources(R.string.pers_item_skip_security_delay_on_linked_terminal, R.string.info_pers_item_skip_security_delay_on_linked_terminal))
|
||||
holder.register(SettingsMask.RestrictOverwriteExtraIssuerData, Resources(R.string.pers_item_restrict_overwrite_ex_issuer_data, R.string.info_pers_item_restrict_overwrite_ex_issuer_data))
|
||||
}
|
||||
|
||||
private fun initSettingsMaskProtocolEnc(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(SettingsMaskProtocolEnc.AllowUnencrypted, Resources(R.string.pers_item_allow_unencrypted, R.string.info_pers_item_allow_unencrypted))
|
||||
holder.register(SettingsMaskProtocolEnc.AllowStaticEncryption, Resources(R.string.pers_item_allow_fast_encryption, R.string.info_pers_item_allow_fast_encryption))
|
||||
}
|
||||
|
||||
private fun initSettingsMaskNdef(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(SettingsMaskNdef.UseNdef, Resources(R.string.pers_item_use_ndef, R.string.info_pers_item_use_ndef))
|
||||
holder.register(SettingsMaskNdef.DynamicNdef, Resources(R.string.pers_item_dynamic_ndef, R.string.info_pers_item_dynamic_ndef))
|
||||
holder.register(SettingsMaskNdef.DisablePrecomputedNdef, Resources(R.string.pers_item_disable_precomputed_ndef, R.string.info_pers_item_disable_precomputed_ndef))
|
||||
holder.register(SettingsMaskNdef.Aar, Resources(R.string.pers_item_aar, R.string.info_pers_item_aar))
|
||||
holder.register(SettingsMaskNdef.AarCustom, Resources(R.string.pers_item_custom_aar_package_name, R.string.info_pers_item_custom_aar_package_name))
|
||||
holder.register(SettingsMaskNdef.Uri, Resources(R.string.pers_item_uri, R.string.info_pers_item_uri))
|
||||
}
|
||||
|
||||
private fun initPins(holder: TypedHolder<Id, Resources>) {
|
||||
holder.register(Pins.Pin, Resources(R.string.pers_item_pin, R.string.info_pers_item_pin))
|
||||
holder.register(Pins.Pin2, Resources(R.string.pers_item_pin2, R.string.info_pers_item_pin2))
|
||||
holder.register(Pins.Pin3, Resources(R.string.pers_item_pin3, R.string.info_pers_item_pin3))
|
||||
holder.register(Pins.Cvc, Resources(R.string.pers_item_cvc, R.string.info_pers_item_cvc))
|
||||
holder.register(Pins.PauseBeforePin2, Resources(R.string.pers_item_pause_before_pin2, R.string.info_pers_item_pause_before_pin2))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.devkit.ucase.resources.initializers
|
||||
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit.ucase.resources.MainResourceHolder
|
||||
import com.tangem.devkit.ucase.resources.Resources
|
||||
import com.tangem.devkit.ucase.variants.responses.CardDataId
|
||||
import com.tangem.devkit.ucase.variants.responses.CardId
|
||||
import com.tangem.devkit.ucase.variants.responses.DepersonalizeId
|
||||
import com.tangem.devkit.ucase.variants.responses.SignId
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ResponseResources {
|
||||
fun init(holder: MainResourceHolder) {
|
||||
initCard(holder)
|
||||
initCardData(holder)
|
||||
initSignResponse(holder)
|
||||
initDepersonalizeResponse(holder)
|
||||
}
|
||||
|
||||
private fun initCard(holder: MainResourceHolder) {
|
||||
holder.register(CardId.cardId, Resources(R.string.response_card_cid, R.string.info_response_card_cid))
|
||||
holder.register(CardId.manufacturerName, Resources(R.string.response_card_manufacturer_name, R.string.info_response_card_manufacturer_name))
|
||||
holder.register(CardId.status, Resources(R.string.response_card_status, R.string.info_response_card_status))
|
||||
holder.register(CardId.firmwareVersion, Resources(R.string.response_card_firmware_version, R.string.info_response_card_firmware_version))
|
||||
holder.register(CardId.cardPublicKey, Resources(R.string.response_card_public_key, R.string.info_response_card_public_key))
|
||||
holder.register(CardId.settingsMask, Resources(R.string.response_card_settings_mask, R.string.info_response_card_settings_mask))
|
||||
holder.register(CardId.cardData, Resources(R.string.response_card_card_data, R.string.info_response_card_card_data))
|
||||
holder.register(CardId.issuerPublicKey, Resources(R.string.response_card_issuer_data_public_key, R.string.info_response_card_issuer_data_public_key))
|
||||
holder.register(CardId.curve, Resources(R.string.response_card_curve, R.string.info_response_card_curve))
|
||||
holder.register(CardId.maxSignatures, Resources(R.string.response_card_max_signatures, R.string.info_response_card_max_signatures))
|
||||
holder.register(CardId.signingMethod, Resources(R.string.response_card_signing_method, R.string.info_response_card_signing_method))
|
||||
holder.register(CardId.pauseBeforePin2, Resources(R.string.response_card_pause_before_pin2, R.string.info_response_card_allow_pin2))
|
||||
holder.register(CardId.walletPublicKey, Resources(R.string.response_card_wallet_public_key, R.string.info_response_card_wallet_public_key))
|
||||
holder.register(CardId.walletRemainingSignatures, Resources(R.string.response_card_wallet_remaining_signatures, R.string.info_response_card_wallet_remaining_signatures))
|
||||
holder.register(CardId.walletSignedHashes, Resources(R.string.response_card_wallet_signed_hashes, R.string.info_response_card_wallet_signed_hashes))
|
||||
holder.register(CardId.health, Resources(R.string.response_card_health, R.string.info_response_card_health))
|
||||
holder.register(CardId.isActivated, Resources(R.string.response_card_is_activated, R.string.info_response_card_is_activated))
|
||||
holder.register(CardId.activationSeed, Resources(R.string.response_card_activation_seed, R.string.info_response_card_activation_seed))
|
||||
holder.register(CardId.paymentFlowVersion, Resources(R.string.response_card_payment_flow_version, R.string.info_response_card_payment_flow_version))
|
||||
holder.register(CardId.userCounter, Resources(R.string.response_card_user_counter, R.string.info_response_card_user_counter))
|
||||
holder.register(CardId.userProtectedCounter, Resources(R.string.response_card_user_protected_counter, R.string.info_response_card_user_protected_counter))
|
||||
}
|
||||
|
||||
private fun initCardData(holder: MainResourceHolder) {
|
||||
holder.register(CardDataId.batchId, Resources(R.string.response_card_card_data_batch_id, R.string.info_response_card_card_data_batch_id))
|
||||
holder.register(CardDataId.manufactureDateTime, Resources(R.string.response_card_card_data_manufacture_date_time, R.string.info_response_card_card_data_manufacture_date_time))
|
||||
holder.register(CardDataId.issuerName, Resources(R.string.response_card_card_data_issuer_name, R.string.info_response_card_card_data_issuer_name))
|
||||
holder.register(CardDataId.blockchainName, Resources(R.string.response_card_card_data_blockchain_name, R.string.info_response_card_card_data_blockchain_name))
|
||||
holder.register(CardDataId.manufacturerSignature, Resources(R.string.response_card_card_data_manufacturer_signature, R.string.info_response_card_card_data_manufacturer_signature))
|
||||
holder.register(CardDataId.productMask, Resources(R.string.response_card_card_data_product_mask, R.string.info_response_card_card_data_product_mask))
|
||||
holder.register(CardDataId.tokenSymbol, Resources(R.string.response_card_card_data_token_symbol, R.string.info_response_card_card_data_token_symbol))
|
||||
holder.register(CardDataId.tokenContractAddress, Resources(R.string.response_card_card_data_token_contract_address, R.string.info_response_card_card_data_token_contract_address))
|
||||
holder.register(CardDataId.tokenDecimal, Resources(R.string.response_card_card_data_token_decimal, R.string.info_response_card_card_data_token_decimal))
|
||||
}
|
||||
|
||||
private fun initSignResponse(holder: MainResourceHolder) {
|
||||
holder.register(SignId.cid, Resources(R.string.response_sign_cid, R.string.info_response_sign_cid))
|
||||
holder.register(SignId.walletSignedHashes, Resources(R.string.response_sign_wallet_signed_hashes, R.string.info_response_sign_wallet_signed_hashes))
|
||||
holder.register(SignId.walletRemainingSignatures, Resources(R.string.response_sign_wallet_remaining_signatures, R.string.info_response_sign_wallet_remaining_signatures))
|
||||
holder.register(SignId.signature, Resources(R.string.response_sign_signature, R.string.info_response_sign_signature))
|
||||
}
|
||||
|
||||
private fun initDepersonalizeResponse(holder: MainResourceHolder) {
|
||||
holder.register(DepersonalizeId.isSuccess, Resources(R.string.response_depersonalize_is_success, R.string.info_response_depersonalize_is_success))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.devkit.ucase.resources.initializers
|
||||
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit.ucase.resources.MainResourceHolder
|
||||
import com.tangem.devkit.ucase.resources.Resources
|
||||
import com.tangem.devkit.ucase.variants.TlvId
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TlvResources {
|
||||
fun init(holder: MainResourceHolder) {
|
||||
initScan(holder)
|
||||
}
|
||||
|
||||
private fun initScan(holder: MainResourceHolder) {
|
||||
holder.register(TlvId.CardId, Resources(R.string.tlv_card_id, R.string.info_tlv_card_id))
|
||||
holder.register(TlvId.TransactionOutHash, Resources(R.string.tlv_transaction_out_hash, R.string.info_tlv_transaction_out_hash))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.devkit.ucase.tunnel
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Errors : Id
|
||||
|
||||
enum class CardError : Errors {
|
||||
NotPersonalized,
|
||||
}
|
||||
|
||||
enum class ItemError : Errors {
|
||||
BadSeries,
|
||||
BadCardNumber,
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.devkit.ucase.tunnel
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
|
||||
interface SnackbarHolder {
|
||||
fun showSnackbar(message: String, length: Int = Snackbar.LENGTH_SHORT)
|
||||
fun showSnackbar(id: Int, length: Int = Snackbar.LENGTH_SHORT)
|
||||
}
|
||||
|
||||
interface ViewScreen
|
||||
|
||||
interface ActionView : ViewScreen, SnackbarHolder {
|
||||
fun enableActionFab(enable: Boolean)
|
||||
fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)? = null)
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
package com.tangem.devkit.ucase.ui
|
||||
|
||||
import android.view.View
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.lifecycle.*
|
||||
import com.google.gson.Gson
|
||||
import com.tangem.SessionError
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.devkit._arch.SingleLiveEvent
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.Payload
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.abstraction.iterate
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.devkit.ucase.domain.responses.ResponseJsonConverter
|
||||
import com.tangem.devkit.ucase.resources.ActionType
|
||||
import com.tangem.devkit.ucase.tunnel.ViewScreen
|
||||
import com.tangem.devkit.ucase.variants.personalize.converter.ItemTypes
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ActionViewModelFactory(private val manager: ItemsManager) : ViewModelProvider.NewInstanceFactory() {
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T = ActionViewModel(manager) as T
|
||||
}
|
||||
|
||||
class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), LifecycleObserver {
|
||||
|
||||
val seResponse = SingleLiveEvent<CommandResponse>()
|
||||
val seResponseData = SingleLiveEvent<CommandResponse>()
|
||||
val seResponseCardData = SingleLiveEvent<Card>()
|
||||
|
||||
val ldItemList = MutableLiveData(itemsManager.getItems())
|
||||
val seError: MutableLiveData<String> = SingleLiveEvent()
|
||||
val seChangedItems: MutableLiveData<List<Item>> = SingleLiveEvent()
|
||||
|
||||
private val notifier: Notifier = Notifier(this)
|
||||
private lateinit var tangemSdk: TangemSdk
|
||||
|
||||
fun setCardManager(tangemSdk: TangemSdk) {
|
||||
this.tangemSdk = tangemSdk
|
||||
}
|
||||
|
||||
@Deprecated("Events must be send directly from the Widget")
|
||||
fun userChangedItem(id: Id, value: Any?) {
|
||||
itemChanged(id, value)
|
||||
}
|
||||
|
||||
private fun itemChanged(id: Id, value: Any?) {
|
||||
itemsManager.itemChanged(id, value) { notifier.notifyItemsChanged(it) }
|
||||
}
|
||||
|
||||
//invokes Scan, Sign etc...
|
||||
fun invokeMainAction() {
|
||||
if (!::tangemSdk.isInitialized) {
|
||||
Log.e(this, "TangemSdk isn't initialized")
|
||||
return
|
||||
}
|
||||
itemsManager.invokeMainAction(tangemSdk) { response, listOfChangedParams ->
|
||||
notifier.handleActionResult(response, listOfChangedParams)
|
||||
}
|
||||
}
|
||||
|
||||
fun getItemAction(id: Id): (() -> Unit)? {
|
||||
val itemFunction = itemsManager.getActionByTag(id, tangemSdk) ?: return null
|
||||
|
||||
return {
|
||||
itemFunction { response, listOfChangedParams ->
|
||||
notifier.handleActionResult(response, listOfChangedParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleDescriptionVisibility(state: Boolean) {
|
||||
ldItemList.value?.iterate {
|
||||
it.viewModel.viewState.descriptionVisibility.value = if (state) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
fun attachToPayload(payload: Payload) {
|
||||
itemsManager.attachPayload(payload)
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
fun viewOnDestroy() {
|
||||
val keyList = mutableListOf<String>()
|
||||
itemsManager.payload.filterValues { it is ViewScreen }.forEach { keyList.add(it.key) }
|
||||
keyList.forEach { itemsManager.payload.remove(it) }
|
||||
}
|
||||
|
||||
fun showFields(type: ActionType) {
|
||||
toggleFieldsVisibility(type, true)
|
||||
}
|
||||
|
||||
fun hideFields(type: ActionType) {
|
||||
toggleFieldsVisibility(type, false)
|
||||
}
|
||||
|
||||
private fun toggleFieldsVisibility(type: ActionType, show: Boolean) {
|
||||
val oftenUsed = getItemsForTogglingVisibilityState(type)
|
||||
val hidden = getItemIdsWhichWontShows(type)
|
||||
itemsManager.getItems().iterate {
|
||||
if (!hidden.contains(it.id)) {
|
||||
if (!oftenUsed.contains(it.id)) {
|
||||
it.viewModel.viewState.isVisibleState.value = show
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun getItemsForTogglingVisibilityState(type: ActionType): List<Id> {
|
||||
return when (type) {
|
||||
ActionType.Personalize -> ItemTypes().oftenUsedList
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getItemIdsWhichWontShows(type: ActionType): List<Id> {
|
||||
return when (type) {
|
||||
ActionType.Personalize -> ItemTypes().hiddenList
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class Notifier(private val vm: ActionViewModel) {
|
||||
|
||||
private var notShowedError: SessionError? = null
|
||||
private val gson: Gson = ResponseJsonConverter().gson
|
||||
|
||||
fun handleActionResult(result: CompletionResult<*>, list: List<Item>) {
|
||||
if (list.isNotEmpty()) notifyItemsChanged(list)
|
||||
handleCompletionResult(result)
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun notifyItemsChanged(list: List<Item>) {
|
||||
vm.seChangedItems.postValue(list)
|
||||
}
|
||||
|
||||
fun handleCompletionResult(result: CompletionResult<*>) {
|
||||
val commandResponse = result as? CompletionResult<CommandResponse> ?: return
|
||||
|
||||
when (commandResponse) {
|
||||
is CompletionResult.Success -> handleData(commandResponse.data)
|
||||
is CompletionResult.Failure -> handleError(commandResponse.error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleData(event: CommandResponse?) {
|
||||
vm.seResponse.postValue(event)
|
||||
when (event) {
|
||||
is Card -> vm.seResponseCardData.postValue(event)
|
||||
else -> vm.seResponseData.postValue(event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleError(error: SessionError) {
|
||||
Log.d(this, "error = $error")
|
||||
when (error) {
|
||||
is SessionError.UserCancelled -> {
|
||||
if (notShowedError == null) {
|
||||
vm.seError.postValue("User canceled the action")
|
||||
} else {
|
||||
vm.seError.postValue("${notShowedError!!::class.simpleName}")
|
||||
notShowedError = null
|
||||
}
|
||||
}
|
||||
else -> notShowedError = error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
package com.tangem.devkit.ucase.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.plusAssign
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._main.MainViewModel
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.PayloadKey
|
||||
import com.tangem.devkit.ucase.tunnel.ActionView
|
||||
import com.tangem.devkit.ucase.tunnel.CardError
|
||||
import com.tangem.devkit.ucase.ui.widgets.ParameterWidget
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.enable
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseCardActionFragment : BaseFragment(), ActionView {
|
||||
|
||||
protected lateinit var swrLayout: SwipeRefreshLayout
|
||||
protected lateinit var contentContainer: ViewGroup
|
||||
protected lateinit var actionFab: FloatingActionButton
|
||||
|
||||
protected abstract val itemsManager: ItemsManager
|
||||
protected val mainActivityVM by activityViewModels<MainViewModel>()
|
||||
protected val actionVM: ActionViewModel by viewModels { ActionViewModelFactory(itemsManager) }
|
||||
protected val UNDEFINED = -1
|
||||
|
||||
private val paramsWidgetList = mutableListOf<ParameterWidget>()
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_base_action_layout
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
Log.d(this, "onViewCreated")
|
||||
|
||||
bindViews()
|
||||
viewLifecycleOwner.lifecycle.addObserver(actionVM)
|
||||
actionVM.setCardManager(TangemSdk.init(requireActivity()))
|
||||
actionVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView))
|
||||
|
||||
initViews()
|
||||
createWidgets {
|
||||
widgetsWasCreated()
|
||||
subscribeToViewModelChanges()
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun widgetsWasCreated() {}
|
||||
|
||||
protected open fun bindViews() {
|
||||
swrLayout = mainView.findViewById(R.id.swr_layout)
|
||||
contentContainer = mainView.findViewById(R.id.ll_content_container)
|
||||
actionFab = mainView.findViewById(R.id.fab_action)
|
||||
}
|
||||
|
||||
protected open fun initViews() {
|
||||
swrLayout.isEnabled = false
|
||||
enableActionFab(false)
|
||||
actionFab.setOnClickListener { actionVM.invokeMainAction() }
|
||||
}
|
||||
|
||||
protected open fun createWidgets(widgetCreatedCallback: () -> Unit) {
|
||||
Log.d(this, "createWidgets")
|
||||
actionVM.ldItemList.observe(viewLifecycleOwner, Observer { itemList ->
|
||||
itemList.forEach { param ->
|
||||
val widget = ParameterWidget(inflateParamView(contentContainer), param)
|
||||
widget.onValueChanged = { id, value -> actionVM.userChangedItem(id, value) }
|
||||
widget.onActionBtnClickListener = actionVM.getItemAction(param.id)
|
||||
paramsWidgetList.add(widget)
|
||||
}
|
||||
widgetCreatedCallback()
|
||||
})
|
||||
}
|
||||
|
||||
protected open fun subscribeToViewModelChanges() {
|
||||
Log.d(this, "subscribeToViewModelChanges")
|
||||
actionVM.seResponse.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "handle response: $it")
|
||||
handleResponse(it)
|
||||
})
|
||||
actionVM.seResponseData.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "handle responseData: $it")
|
||||
handleResponseData(it)
|
||||
})
|
||||
actionVM.seResponseCardData.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "handle responseCardData: $it")
|
||||
handleResponseCardData(it)
|
||||
})
|
||||
actionVM.seError.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "handle error: $it")
|
||||
handleError(it)
|
||||
})
|
||||
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "handle descriptionVisibilityState: $it")
|
||||
handleDescriptionSwitchChanges(it)
|
||||
})
|
||||
listenChangedItems()
|
||||
}
|
||||
|
||||
protected open fun handleResponse(response: CommandResponse) {
|
||||
mainActivityVM.changeResponseEvent(response)
|
||||
}
|
||||
|
||||
protected open fun handleResponseData(response: CommandResponse) {
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen, options = null)
|
||||
}
|
||||
|
||||
protected open fun handleResponseCardData(card: Card) {}
|
||||
|
||||
protected open fun handleError(error: String) {
|
||||
showSnackbar(error)
|
||||
}
|
||||
|
||||
protected open fun handleDescriptionSwitchChanges(descriptionVisibilityState: Boolean) {
|
||||
actionVM.toggleDescriptionVisibility(descriptionVisibilityState)
|
||||
paramsWidgetList.forEach { it.toggleDescriptionVisibility(descriptionVisibilityState) }
|
||||
}
|
||||
|
||||
@Deprecated("Start to use itemViewModel")
|
||||
protected open fun listenChangedItems() {
|
||||
actionVM.seChangedItems.observe(viewLifecycleOwner, Observer { itemList ->
|
||||
itemList.forEach { item ->
|
||||
Log.d(this, "item changed from VM - name: ${item.id}, value:${item.viewModel.data}")
|
||||
paramsWidgetList.firstOrNull { it.id == item.id }?.changeParamValue(item.viewModel.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun inflateParamView(where: ViewGroup): ViewGroup {
|
||||
val inflater = LayoutInflater.from(where.context)
|
||||
val view = inflater.inflate(R.layout.w_card_incoming_param, where, false)
|
||||
where.plusAssign(view)
|
||||
return view as ViewGroup
|
||||
}
|
||||
|
||||
override fun enableActionFab(enable: Boolean) {
|
||||
actionFab.enable(enable)
|
||||
}
|
||||
|
||||
override fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)?) {
|
||||
val resourceId = when (id) {
|
||||
CardError.NotPersonalized -> R.string.card_error_not_personalized
|
||||
else -> additionalHandler?.invoke(id) ?: UNDEFINED
|
||||
}
|
||||
|
||||
if (resourceId != UNDEFINED) showSnackbar(resourceId)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.devkit.ucase.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.fragment.NavHostFragment.findNavController
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.devkit.ucase.getDefaultNavigationOptions
|
||||
import com.tangem.devkit.ucase.tunnel.SnackbarHolder
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseFragment : Fragment(), LayoutHolder, SnackbarHolder {
|
||||
|
||||
protected lateinit var mainView: View
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
mainView = inflater.inflate(getLayoutId(), container, false)
|
||||
return mainView
|
||||
}
|
||||
|
||||
protected fun navigateTo(navigationId: Int, bundle: Bundle? = null, options: NavOptions? = getDefaultNavigationOptions()) {
|
||||
findNavController(this).navigate(navigationId, bundle, options)
|
||||
}
|
||||
|
||||
override fun showSnackbar(id: Int, length: Int) {
|
||||
showSnackbar(requireContext().getString(id), length)
|
||||
}
|
||||
|
||||
override fun showSnackbar(message: String, length: Int) {
|
||||
Snackbar.make(mainView, message, length).show()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.tangem.devkit.ucase.ui.widgets
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.impl.EditTextItem
|
||||
import com.tangem.devkit.extensions.view.beginDelayedTransition
|
||||
import com.tangem.devkit.ucase.resources.ActionType
|
||||
import com.tangem.devkit.ucase.resources.MainResourceHolder
|
||||
import com.tangem.devkit.ucase.resources.Resources
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ParameterWidget(
|
||||
private val parent: ViewGroup,
|
||||
item: Item
|
||||
) {
|
||||
|
||||
val id: Id = item.id
|
||||
val typedItem = item as EditTextItem
|
||||
|
||||
var onValueChanged: ((Id, Any?) -> Unit)? = null
|
||||
var onActionBtnClickListener: (() -> Unit)? = null
|
||||
set(value) {
|
||||
field = value
|
||||
toggleActionBtnVisibility()
|
||||
}
|
||||
|
||||
private val tilValue: TextInputLayout = parent.findViewById(R.id.til_param)
|
||||
private val etValue: TextInputEditText = parent.findViewById(R.id.et_param)
|
||||
private val btnAction: Button = parent.findViewById(R.id.btn_action)
|
||||
private val valueWatcher: TextWatcher by lazy { getWatcher() }
|
||||
|
||||
private val descriptionContainer: ViewGroup by lazy { parent.findViewById<ViewGroup>(R.id.container_description) }
|
||||
private val tvDescription: TextView? by lazy { descriptionContainer.findViewById<TextView>(R.id.tv_description) }
|
||||
|
||||
private var actionBtnVisibilityState: Int = btnAction.visibility
|
||||
private var value: Any? = typedItem.viewModel.data
|
||||
|
||||
init {
|
||||
tilValue.hint = tilValue.context.getString(getResNameId())
|
||||
etValue.setText(stringOf(typedItem.viewModel.data))
|
||||
etValue.addTextChangedListener(valueWatcher)
|
||||
btnAction.setOnClickListener { onActionBtnClickListener?.invoke() }
|
||||
btnAction.setText(getResNameId(ActionType.Scan))
|
||||
toggleActionBtnVisibility()
|
||||
initDescriptionWidget()
|
||||
}
|
||||
|
||||
fun changeParamValue(data: Any?, silent: Boolean = true) {
|
||||
Log.d(this, "changeParamValue: tag: $id, value: $data")
|
||||
value = data
|
||||
toggleActionBtnVisibility()
|
||||
if (silent) {
|
||||
etValue.removeTextChangedListener(valueWatcher)
|
||||
etValue.setText(stringOf(value))
|
||||
etValue.addTextChangedListener(valueWatcher)
|
||||
} else {
|
||||
etValue.setText(stringOf(value))
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleDescriptionVisibility(state: Boolean) {
|
||||
(parent.parent as ViewGroup).beginDelayedTransition()
|
||||
descriptionContainer.visibility = if (state) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun getWatcher(): TextWatcher {
|
||||
return object : TextWatcher {
|
||||
override fun afterTextChanged(editable: Editable?) {
|
||||
Log.d(this, "afterTextChanged $editable")
|
||||
value = if (editable.isNullOrEmpty()) null else editable.toString()
|
||||
toggleActionBtnVisibility()
|
||||
onValueChanged?.invoke(id, value)
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleActionBtnVisibility() {
|
||||
fun switchVisibilityState(newState: Int) {
|
||||
actionBtnVisibilityState = newState
|
||||
parent.beginDelayedTransition()
|
||||
btnAction.visibility = actionBtnVisibilityState
|
||||
}
|
||||
when {
|
||||
onActionBtnClickListener == null -> {
|
||||
if (actionBtnVisibilityState == View.GONE) return
|
||||
switchVisibilityState(View.GONE)
|
||||
}
|
||||
value == null && actionBtnVisibilityState != View.VISIBLE -> switchVisibilityState(View.VISIBLE)
|
||||
value != null && actionBtnVisibilityState != View.GONE -> switchVisibilityState(View.GONE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initDescriptionWidget() {
|
||||
getResDescription()?.let { tvDescription?.setText(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun ParameterWidget.getResNameId(id: Id? = null): Int = MainResourceHolder.safeGet<Resources>(id
|
||||
?: this.id).resName
|
||||
|
||||
fun ParameterWidget.getResDescription(): Int? = MainResourceHolder.safeGet<Resources>(id).resDescription
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.devkit.ucase.variants
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
enum class TlvId : Id {
|
||||
CardId,
|
||||
TransactionOutHash,
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.devkit.ucase.variants.depersonalize.ui
|
||||
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.managers.DepersonalizeItemsManager
|
||||
import com.tangem.devkit.ucase.ui.BaseCardActionFragment
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class DepersonalizeActionFragment : BaseCardActionFragment() {
|
||||
|
||||
override val itemsManager: ItemsManager by lazy { DepersonalizeItemsManager() }
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
interface PersonalizationId : Id
|
||||
|
||||
enum class BlockId : PersonalizationId {
|
||||
CardNumber,
|
||||
Common,
|
||||
SigningMethod,
|
||||
SignHashExProp,
|
||||
Denomination,
|
||||
Token,
|
||||
ProdMask,
|
||||
SettingsMask,
|
||||
SettingsMaskProtocolEnc,
|
||||
SettingsMaskNdef,
|
||||
Pins,
|
||||
}
|
||||
|
||||
enum class CardNumber : PersonalizationId {
|
||||
Series,
|
||||
Number,
|
||||
BatchId,
|
||||
}
|
||||
|
||||
enum class Common : PersonalizationId {
|
||||
Curve,
|
||||
Blockchain,
|
||||
BlockchainCustom,
|
||||
MaxSignatures,
|
||||
CreateWallet,
|
||||
}
|
||||
|
||||
enum class SigningMethod : PersonalizationId {
|
||||
SignTx,
|
||||
SignTxRaw,
|
||||
SignValidatedTx,
|
||||
SignValidatedTxRaw,
|
||||
SignValidatedTxIssuer,
|
||||
SignValidatedTxRawIssuer,
|
||||
SignExternal,
|
||||
}
|
||||
|
||||
enum class SignHashExProp : PersonalizationId {
|
||||
PinLessFloorLimit,
|
||||
CryptoExKey,
|
||||
RequireTerminalCertSig,
|
||||
RequireTerminalTxSig,
|
||||
CheckPin3,
|
||||
}
|
||||
|
||||
enum class Denomination : PersonalizationId {
|
||||
WriteOnPersonalize,
|
||||
Denomination,
|
||||
}
|
||||
|
||||
enum class Token : PersonalizationId {
|
||||
ItsToken,
|
||||
Symbol,
|
||||
ContractAddress,
|
||||
Decimal
|
||||
}
|
||||
|
||||
enum class ProductMask : PersonalizationId {
|
||||
Note,
|
||||
Tag,
|
||||
IdCard,
|
||||
IdIssuerCard
|
||||
}
|
||||
|
||||
enum class SettingsMask : PersonalizationId {
|
||||
IsReusable,
|
||||
NeedActivation,
|
||||
ForbidPurge,
|
||||
AllowSelectBlockchain,
|
||||
UseBlock,
|
||||
OneApdu,
|
||||
UseCvc,
|
||||
AllowSwapPin,
|
||||
AllowSwapPin2,
|
||||
ForbidDefaultPin,
|
||||
SmartSecurityDelay,
|
||||
ProtectIssuerDataAgainstReplay,
|
||||
SkipSecurityDelayIfValidated,
|
||||
SkipPin2CvcIfValidated,
|
||||
SkipSecurityDelayOnLinkedTerminal,
|
||||
RestrictOverwriteExtraIssuerData,
|
||||
}
|
||||
|
||||
enum class SettingsMaskProtocolEnc : PersonalizationId {
|
||||
AllowUnencrypted,
|
||||
AllowStaticEncryption
|
||||
}
|
||||
|
||||
enum class SettingsMaskNdef : PersonalizationId {
|
||||
UseNdef,
|
||||
DynamicNdef,
|
||||
DisablePrecomputedNdef,
|
||||
Aar,
|
||||
AarCustom,
|
||||
Uri,
|
||||
}
|
||||
|
||||
enum class Pins : PersonalizationId {
|
||||
Pin,
|
||||
Pin2,
|
||||
Pin3,
|
||||
Cvc,
|
||||
PauseBeforePin2
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.google.gson.Gson
|
||||
import com.tangem.devkit.AppTangemDemo
|
||||
import com.tangem.devkit.commons.KeyedStore
|
||||
import com.tangem.devkit.commons.Store
|
||||
import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig
|
||||
|
||||
class PersonalizationConfigStore(context: Context) : Store<PersonalizationConfig>, KeyedStore<PersonalizationConfig> {
|
||||
|
||||
companion object {
|
||||
val defaultKey = "default"
|
||||
}
|
||||
|
||||
private val sharedPreferencesKey = "personalization_presets"
|
||||
|
||||
private val sp: SharedPreferences = (context.applicationContext as AppTangemDemo).sharedPreferences(sharedPreferencesKey)
|
||||
private val gson: Gson = Gson()
|
||||
|
||||
override fun save(value: PersonalizationConfig) {
|
||||
save(defaultKey, value)
|
||||
}
|
||||
|
||||
override fun restore(): PersonalizationConfig = restore(defaultKey)
|
||||
|
||||
override fun save(key: String, value: PersonalizationConfig) {
|
||||
sp.edit(true) { putString(key, toJson(value)) }
|
||||
}
|
||||
|
||||
override fun restore(key: String): PersonalizationConfig {
|
||||
val json = sp.getString(key, toJson(getDefaultConfig()))
|
||||
return fromJson(json!!)
|
||||
}
|
||||
|
||||
override fun restoreAll(): MutableMap<String, PersonalizationConfig> {
|
||||
val map = mutableMapOf<String, PersonalizationConfig>()
|
||||
sp.all.forEach { map[it.key] = fromJson(it.value as String) }
|
||||
return map.toSortedMap()
|
||||
}
|
||||
|
||||
override fun delete(key: String) {
|
||||
sp.edit(true) { remove(key) }
|
||||
}
|
||||
|
||||
private fun getDefaultConfig(): PersonalizationConfig = PersonalizationConfig.default()
|
||||
|
||||
private fun toJson(value: PersonalizationConfig): String = gson.toJson(value)
|
||||
|
||||
private fun fromJson(json: String): PersonalizationConfig = gson.fromJson(json, PersonalizationConfig::class.java)
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.converter
|
||||
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.abstraction.KeyValue
|
||||
import com.tangem.devkit.ucase.variants.personalize.*
|
||||
import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ConfigValuesHolder : BaseTypedHolder<Id, Value>() {
|
||||
|
||||
// All registered association values can't be objects
|
||||
fun init(default: PersonalizationConfig) {
|
||||
register(CardNumber.Series, Value(default.series))
|
||||
register(CardNumber.Number, Value(default.startNumber))
|
||||
register(CardNumber.BatchId, Value(default.batchId))
|
||||
register(Common.Curve, Value(default.curveID, Helper.listOfCurves()))
|
||||
register(Common.Blockchain, Value(default.blockchain, Helper.listOfBlockchain()))
|
||||
register(Common.BlockchainCustom, Value(default.blockchainCustom))
|
||||
register(Common.MaxSignatures, Value(default.MaxSignatures))
|
||||
register(Common.CreateWallet, Value(default.createWallet))
|
||||
register(SigningMethod.SignTx, Value(default.SigningMethod0))
|
||||
register(SigningMethod.SignTxRaw, Value(default.SigningMethod1))
|
||||
register(SigningMethod.SignValidatedTx, Value(default.SigningMethod2))
|
||||
register(SigningMethod.SignValidatedTxRaw, Value(default.SigningMethod3))
|
||||
register(SigningMethod.SignValidatedTxIssuer, Value(default.SigningMethod4))
|
||||
register(SigningMethod.SignValidatedTxRawIssuer, Value(default.SigningMethod5))
|
||||
register(SigningMethod.SignExternal, Value(default.SigningMethod6))
|
||||
register(SignHashExProp.PinLessFloorLimit, Value(default.pinLessFloorLimit))
|
||||
register(SignHashExProp.CryptoExKey, Value(default.hexCrExKey))
|
||||
register(SignHashExProp.RequireTerminalCertSig, Value(default.requireTerminalCertSignature))
|
||||
register(SignHashExProp.RequireTerminalTxSig, Value(default.requireTerminalTxSignature))
|
||||
register(SignHashExProp.CheckPin3, Value(default.checkPIN3onCard))
|
||||
register(Denomination.WriteOnPersonalize, Value(default.writeOnPersonalization))
|
||||
register(Denomination.Denomination, Value(default.denomination))
|
||||
register(Token.ItsToken, Value(default.itsToken))
|
||||
register(Token.Symbol, Value(default.symbol))
|
||||
register(Token.ContractAddress, Value(default.contractAddress))
|
||||
register(Token.Decimal, Value(default.decimal))
|
||||
register(ProductMask.Note, Value(default.cardData.product_note))
|
||||
register(ProductMask.Tag, Value(default.cardData.product_tag))
|
||||
register(ProductMask.IdCard, Value(default.cardData.product_id_card))
|
||||
register(ProductMask.IdIssuerCard, Value(default.cardData.product_id_issuer))
|
||||
register(SettingsMask.IsReusable, Value(default.isReusable))
|
||||
register(SettingsMask.NeedActivation, Value(default.useActivation))
|
||||
register(SettingsMask.ForbidPurge, Value(default.forbidPurgeWallet))
|
||||
register(SettingsMask.AllowSelectBlockchain, Value(default.allowSelectBlockchain))
|
||||
register(SettingsMask.UseBlock, Value(default.useBlock))
|
||||
register(SettingsMask.OneApdu, Value(default.oneApdu))
|
||||
register(SettingsMask.UseCvc, Value(default.useCVC))
|
||||
register(SettingsMask.AllowSwapPin, Value(default.allowSwapPIN))
|
||||
register(SettingsMask.AllowSwapPin2, Value(default.allowSwapPIN2))
|
||||
register(SettingsMask.ForbidDefaultPin, Value(default.forbidDefaultPIN))
|
||||
register(SettingsMask.SmartSecurityDelay, Value(default.smartSecurityDelay))
|
||||
register(SettingsMask.ProtectIssuerDataAgainstReplay, Value(default.protectIssuerDataAgainstReplay))
|
||||
register(SettingsMask.SkipSecurityDelayIfValidated, Value(default.skipSecurityDelayIfValidatedByIssuer))
|
||||
register(SettingsMask.SkipPin2CvcIfValidated, Value(default.skipCheckPIN2andCVCIfValidatedByIssuer))
|
||||
register(SettingsMask.SkipSecurityDelayOnLinkedTerminal, Value(default.skipSecurityDelayIfValidatedByLinkedTerminal))
|
||||
register(SettingsMask.RestrictOverwriteExtraIssuerData, Value(default.restrictOverwriteIssuerDataEx))
|
||||
register(SettingsMaskProtocolEnc.AllowUnencrypted, Value(default.protocolAllowUnencrypted))
|
||||
register(SettingsMaskProtocolEnc.AllowStaticEncryption, Value(default.protocolAllowStaticEncryption))
|
||||
register(SettingsMaskNdef.UseNdef, Value(default.useNDEF))
|
||||
register(SettingsMaskNdef.DynamicNdef, Value(default.useDynamicNDEF))
|
||||
register(SettingsMaskNdef.DisablePrecomputedNdef, Value(default.disablePrecomputedNDEF))
|
||||
register(SettingsMaskNdef.Aar, Value(default.aar, Helper.aarList()))
|
||||
register(SettingsMaskNdef.AarCustom, Value(default.aarCustom))
|
||||
register(SettingsMaskNdef.Uri, Value(default.uri))
|
||||
register(Pins.Pin, Value(default.PIN))
|
||||
register(Pins.Pin2, Value(default.PIN2))
|
||||
register(Pins.Pin3, Value(default.PIN3))
|
||||
register(Pins.Cvc, Value(default.CVC))
|
||||
register(Pins.PauseBeforePin2, Value(default.pauseBeforePIN2, Helper.pauseBeforePin()))
|
||||
}
|
||||
}
|
||||
|
||||
class Value(
|
||||
private var value: Any? = null,
|
||||
val list: List<KeyValue>? = mutableListOf()
|
||||
) {
|
||||
fun get(): Any? = value
|
||||
fun set(newValue: Any?) {
|
||||
value = newValue
|
||||
}
|
||||
}
|
||||
|
||||
internal class Helper {
|
||||
companion object {
|
||||
fun listOfCurves(): List<KeyValue> {
|
||||
return EllipticCurve.values().map { KeyValue(it.name, it.curve) }
|
||||
}
|
||||
|
||||
fun listOfBlockchain(): List<KeyValue> {
|
||||
return mutableListOf(
|
||||
KeyValue("--- CUSTOM ---", ""),
|
||||
KeyValue("BTC", "BTC"),
|
||||
KeyValue("BTC/test", "BTC/test"),
|
||||
KeyValue("ETH", "ETH"),
|
||||
KeyValue("ETH/test", "ETH/test"),
|
||||
KeyValue("BCH", "BCH"),
|
||||
KeyValue("BCH/test", "BCH/test"),
|
||||
KeyValue("LTC", "LTC"),
|
||||
KeyValue("XLM", "XLM"),
|
||||
KeyValue("XLM/test", "XLM/test"),
|
||||
KeyValue("RSK", "RSK"),
|
||||
KeyValue("XPR", "XPR"),
|
||||
KeyValue("CARDANO", "CARDANO"),
|
||||
KeyValue("BNB", "BNB"),
|
||||
KeyValue("XTZ", "XTZ"),
|
||||
KeyValue("DUC", "DUC")
|
||||
)
|
||||
}
|
||||
|
||||
fun aarList(): List<KeyValue> {
|
||||
return mutableListOf(
|
||||
KeyValue("--- CUSTOM ---", ""),
|
||||
KeyValue("Release APP", "com.tangem.wallet"),
|
||||
KeyValue("Debug APP", "com.tangem.wallet.debug"),
|
||||
KeyValue("None", "")
|
||||
)
|
||||
}
|
||||
|
||||
fun pauseBeforePin(): List<KeyValue> {
|
||||
return mutableListOf(
|
||||
KeyValue("immediately", 0L),
|
||||
KeyValue("2 seconds", 2000L),
|
||||
KeyValue("5 seconds", 5000L),
|
||||
KeyValue("15 seconds", 15000L),
|
||||
KeyValue("30 seconds", 30000L),
|
||||
KeyValue("1 minute", 60000L),
|
||||
KeyValue("2 minute", 120000L)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.converter
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit.ucase.variants.personalize.*
|
||||
|
||||
class ItemTypes {
|
||||
|
||||
val blockIdList = mutableListOf(
|
||||
BlockId.CardNumber, BlockId.Common, BlockId.SigningMethod, BlockId.SignHashExProp, BlockId.Denomination,
|
||||
BlockId.Token, BlockId.ProdMask, BlockId.SettingsMask, BlockId.SettingsMaskProtocolEnc,
|
||||
BlockId.SettingsMaskNdef, BlockId.Pins
|
||||
)
|
||||
|
||||
val listItemList = mutableListOf(Common.Curve, Common.Blockchain, SettingsMaskNdef.Aar, Pins.PauseBeforePin2)
|
||||
|
||||
val boolList = mutableListOf(
|
||||
Common.CreateWallet, SigningMethod.SignTx, SigningMethod.SignTxRaw, SigningMethod.SignValidatedTx,
|
||||
SigningMethod.SignValidatedTxRaw, SigningMethod.SignValidatedTxIssuer,
|
||||
SigningMethod.SignValidatedTxRawIssuer, SigningMethod.SignExternal, SignHashExProp.RequireTerminalCertSig,
|
||||
SignHashExProp.RequireTerminalTxSig, SignHashExProp.CheckPin3, Denomination.WriteOnPersonalize, Token.ItsToken,
|
||||
ProductMask.Note, ProductMask.Tag, ProductMask.IdCard, ProductMask.IdIssuerCard, SettingsMask.IsReusable, SettingsMask.NeedActivation,
|
||||
SettingsMask.ForbidPurge, SettingsMask.AllowSelectBlockchain, SettingsMask.UseBlock, SettingsMask.OneApdu,
|
||||
SettingsMask.UseCvc, SettingsMask.AllowSwapPin, SettingsMask.AllowSwapPin2, SettingsMask.ForbidDefaultPin,
|
||||
SettingsMask.SmartSecurityDelay, SettingsMask.ProtectIssuerDataAgainstReplay,
|
||||
SettingsMask.SkipSecurityDelayIfValidated, SettingsMask.SkipPin2CvcIfValidated,
|
||||
SettingsMask.SkipSecurityDelayOnLinkedTerminal, SettingsMask.RestrictOverwriteExtraIssuerData,
|
||||
SettingsMaskProtocolEnc.AllowUnencrypted, SettingsMaskProtocolEnc.AllowStaticEncryption,
|
||||
SettingsMaskNdef.UseNdef, SettingsMaskNdef.DynamicNdef, SettingsMaskNdef.DisablePrecomputedNdef
|
||||
)
|
||||
|
||||
val editTextList = mutableListOf(
|
||||
CardNumber.Series, CardNumber.BatchId, Common.BlockchainCustom, SignHashExProp.CryptoExKey, Token.Symbol,
|
||||
Token.ContractAddress, Pins.Pin, Pins.Pin2, Pins.Pin3, Pins.Cvc, SettingsMaskNdef.AarCustom, SettingsMaskNdef.Uri
|
||||
)
|
||||
|
||||
val numberList = mutableListOf(
|
||||
CardNumber.Number, Common.MaxSignatures, SignHashExProp.PinLessFloorLimit, Denomination.Denomination, Token.Decimal
|
||||
)
|
||||
|
||||
val hiddenList = mutableListOf<Id>(
|
||||
CardNumber.Series, CardNumber.BatchId, Pins.Pin3, SigningMethod.SignExternal,
|
||||
SignHashExProp.CryptoExKey, SignHashExProp.CheckPin3, SettingsMask.OneApdu,
|
||||
SettingsMask.UseBlock, SettingsMask.ProtectIssuerDataAgainstReplay,
|
||||
SignHashExProp.RequireTerminalCertSig, SignHashExProp.RequireTerminalTxSig
|
||||
)
|
||||
|
||||
val oftenUsedList = listOf<Id>(
|
||||
// BlockId.CardNumber,
|
||||
BlockId.Common,
|
||||
// BlockId.SigningMethod,
|
||||
// BlockId.SignHashExProp,
|
||||
// BlockId.Denomination,
|
||||
// BlockId.Token,
|
||||
BlockId.ProdMask,
|
||||
// BlockId.SettingsMask,
|
||||
// BlockId.SettingsMaskProtocolEnc,
|
||||
BlockId.SettingsMaskNdef,
|
||||
// BlockId.Pins,
|
||||
Common.Curve,
|
||||
Common.Blockchain,
|
||||
Common.BlockchainCustom,
|
||||
// Common.MaxSignatures,
|
||||
Common.CreateWallet,
|
||||
// SigningMethod.SignTx,
|
||||
// SigningMethod.SignTxRaw,
|
||||
// SigningMethod.SignValidatedTx,
|
||||
// SigningMethod.SignValidatedTxRaw,
|
||||
// SigningMethod.SignValidatedTxIssuer,
|
||||
// SigningMethod.SignValidatedTxRawIssuer,
|
||||
// SigningMethod.SignExternal,
|
||||
// SignHashExProp.PinLessFloorLimit,
|
||||
// SignHashExProp.CryptoExKey,
|
||||
// SignHashExProp.RequireTerminalCertSig,
|
||||
// SignHashExProp.RequireTerminalTxSig,
|
||||
// SignHashExProp.CheckPin3,
|
||||
// Denomination.WriteOnPersonalize,
|
||||
// Denomination.Denomination,
|
||||
// Token.ItsToken,
|
||||
// Token.Symbol,
|
||||
// Token.ContractAddress,
|
||||
// Token.Decimal,
|
||||
ProductMask.Note,
|
||||
ProductMask.Tag,
|
||||
ProductMask.IdCard,
|
||||
ProductMask.IdIssuerCard,
|
||||
// SettingsMask.IsReusable,
|
||||
// SettingsMask.NeedActivation,
|
||||
// SettingsMask.ForbidPurge,
|
||||
// SettingsMask.AllowSelectBlockchain,
|
||||
// SettingsMask.UseBlock,
|
||||
// SettingsMask.OneApdu,
|
||||
// SettingsMask.UseCvc,
|
||||
// SettingsMask.AllowSwapPin,
|
||||
// SettingsMask.AllowSwapPin2,
|
||||
// SettingsMask.ForbidDefaultPin,
|
||||
// SettingsMask.SmartSecurityDelay,
|
||||
// SettingsMask.ProtectIssuerDataAgainstReplay,
|
||||
// SettingsMask.SkipSecurityDelayIfValidated,
|
||||
// SettingsMask.SkipPin2CvcIfValidated,
|
||||
// SettingsMask.SkipSecurityDelayOnLinkedTerminal,
|
||||
// SettingsMask.RestrictOverwriteExtraIssuerData,
|
||||
// SettingsMaskProtocolEnc.AllowUnencrypted,
|
||||
// SettingsMaskProtocolEnc.AllowStaticEncryption,
|
||||
// SettingsMaskNdef.UseNdef,
|
||||
// SettingsMaskNdef.DynamicNdef,
|
||||
// SettingsMaskNdef.DisablePrecomputedNdef,
|
||||
SettingsMaskNdef.Aar,
|
||||
SettingsMaskNdef.AarCustom,
|
||||
SettingsMaskNdef.Uri,
|
||||
// Pins.Pin,
|
||||
// Pins.Pin2,
|
||||
// Pins.Pin3,
|
||||
// Pins.Cvc,
|
||||
Pins.PauseBeforePin2
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.converter
|
||||
|
||||
import com.tangem.devkit._arch.structure.Additional
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.abstraction.*
|
||||
import com.tangem.devkit._arch.structure.impl.*
|
||||
import com.tangem.devkit.ucase.variants.personalize.*
|
||||
import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PersonalizationConfigConverter : ModelConverter<PersonalizationConfig> {
|
||||
|
||||
private val toModel: ItemsToModel<PersonalizationConfig> = ItemsToPersonalizationConfig()
|
||||
private val toItems: ModelToItems<PersonalizationConfig> = PersonalizationConfigToItems()
|
||||
|
||||
override fun convert(from: List<Item>, default: PersonalizationConfig): PersonalizationConfig {
|
||||
return toModel.convert(from, default)
|
||||
}
|
||||
|
||||
override fun convert(from: PersonalizationConfig): List<Item> {
|
||||
return toItems.convert(from)
|
||||
}
|
||||
}
|
||||
|
||||
class ItemsToPersonalizationConfig : ItemsToModel<PersonalizationConfig> {
|
||||
protected val valuesHolder = ConfigValuesHolder()
|
||||
|
||||
override fun convert(from: List<Item>, default: PersonalizationConfig): PersonalizationConfig {
|
||||
valuesHolder.init(default)
|
||||
mapListItems(from)
|
||||
return createModel()
|
||||
}
|
||||
|
||||
private fun mapListItems(itemList: List<Item>) {
|
||||
itemList.iterate { item -> mapItemToHolder(item) }
|
||||
}
|
||||
|
||||
private fun mapItemToHolder(item: Item) {
|
||||
when (item) {
|
||||
is ItemGroup -> mapListItems(item.itemList)
|
||||
is BaseItem -> {
|
||||
val defValue = valuesHolder.get(item.id) ?: return
|
||||
defValue.set(item.viewModel.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createModel(): PersonalizationConfig {
|
||||
val export = PersonalizationConfig()
|
||||
export.series = getTyped(CardNumber.Series)
|
||||
export.startNumber = getTyped(CardNumber.Number)
|
||||
export.batchId = getTyped(CardNumber.BatchId)
|
||||
export.curveID = getTyped(Common.Curve)
|
||||
export.blockchain = getTyped(Common.Blockchain)
|
||||
export.blockchainCustom = getTyped(Common.BlockchainCustom)
|
||||
export.MaxSignatures = getTyped(Common.MaxSignatures)
|
||||
export.createWallet = getTyped(Common.CreateWallet)
|
||||
export.SigningMethod0 = getTyped(SigningMethod.SignTx)
|
||||
export.SigningMethod1 = getTyped(SigningMethod.SignTxRaw)
|
||||
export.SigningMethod2 = getTyped(SigningMethod.SignValidatedTx)
|
||||
export.SigningMethod3 = getTyped(SigningMethod.SignValidatedTxRaw)
|
||||
export.SigningMethod4 = getTyped(SigningMethod.SignValidatedTxIssuer)
|
||||
export.SigningMethod5 = getTyped(SigningMethod.SignValidatedTxRawIssuer)
|
||||
export.SigningMethod6 = getTyped(SigningMethod.SignExternal)
|
||||
export.pinLessFloorLimit = getTyped(SignHashExProp.PinLessFloorLimit)
|
||||
export.hexCrExKey = getTyped(SignHashExProp.CryptoExKey)
|
||||
export.requireTerminalCertSignature = getTyped(SignHashExProp.RequireTerminalCertSig)
|
||||
export.requireTerminalTxSignature = getTyped(SignHashExProp.RequireTerminalTxSig)
|
||||
export.checkPIN3onCard = getTyped(SignHashExProp.CheckPin3)
|
||||
export.writeOnPersonalization = getTyped(Denomination.WriteOnPersonalize)
|
||||
export.denomination = getTyped(Denomination.Denomination)
|
||||
export.itsToken = getTyped(Token.ItsToken)
|
||||
export.symbol = getTyped(Token.Symbol)
|
||||
export.contractAddress = getTyped(Token.ContractAddress)
|
||||
export.decimal = getTyped(Token.Decimal)
|
||||
export.cardData = export.cardData.apply { this.product_note = getTyped(ProductMask.Note) }
|
||||
export.cardData = export.cardData.apply { this.product_tag = getTyped(ProductMask.Tag) }
|
||||
export.cardData = export.cardData.apply { this.product_id_card = getTyped(ProductMask.IdCard) }
|
||||
export.cardData = export.cardData.apply { this.product_id_issuer = getTyped(ProductMask.IdIssuerCard) }
|
||||
export.isReusable = getTyped(SettingsMask.IsReusable)
|
||||
export.useActivation = getTyped(SettingsMask.NeedActivation)
|
||||
export.forbidPurgeWallet = getTyped(SettingsMask.ForbidPurge)
|
||||
export.allowSelectBlockchain = getTyped(SettingsMask.AllowSelectBlockchain)
|
||||
export.useBlock = getTyped(SettingsMask.UseBlock)
|
||||
export.oneApdu = getTyped(SettingsMask.OneApdu)
|
||||
export.useCVC = getTyped(SettingsMask.UseCvc)
|
||||
export.allowSwapPIN = getTyped(SettingsMask.AllowSwapPin)
|
||||
export.allowSwapPIN2 = getTyped(SettingsMask.AllowSwapPin2)
|
||||
export.forbidDefaultPIN = getTyped(SettingsMask.ForbidDefaultPin)
|
||||
export.smartSecurityDelay = getTyped(SettingsMask.SmartSecurityDelay)
|
||||
export.protectIssuerDataAgainstReplay = getTyped(SettingsMask.ProtectIssuerDataAgainstReplay)
|
||||
export.skipSecurityDelayIfValidatedByIssuer = getTyped(SettingsMask.SkipSecurityDelayIfValidated)
|
||||
export.skipCheckPIN2andCVCIfValidatedByIssuer = getTyped(SettingsMask.SkipPin2CvcIfValidated)
|
||||
export.skipSecurityDelayIfValidatedByLinkedTerminal = getTyped(SettingsMask.SkipSecurityDelayOnLinkedTerminal)
|
||||
export.restrictOverwriteIssuerDataEx = getTyped(SettingsMask.RestrictOverwriteExtraIssuerData)
|
||||
export.protocolAllowUnencrypted = getTyped(SettingsMaskProtocolEnc.AllowUnencrypted)
|
||||
export.protocolAllowStaticEncryption = getTyped(SettingsMaskProtocolEnc.AllowStaticEncryption)
|
||||
export.useNDEF = getTyped(SettingsMaskNdef.UseNdef)
|
||||
export.useDynamicNDEF = getTyped(SettingsMaskNdef.DynamicNdef)
|
||||
export.disablePrecomputedNDEF = getTyped(SettingsMaskNdef.DisablePrecomputedNdef)
|
||||
export.aar = getTyped(SettingsMaskNdef.Aar)
|
||||
export.aarCustom = getTyped(SettingsMaskNdef.AarCustom)
|
||||
export.uri = getTyped(SettingsMaskNdef.Uri)
|
||||
export.PIN = getTyped(Pins.Pin)
|
||||
export.PIN2 = getTyped(Pins.Pin2)
|
||||
export.PIN3 = getTyped(Pins.Pin3)
|
||||
export.CVC = getTyped(Pins.Cvc)
|
||||
export.pauseBeforePIN2 = getTyped(Pins.PauseBeforePin2)
|
||||
return export
|
||||
}
|
||||
|
||||
private inline fun <reified Type> getTyped(id: Id): Type {
|
||||
return getTypedBy<Type>(valuesHolder, id)!!
|
||||
}
|
||||
|
||||
private inline fun <reified Type> getTypedBy(holder: ConfigValuesHolder, id: Id): Type? {
|
||||
Log.d(this, "getTyped for id: $id")
|
||||
var typedValue = holder.get(id)?.get()
|
||||
|
||||
typedValue = when (typedValue) {
|
||||
is ListViewModel -> typedValue.selectedItem as Type
|
||||
else -> typedValue as? Type ?: null
|
||||
}
|
||||
return typedValue
|
||||
}
|
||||
}
|
||||
|
||||
class PersonalizationConfigToItems : ModelToItems<PersonalizationConfig> {
|
||||
private val valuesHolder = ConfigValuesHolder()
|
||||
private val itemTypes = ItemTypes()
|
||||
|
||||
override fun convert(from: PersonalizationConfig): List<Item> {
|
||||
valuesHolder.init(from)
|
||||
val blocList = mutableListOf<Item>()
|
||||
blocList.add(cardNumber())
|
||||
blocList.add(common())
|
||||
blocList.add(signingMethod())
|
||||
blocList.add(signHashExProperties())
|
||||
blocList.add(denomination())
|
||||
blocList.add(token())
|
||||
blocList.add(productMask())
|
||||
blocList.add(settingsMask())
|
||||
blocList.add(settingsMaskProtocolEnc())
|
||||
blocList.add(settingsMaskNdef())
|
||||
blocList.add(pins())
|
||||
blocList.iterate {
|
||||
if (itemTypes.hiddenList.contains(it.id)) {
|
||||
it.viewModel.viewState.isVisibleState.value = false
|
||||
}
|
||||
}
|
||||
return blocList
|
||||
}
|
||||
|
||||
private fun cardNumber(): ItemGroup {
|
||||
val block = createGroup(BlockId.CardNumber)
|
||||
mutableListOf(
|
||||
CardNumber.Series,
|
||||
CardNumber.Number,
|
||||
CardNumber.BatchId
|
||||
).forEach { createItem(block, it as Id) }
|
||||
return block
|
||||
}
|
||||
|
||||
private fun common(): ItemGroup {
|
||||
val block = createGroup(BlockId.Common)
|
||||
mutableListOf(
|
||||
Common.Blockchain,
|
||||
Common.BlockchainCustom,
|
||||
Common.Curve,
|
||||
Common.MaxSignatures,
|
||||
Common.CreateWallet,
|
||||
Pins.PauseBeforePin2
|
||||
).forEach { createItem(block, it as Id) }
|
||||
return block
|
||||
}
|
||||
|
||||
private fun signingMethod(): ItemGroup {
|
||||
val block = createGroup(BlockId.SigningMethod)
|
||||
mutableListOf(
|
||||
SigningMethod.SignTx,
|
||||
SigningMethod.SignTxRaw,
|
||||
SigningMethod.SignValidatedTx,
|
||||
SigningMethod.SignValidatedTxRaw,
|
||||
SigningMethod.SignValidatedTxIssuer,
|
||||
SigningMethod.SignValidatedTxRawIssuer,
|
||||
SigningMethod.SignExternal
|
||||
).forEach { createItem(block, it) }
|
||||
return block
|
||||
}
|
||||
|
||||
private fun signHashExProperties(): ItemGroup {
|
||||
val block = createGroup(BlockId.SignHashExProp)
|
||||
mutableListOf(
|
||||
SignHashExProp.PinLessFloorLimit,
|
||||
SignHashExProp.CryptoExKey,
|
||||
SignHashExProp.RequireTerminalCertSig,
|
||||
SignHashExProp.RequireTerminalTxSig,
|
||||
SignHashExProp.CheckPin3
|
||||
).forEach { createItem(block, it) }
|
||||
return block
|
||||
}
|
||||
|
||||
private fun denomination(): ItemGroup {
|
||||
val block = createGroup(BlockId.Denomination)
|
||||
mutableListOf(
|
||||
Denomination.WriteOnPersonalize,
|
||||
Denomination.Denomination
|
||||
).forEach { createItem(block, it) }
|
||||
return block
|
||||
}
|
||||
|
||||
private fun token(): ItemGroup {
|
||||
val block = createGroup(BlockId.Token)
|
||||
mutableListOf(
|
||||
Token.ItsToken,
|
||||
Token.Symbol,
|
||||
Token.ContractAddress,
|
||||
Token.Decimal
|
||||
).forEach { createItem(block, it) }
|
||||
return block
|
||||
}
|
||||
|
||||
private fun productMask(): ItemGroup {
|
||||
val block = createGroup(BlockId.ProdMask)
|
||||
mutableListOf(
|
||||
ProductMask.Note,
|
||||
ProductMask.Tag,
|
||||
ProductMask.IdCard,
|
||||
ProductMask.IdIssuerCard
|
||||
).forEach { createItem(block, it) }
|
||||
return block
|
||||
}
|
||||
|
||||
private fun settingsMask(): ItemGroup {
|
||||
val block = createGroup(BlockId.SettingsMask)
|
||||
mutableListOf(
|
||||
SettingsMask.IsReusable,
|
||||
SettingsMask.NeedActivation,
|
||||
SettingsMask.ForbidPurge,
|
||||
SettingsMask.AllowSelectBlockchain,
|
||||
SettingsMask.UseBlock,
|
||||
SettingsMask.OneApdu,
|
||||
SettingsMask.UseCvc,
|
||||
SettingsMask.AllowSwapPin,
|
||||
SettingsMask.AllowSwapPin2,
|
||||
SettingsMask.ForbidDefaultPin,
|
||||
SettingsMask.SmartSecurityDelay,
|
||||
SettingsMask.ProtectIssuerDataAgainstReplay,
|
||||
SettingsMask.SkipSecurityDelayIfValidated,
|
||||
SettingsMask.SkipPin2CvcIfValidated,
|
||||
SettingsMask.SkipSecurityDelayOnLinkedTerminal,
|
||||
SettingsMask.RestrictOverwriteExtraIssuerData
|
||||
).forEach { createItem(block, it) }
|
||||
return block
|
||||
}
|
||||
|
||||
private fun settingsMaskProtocolEnc(): ItemGroup {
|
||||
val block = createGroup(BlockId.SettingsMaskProtocolEnc)
|
||||
mutableListOf(
|
||||
SettingsMaskProtocolEnc.AllowUnencrypted,
|
||||
SettingsMaskProtocolEnc.AllowStaticEncryption
|
||||
).forEach { createItem(block, it) }
|
||||
return block
|
||||
}
|
||||
|
||||
private fun settingsMaskNdef(): ItemGroup {
|
||||
val block = createGroup(BlockId.SettingsMaskNdef)
|
||||
mutableListOf(
|
||||
SettingsMaskNdef.UseNdef,
|
||||
SettingsMaskNdef.DynamicNdef,
|
||||
SettingsMaskNdef.DisablePrecomputedNdef,
|
||||
SettingsMaskNdef.Aar,
|
||||
SettingsMaskNdef.AarCustom,
|
||||
SettingsMaskNdef.Uri
|
||||
).forEach { createItem(block, it) }
|
||||
return block
|
||||
}
|
||||
|
||||
private fun pins(): ItemGroup {
|
||||
val block = createGroup(BlockId.Pins)
|
||||
mutableListOf(
|
||||
Pins.Pin,
|
||||
Pins.Pin2,
|
||||
Pins.Pin3,
|
||||
Pins.Cvc
|
||||
).forEach { createItem(block, it) }
|
||||
return block
|
||||
}
|
||||
|
||||
private fun createGroup(id: Id): ItemGroup {
|
||||
return SimpleItemGroup(id).apply { addItem(TextItem(id)) }
|
||||
}
|
||||
|
||||
private fun createItem(itemGroup: ItemGroup, id: Id) {
|
||||
val holder = valuesHolder.get(id) ?: return
|
||||
val item = when {
|
||||
itemTypes.blockIdList.contains(id) -> TextItem(id, holder.get() as? String)
|
||||
itemTypes.listItemList.contains(id) -> SpinnerItem(id, holder.list as List<KeyValue>, holder.get())
|
||||
itemTypes.boolList.contains(id) -> BoolItem(id, holder.get() as? Boolean)
|
||||
itemTypes.editTextList.contains(id) -> EditTextItem(id, holder.get() as? String)
|
||||
itemTypes.numberList.contains(id) -> NumberItem(id, holder.get() as? Number)
|
||||
else -> SimpleItemGroup(Additional.UNDEFINED)
|
||||
}
|
||||
itemGroup.addItem(item)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.converter
|
||||
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.personalization.entities.CardConfig
|
||||
import com.tangem.commands.personalization.entities.NdefRecord
|
||||
import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.common.Converter
|
||||
import java.util.*
|
||||
|
||||
class PersonalizationConfigToCardConfig : Converter<PersonalizationConfig, CardConfig> {
|
||||
|
||||
override fun convert(from: PersonalizationConfig): CardConfig {
|
||||
val signingMethodMaskBuilder = SigningMethodMaskBuilder()
|
||||
if (from.SigningMethod0) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHash)
|
||||
}
|
||||
if (from.SigningMethod1) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRaw)
|
||||
}
|
||||
if (from.SigningMethod2) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuer)
|
||||
}
|
||||
if (from.SigningMethod3) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuer)
|
||||
}
|
||||
if (from.SigningMethod4) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuerWriteIssuerData)
|
||||
}
|
||||
if (from.SigningMethod5) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuerWriteIssuerData)
|
||||
}
|
||||
if (from.SigningMethod6) {
|
||||
signingMethodMaskBuilder.add(SigningMethod.SignHash)
|
||||
}
|
||||
val signingMethod = signingMethodMaskBuilder.build()
|
||||
|
||||
val isNote = from.cardData.product_note
|
||||
val isTag = from.cardData.product_tag
|
||||
val isIdCard = from.cardData.product_id_card
|
||||
val isIdIssuer = from.cardData.product_id_issuer
|
||||
|
||||
val productMaskBuilder = ProductMaskBuilder()
|
||||
if (isNote) productMaskBuilder.add(com.tangem.commands.Product.Note)
|
||||
if (isTag) productMaskBuilder.add(com.tangem.commands.Product.Tag)
|
||||
if (isIdCard) productMaskBuilder.add(com.tangem.commands.Product.IdCard)
|
||||
if (isIdIssuer) productMaskBuilder.add(com.tangem.commands.Product.IdIssuer)
|
||||
val productMask = productMaskBuilder.build()
|
||||
|
||||
var tokenSymbol: String? = null
|
||||
var tokenContractAddress: String? = null
|
||||
var tokenDecimal: Int? = null
|
||||
if (from.itsToken) {
|
||||
tokenSymbol = from.symbol
|
||||
tokenContractAddress = from.contractAddress
|
||||
tokenDecimal = from.decimal.toInt()
|
||||
}
|
||||
|
||||
val blockchain = if (from.blockchain.isNotEmpty()) from.blockchain else from.blockchainCustom
|
||||
val cardData = CardData(
|
||||
blockchainName = blockchain,
|
||||
batchId = from.batchId,
|
||||
productMask = productMask,
|
||||
tokenSymbol = tokenSymbol,
|
||||
tokenContractAddress = tokenContractAddress,
|
||||
tokenDecimal = tokenDecimal,
|
||||
issuerName = null,
|
||||
manufactureDateTime = Calendar.getInstance().time,
|
||||
manufacturerSignature = null)
|
||||
|
||||
|
||||
val ndefs = mutableListOf<NdefRecord>()
|
||||
if (from.uri.isNotEmpty()) {
|
||||
ndefs.add(NdefRecord(NdefRecord.Type.URI, from.uri))
|
||||
}
|
||||
when (from.aar) {
|
||||
"None" -> null
|
||||
"--- CUSTOM ---" -> NdefRecord(NdefRecord.Type.AAR, from.aarCustom)
|
||||
else -> NdefRecord(NdefRecord.Type.AAR, from.aar)
|
||||
}?.let { ndefs.add(it) }
|
||||
|
||||
return CardConfig(
|
||||
"Tangem",
|
||||
"Tangem Test",
|
||||
from.series,
|
||||
from.startNumber,
|
||||
1000,
|
||||
from.PIN,
|
||||
from.PIN2,
|
||||
from.PIN3,
|
||||
from.hexCrExKey,
|
||||
from.CVC,
|
||||
from.pauseBeforePIN2.toInt(),
|
||||
from.smartSecurityDelay,
|
||||
EllipticCurve.byName(from.curveID) ?: EllipticCurve.Secp256k1,
|
||||
signingMethod,
|
||||
from.MaxSignatures.toInt(),
|
||||
from.isReusable,
|
||||
from.allowSwapPIN,
|
||||
from.allowSwapPIN2,
|
||||
from.useActivation,
|
||||
from.useCVC,
|
||||
from.useNDEF,
|
||||
from.useDynamicNDEF,
|
||||
from.oneApdu,
|
||||
from.useBlock,
|
||||
from.allowSelectBlockchain,
|
||||
from.forbidPurgeWallet,
|
||||
from.protocolAllowUnencrypted,
|
||||
from.protocolAllowStaticEncryption,
|
||||
from.protectIssuerDataAgainstReplay,
|
||||
from.forbidDefaultPIN,
|
||||
from.disablePrecomputedNDEF,
|
||||
from.skipSecurityDelayIfValidatedByIssuer,
|
||||
from.skipCheckPIN2andCVCIfValidatedByIssuer,
|
||||
from.skipSecurityDelayIfValidatedByLinkedTerminal,
|
||||
from.restrictOverwriteIssuerDataEx,
|
||||
from.requireTerminalTxSignature,
|
||||
from.requireTerminalCertSignature,
|
||||
from.checkPIN3onCard,
|
||||
from.createWallet,
|
||||
cardData,
|
||||
ndefs
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.dto
|
||||
|
||||
import com.tangem.KeyPair
|
||||
import com.tangem.commands.personalization.entities.Acquirer
|
||||
import com.tangem.commands.personalization.entities.Issuer
|
||||
import com.tangem.commands.personalization.entities.Manufacturer
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
interface DefaultPersonalizationParams {
|
||||
companion object {
|
||||
|
||||
fun issuer(): Issuer {
|
||||
val name = "TANGEM SDK"
|
||||
return Issuer(
|
||||
name = name,
|
||||
id = name + "\u0000",
|
||||
dataKeyPair = KeyPair(
|
||||
"".toByteArray(),
|
||||
"11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92".toByteArray()
|
||||
),
|
||||
transactionKeyPair = KeyPair(
|
||||
"".toByteArray(),
|
||||
"11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312".toByteArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun acquirer(): Acquirer {
|
||||
val name = "Smart Cash"
|
||||
return Acquirer(
|
||||
name = name,
|
||||
id = name + "\u0000",
|
||||
keyPair = KeyPair(
|
||||
"".toByteArray(),
|
||||
"21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92".toByteArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun manufacturer(): Manufacturer {
|
||||
val keys = Manufacturer(com.tangem.devkit.ucase.variants.personalize.dto.Manufacturer.Mode.Developer)
|
||||
return Manufacturer(
|
||||
name = "Tangem",
|
||||
keyPair = KeyPair(keys.publicKey, keys.privateKey)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.dto
|
||||
|
||||
class Manufacturer(private val mode: Mode) {
|
||||
|
||||
private val devPrivateKeyArray = byteArrayOf(
|
||||
0x1B.toByte(), 0x48.toByte(), 0xCF.toByte(), 0xD2.toByte(), 0x4B.toByte(), 0xBB.toByte(), 0x5B.toByte(), 0x39.toByte(),
|
||||
0x47.toByte(), 0x71.toByte(), 0xED.toByte(), 0x81.toByte(), 0xF2.toByte(), 0xBA.toByte(), 0xCF.toByte(), 0x57.toByte(),
|
||||
0x47.toByte(), 0x9E.toByte(), 0x47.toByte(), 0x35.toByte(), 0xEB.toByte(), 0x14.toByte(), 0x05.toByte(), 0x08.toByte(),
|
||||
0x39.toByte(), 0x27.toByte(), 0x37.toByte(), 0x2D.toByte(), 0x40.toByte(), 0xDA.toByte(), 0x9E.toByte(), 0x92.toByte()
|
||||
)
|
||||
private val devPublicKeyArray = byteArrayOf(0x04,
|
||||
0xBA.toByte(), 0xB8.toByte(), 0x6D.toByte(), 0x56.toByte(), 0x29.toByte(), 0x8C.toByte(), 0x99.toByte(), 0x6F.toByte(),
|
||||
0x56.toByte(), 0x4A.toByte(), 0x84.toByte(), 0xFC.toByte(), 0x88.toByte(), 0xE2.toByte(), 0x8A.toByte(), 0xED.toByte(),
|
||||
0x38.toByte(), 0x18.toByte(), 0x4B.toByte(), 0x12.toByte(), 0xF0.toByte(), 0x7E.toByte(), 0x51.toByte(), 0x91.toByte(),
|
||||
0x13.toByte(), 0xBE.toByte(), 0xF4.toByte(), 0x8C.toByte(), 0x76.toByte(), 0xF3.toByte(), 0xDF.toByte(), 0x3A.toByte(),
|
||||
0xDC.toByte(), 0x30.toByte(), 0x35.toByte(), 0x99.toByte(), 0xB0.toByte(), 0x8A.toByte(), 0xC0.toByte(), 0x5B.toByte(),
|
||||
0x55.toByte(), 0xEC.toByte(), 0x3D.toByte(), 0xF9.toByte(), 0x8D.toByte(), 0x93.toByte(), 0x38.toByte(), 0x57.toByte(),
|
||||
0x3A.toByte(), 0x62.toByte(), 0x42.toByte(), 0xF7.toByte(), 0x6F.toByte(), 0x5D.toByte(), 0x28.toByte(), 0xF4.toByte(),
|
||||
0xF0.toByte(), 0xF3.toByte(), 0x64.toByte(), 0xE8.toByte(), 0x7E.toByte(), 0x8F.toByte(), 0xCA.toByte(), 0x2F.toByte()
|
||||
)
|
||||
private val releasePublicKeyArray = byteArrayOf(0x04.toByte(),
|
||||
0x2E.toByte(), 0xDE.toByte(), 0x11.toByte(), 0x9B.toByte(), 0xF3.toByte(), 0x37.toByte(), 0xB2.toByte(), 0x64.toByte(),
|
||||
0xFD.toByte(), 0xA1.toByte(), 0x32.toByte(), 0xCF.toByte(), 0xC7.toByte(), 0xC1.toByte(), 0x77.toByte(), 0x82.toByte(),
|
||||
0x4D.toByte(), 0x36.toByte(), 0x17.toByte(), 0xDA.toByte(), 0xC8.toByte(), 0x0F.toByte(), 0x25.toByte(), 0xDB.toByte(),
|
||||
0xB2.toByte(), 0xA4.toByte(), 0xA8.toByte(), 0xA1.toByte(), 0x18.toByte(), 0x3C.toByte(), 0x03.toByte(), 0xB9.toByte(),
|
||||
0x15.toByte(), 0x23.toByte(), 0x05.toByte(), 0xF8.toByte(), 0xF1.toByte(), 0xDB.toByte(), 0x97.toByte(), 0x00.toByte(),
|
||||
0x45.toByte(), 0x18.toByte(), 0x48.toByte(), 0x0D.toByte(), 0x50.toByte(), 0x91.toByte(), 0xAD.toByte(), 0xC1.toByte(),
|
||||
0xCA.toByte(), 0xB9.toByte(), 0xEA.toByte(), 0xCC.toByte(), 0xC1.toByte(), 0x8E.toByte(), 0x1B.toByte(), 0x9E.toByte(),
|
||||
0x9C.toByte(), 0x3B.toByte(), 0xEF.toByte(), 0xB2.toByte(), 0x93.toByte(), 0xDD.toByte(), 0x37.toByte(), 0xB2.toByte()
|
||||
)
|
||||
|
||||
val publicKey: ByteArray
|
||||
get() = if (mode == Mode.Developer) {
|
||||
devPublicKeyArray
|
||||
} else {
|
||||
releasePublicKeyArray
|
||||
}
|
||||
|
||||
@get:Throws(Exception::class)
|
||||
val privateKey: ByteArray
|
||||
get() = if (mode == Mode.Developer) {
|
||||
devPrivateKeyArray
|
||||
} else {
|
||||
throw Exception("Real manufacturer key is very secret!")
|
||||
}
|
||||
|
||||
val name: String
|
||||
get() = if (mode == Mode.Release) "SMART CASH" else "SMART CASH SDK"
|
||||
|
||||
enum class Mode {
|
||||
Developer, Release
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.dto
|
||||
|
||||
import com.tangem.commands.EllipticCurve
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PersonalizationConfig {
|
||||
|
||||
// Card number
|
||||
var series = ""
|
||||
var startNumber: Long = 0
|
||||
var batchId = ""
|
||||
|
||||
// Common
|
||||
var curveID = ""
|
||||
var blockchain = ""
|
||||
var blockchainCustom = ""
|
||||
var MaxSignatures: Long = 0
|
||||
var createWallet = false
|
||||
|
||||
// Signing method
|
||||
var SigningMethod0 = false
|
||||
var SigningMethod1 = false
|
||||
var SigningMethod2 = false
|
||||
var SigningMethod3 = false
|
||||
var SigningMethod4 = false
|
||||
var SigningMethod5 = false
|
||||
var SigningMethod6 = false
|
||||
|
||||
// Sign hash external properties
|
||||
var pinLessFloorLimit: Long = 0
|
||||
var hexCrExKey = ""
|
||||
var requireTerminalTxSignature = false
|
||||
var requireTerminalCertSignature = false
|
||||
var checkPIN3onCard = false
|
||||
|
||||
// Denomination
|
||||
var writeOnPersonalization = false
|
||||
var denomination: Long = 0
|
||||
|
||||
// Token
|
||||
var itsToken = false
|
||||
var symbol = ""
|
||||
var contractAddress = ""
|
||||
var decimal: Long = 0
|
||||
|
||||
var cardData = CardData()
|
||||
|
||||
// Settings mask
|
||||
var isReusable = false
|
||||
var useActivation = false
|
||||
var forbidPurgeWallet = false
|
||||
var allowSelectBlockchain = false
|
||||
var useBlock = false
|
||||
var oneApdu = false
|
||||
var useCVC = false
|
||||
var allowSwapPIN = false
|
||||
var allowSwapPIN2 = false
|
||||
var forbidDefaultPIN = false
|
||||
var smartSecurityDelay = false
|
||||
var protectIssuerDataAgainstReplay = false
|
||||
var skipSecurityDelayIfValidatedByIssuer = false
|
||||
var skipCheckPIN2andCVCIfValidatedByIssuer = false
|
||||
var skipSecurityDelayIfValidatedByLinkedTerminal = false
|
||||
var restrictOverwriteIssuerDataEx = false
|
||||
|
||||
// Settings mask - protocol encryption
|
||||
var protocolAllowUnencrypted = false
|
||||
var protocolAllowStaticEncryption = false
|
||||
|
||||
var useNDEF = false
|
||||
var useDynamicNDEF = false
|
||||
var disablePrecomputedNDEF = false
|
||||
var aar = ""
|
||||
var aarCustom = ""
|
||||
var uri = ""
|
||||
|
||||
// Pins
|
||||
var PIN = ""
|
||||
var PIN2 = ""
|
||||
var PIN3 = ""
|
||||
var CVC = ""
|
||||
var pauseBeforePIN2: Long = 0
|
||||
|
||||
companion object {
|
||||
fun default(): PersonalizationConfig {
|
||||
return PersonalizationConfig().apply {
|
||||
// Card number
|
||||
series = "BB"
|
||||
startNumber = 300000000000L
|
||||
batchId = "ffff"
|
||||
|
||||
// Common
|
||||
curveID = EllipticCurve.Secp256k1.curve
|
||||
blockchain = "ETH"
|
||||
blockchainCustom = ""
|
||||
MaxSignatures = 999999L
|
||||
createWallet = true
|
||||
|
||||
// Signing method
|
||||
SigningMethod0 = true
|
||||
SigningMethod1 = false
|
||||
SigningMethod2 = false
|
||||
SigningMethod3 = false
|
||||
SigningMethod4 = false
|
||||
SigningMethod5 = false
|
||||
SigningMethod6 = false
|
||||
|
||||
// Sign hash external properties
|
||||
pinLessFloorLimit = 100000L
|
||||
hexCrExKey = "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"
|
||||
requireTerminalTxSignature = false
|
||||
requireTerminalCertSignature = false
|
||||
checkPIN3onCard = true
|
||||
|
||||
// Denomination
|
||||
writeOnPersonalization = false
|
||||
denomination = 1000000L
|
||||
|
||||
// Token
|
||||
itsToken = false
|
||||
symbol = ""
|
||||
contractAddress = ""
|
||||
decimal = 0L
|
||||
|
||||
cardData = CardData()
|
||||
|
||||
// Settings mask
|
||||
isReusable = true
|
||||
useActivation = false
|
||||
forbidPurgeWallet = false
|
||||
allowSelectBlockchain = false
|
||||
useBlock = false
|
||||
oneApdu = false
|
||||
useCVC = false
|
||||
allowSwapPIN = true
|
||||
allowSwapPIN2 = true
|
||||
forbidDefaultPIN = false
|
||||
smartSecurityDelay = true
|
||||
protectIssuerDataAgainstReplay = true
|
||||
skipSecurityDelayIfValidatedByIssuer = true
|
||||
skipCheckPIN2andCVCIfValidatedByIssuer = true
|
||||
skipSecurityDelayIfValidatedByLinkedTerminal = true
|
||||
restrictOverwriteIssuerDataEx = false
|
||||
|
||||
// Settings mask - protocol encryption
|
||||
protocolAllowUnencrypted = true
|
||||
protocolAllowStaticEncryption = true
|
||||
|
||||
useNDEF = true
|
||||
useDynamicNDEF = true
|
||||
disablePrecomputedNDEF = false
|
||||
aar = "com.tangem.wallet"
|
||||
aarCustom = ""
|
||||
uri = "https://tangem.com"
|
||||
|
||||
// Pins
|
||||
PIN = "000000"
|
||||
PIN2 = "000"
|
||||
PIN3 = ""
|
||||
CVC = "000"
|
||||
pauseBeforePIN2 = 5000L
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CardData {
|
||||
var date = "2020-02-17"
|
||||
var batch = "FF87"
|
||||
var blockchain = "IROHA"
|
||||
var product_note = true
|
||||
var product_tag = false
|
||||
var product_id_card = false
|
||||
var product_id_issuer = false
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.transition.Fade
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.StringId
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.abstraction.SafeValueChanged
|
||||
import com.tangem.devkit._arch.widget.WidgetBuilder
|
||||
import com.tangem.devkit.commons.DialogController
|
||||
import com.tangem.devkit.commons.view.MultiActionView
|
||||
import com.tangem.devkit.commons.view.ViewAction
|
||||
import com.tangem.devkit.extensions.view.beginDelayedTransition
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.PayloadKey
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.managers.PersonalizationItemsManager
|
||||
import com.tangem.devkit.ucase.resources.ActionType
|
||||
import com.tangem.devkit.ucase.tunnel.ActionView
|
||||
import com.tangem.devkit.ucase.tunnel.ItemError
|
||||
import com.tangem.devkit.ucase.ui.BaseCardActionFragment
|
||||
import com.tangem.devkit.ucase.variants.personalize.PersonalizationConfigStore
|
||||
import com.tangem.devkit.ucase.variants.personalize.ui.presets.PersonalizationPresetManager
|
||||
import com.tangem.devkit.ucase.variants.personalize.ui.presets.PersonalizationPresetView
|
||||
import com.tangem.devkit.ucase.variants.personalize.ui.presets.RvPresetNamesAdapter
|
||||
import com.tangem.devkit.ucase.variants.personalize.ui.widgets.PersonalizationItemBuilder
|
||||
import com.tangem.devkit.ucase.variants.responses.ui.ResponseFragment
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.threading.post
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postWork
|
||||
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PersonalizationFragment : BaseCardActionFragment(), PersonalizationPresetView {
|
||||
|
||||
override val itemsManager: ItemsManager by lazy { PersonalizationItemsManager(PersonalizationConfigStore(requireContext())) }
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_base_action_layout
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
lifecycle.addObserver(itemsManager as PersonalizationItemsManager)
|
||||
}
|
||||
|
||||
override fun bindViews() {
|
||||
super.bindViews()
|
||||
swrLayout.isRefreshing = true
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
actionFab.setOnClickListener { actionVM.invokeMainAction() }
|
||||
}
|
||||
|
||||
override fun createWidgets(widgetCreatedCallback: () -> Unit) {
|
||||
Log.d(this, "createWidgets")
|
||||
val itemList = mutableListOf<Item>()
|
||||
|
||||
val maxDelay = 500
|
||||
val timeStart = System.currentTimeMillis()
|
||||
actionVM.ldItemList.observe(viewLifecycleOwner, Observer { list ->
|
||||
Log.d(this, "ldBlockList size: ${list.size}")
|
||||
itemList.addAll(list)
|
||||
val llContainer = LinearLayout(requireContext()).apply { orientation = LinearLayout.VERTICAL }
|
||||
postWork {
|
||||
val builder = WidgetBuilder(PersonalizationItemBuilder())
|
||||
itemList.forEach { builder.build(it, llContainer) }
|
||||
actionVM.attachToPayload(mutableMapOf(
|
||||
PayloadKey.actionView to this as ActionView,
|
||||
PayloadKey.itemList to itemList
|
||||
))
|
||||
val timeEnd = System.currentTimeMillis()
|
||||
val diff = timeEnd - timeStart
|
||||
postUI(maxDelay - diff) {
|
||||
contentContainer.beginDelayedTransition(Fade())
|
||||
contentContainer.addView(llContainer)
|
||||
widgetCreatedCallback()
|
||||
swrLayout.isRefreshing = false
|
||||
swrLayout.isEnabled = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun widgetsWasCreated() {
|
||||
super.widgetsWasCreated()
|
||||
|
||||
val btnContainer = contentContainer.inflate<ViewGroup>(R.layout.view_simple_button)
|
||||
val btn = btnContainer.findViewById<Button>(R.id.button)
|
||||
|
||||
val show = StringId("show")
|
||||
val hide = StringId("hide")
|
||||
val multiAction = MultiActionView(mutableListOf(
|
||||
ViewAction(show, R.string.show_rare_fields) { actionVM.showFields(ActionType.Personalize) },
|
||||
ViewAction(hide, R.string.hide_rare_fields) { actionVM.hideFields(ActionType.Personalize) }
|
||||
), btn)
|
||||
multiAction.afterAction = {
|
||||
multiAction.state = if (it == show) hide else show
|
||||
}
|
||||
multiAction.performAction(hide)
|
||||
contentContainer.addView(btnContainer)
|
||||
}
|
||||
|
||||
override fun handleResponseCardData(card: Card) {
|
||||
super.handleResponseCardData(card)
|
||||
val bundle = ResponseFragment.setTittle(R.string.fg_name_response_personalization)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen, bundle, null)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_fg_peronalization, menu)
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val store = PersonalizationConfigStore(requireContext())
|
||||
val presetManager = PersonalizationPresetManager(itemsManager, store, this)
|
||||
val result = when (item.itemId) {
|
||||
R.id.action_reset -> presetManager.resetToDefault()
|
||||
R.id.action_save -> presetManager.savePreset()
|
||||
R.id.action_load -> presetManager.loadPreset()
|
||||
else -> null
|
||||
}
|
||||
return if (result == null) super.onOptionsItemSelected(item) else true
|
||||
}
|
||||
|
||||
override fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)?) {
|
||||
super.showSnackbar(id) {
|
||||
when (id) {
|
||||
ItemError.BadSeries -> R.string.card_error_bad_series
|
||||
ItemError.BadCardNumber -> R.string.card_error_bad_series_number
|
||||
else -> additionalHandler?.invoke(id) ?: UNDEFINED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun showSavePresetDialog(onOk: SafeValueChanged<String>) {
|
||||
val dlgController = DialogController()
|
||||
val dlg = dlgController.createAlert(requireActivity(), R.layout.dlg_personalization_preset_save)
|
||||
dlg.setTitle(R.string.menu_personalization_preset_save)
|
||||
dlg.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.btn_cancel)) { dialog, which -> }
|
||||
dlg.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.btn_ok)) { dialog, which ->
|
||||
val tvName = dlgController.view?.findViewById<EditText>(R.id.et_item)
|
||||
?: return@setButton
|
||||
val name = tvName.text.toString()
|
||||
if (name.isEmpty()) showSnackbar("Not saved")
|
||||
else onOk.invoke(name)
|
||||
}
|
||||
dlgController.onShowCallback = {
|
||||
dlgController.view?.findViewById<TextView>(R.id.et_item)?.let {
|
||||
post(150) {
|
||||
it.requestFocus()
|
||||
val imm = getSystemService(requireContext(), InputMethodManager::class.java)
|
||||
imm?.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
}
|
||||
dlgController.show()
|
||||
}
|
||||
|
||||
override fun showLoadPresetDialog(namesList: List<String>, onChoose: SafeValueChanged<String>, onDelete: SafeValueChanged<String>) {
|
||||
val dlgController = DialogController()
|
||||
dlgController.createAlert(requireActivity(), R.layout.dlg_personalization_preset_load)
|
||||
.setTitle(R.string.menu_personalization_preset_load)
|
||||
|
||||
val rvPresetNames: RecyclerView = dlgController.view?.findViewById(R.id.recycler_view) ?: return
|
||||
val layoutManager = LinearLayoutManager(context)
|
||||
rvPresetNames.layoutManager = layoutManager
|
||||
rvPresetNames.addItemDecoration(DividerItemDecoration(activity, layoutManager.orientation))
|
||||
|
||||
val adapter = RvPresetNamesAdapter({
|
||||
onChoose(it)
|
||||
dlgController.dismiss()
|
||||
}, {
|
||||
onDelete(it)
|
||||
if (rvPresetNames.adapter?.itemCount == 0)
|
||||
dlgController.dismiss()
|
||||
})
|
||||
adapter.setItemList(namesList.toMutableList())
|
||||
|
||||
rvPresetNames.adapter = adapter
|
||||
dlgController.show()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui.presets
|
||||
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.devkit.ucase.variants.personalize.PersonalizationConfigStore
|
||||
import com.tangem.devkit.ucase.variants.personalize.converter.PersonalizationConfigConverter
|
||||
import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig
|
||||
|
||||
class PersonalizationPresetManager(
|
||||
private val itemsManager: ItemsManager,
|
||||
private val store: PersonalizationConfigStore,
|
||||
private val view: PersonalizationPresetView
|
||||
) {
|
||||
|
||||
fun resetToDefault() {
|
||||
val config = PersonalizationConfig.default()
|
||||
val converter = PersonalizationConfigConverter()
|
||||
itemsManager.updateByItemList(converter.convert(config))
|
||||
store.save(config)
|
||||
}
|
||||
|
||||
fun loadPreset() {
|
||||
val presets = store.restoreAll()
|
||||
presets.remove(PersonalizationConfigStore.defaultKey)
|
||||
val namesList = presets.map { it.key }.toMutableList()
|
||||
if (namesList.isEmpty()) {
|
||||
view.showSnackbar(R.string.error_nothing_to_load)
|
||||
return
|
||||
}
|
||||
|
||||
view.showLoadPresetDialog(namesList, {
|
||||
val converter = PersonalizationConfigConverter()
|
||||
val config = store.restore(it)
|
||||
itemsManager.updateByItemList(converter.convert(config))
|
||||
}, {
|
||||
store.delete(it)
|
||||
})
|
||||
}
|
||||
|
||||
fun savePreset() {
|
||||
view.showSavePresetDialog { name ->
|
||||
val converter = PersonalizationConfigConverter()
|
||||
val config = converter.convert(itemsManager.getItems(), PersonalizationConfig.default())
|
||||
store.save(name, config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui.presets
|
||||
|
||||
import com.tangem.devkit._arch.structure.abstraction.SafeValueChanged
|
||||
import com.tangem.devkit.ucase.tunnel.SnackbarHolder
|
||||
|
||||
interface PersonalizationPresetView : SnackbarHolder {
|
||||
fun showSavePresetDialog(onOk: SafeValueChanged<String>)
|
||||
fun showLoadPresetDialog(namesList: List<String>, onChoose: SafeValueChanged<String>, onDelete: SafeValueChanged<String>)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui.presets
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.abstraction.SafeValueChanged
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseAdapter
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvVH
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class RvPresetNamesAdapter(
|
||||
private val onItemClicked: SafeValueChanged<String>,
|
||||
private val onDeleteClicked: SafeValueChanged<String>
|
||||
) : RvBaseAdapter<PresetNameVH, String>() {
|
||||
override fun onBindViewHolder(holder: PresetNameVH, position: Int) {
|
||||
holder.bindData(itemList[position])
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PresetNameVH {
|
||||
val view = parent.inflate<View>(R.layout.vh_personalization_preset_name, false)
|
||||
return PresetNameVH(view, onItemClicked, { position, value ->
|
||||
itemList.removeAt(position)
|
||||
this.notifyItemRemoved(position)
|
||||
onDeleteClicked(value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class PresetNameVH(
|
||||
itemView: View,
|
||||
private val onItemClicked: SafeValueChanged<String>,
|
||||
private val onDeleteClicked: (Int, String) -> Unit
|
||||
) : RvVH<String>(itemView) {
|
||||
private val tvName = itemView.findViewById<TextView>(R.id.tv_name)
|
||||
private val btnDelete = itemView.findViewById<View>(R.id.btn_delete)
|
||||
override fun onDataBound(data: String) {
|
||||
tvName.text = data
|
||||
tvName.isClickable = false
|
||||
itemView.setOnClickListener { onItemClicked(data) }
|
||||
btnDelete.setOnClickListener { onDeleteClicked(absoluteAdapterPosition, data) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.devkit._arch.structure.StringId
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.widget.abstraction.BaseViewWidget
|
||||
import com.tangem.devkit._arch.widget.abstraction.ViewWidget
|
||||
import com.tangem.devkit.ucase.resources.MainResourceHolder
|
||||
import com.tangem.devkit.ucase.resources.Resources
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.stringFrom
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseAppWidget(parent: ViewGroup, item: Item) : BaseViewWidget(parent, item) {
|
||||
override fun getName(): String {
|
||||
val idName = if (item.id is StringId) (item.id as StringId).value else ""
|
||||
|
||||
val resources = getResources() ?: return idName
|
||||
return view.stringFrom(resources.resName)
|
||||
}
|
||||
}
|
||||
|
||||
fun ViewWidget.getResources(): Resources? = MainResourceHolder.get(item.id)
|
||||
|
||||
fun ViewWidget.getResNameId(): Int = MainResourceHolder.safeGet<Resources>(item.id).resName
|
||||
|
||||
fun ViewWidget.getResDescriptionId(): Int? = MainResourceHolder.get(item.id)?.resDescription
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.abstraction.ViewState
|
||||
import com.tangem.devkit.extensions.view.beginDelayedTransition
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class DescriptionWidget(
|
||||
parent: ViewGroup,
|
||||
item: Item
|
||||
) : BaseAppWidget(parent, item) {
|
||||
|
||||
private val descriptionContainer: ViewGroup by lazy { view.findViewById<ViewGroup>(R.id.container_description) }
|
||||
private val tvDescription: TextView? by lazy { descriptionContainer.findViewById<TextView>(R.id.tv_description) }
|
||||
|
||||
override fun subscribeToViewStateChanges(viewState: ViewState) {
|
||||
super.subscribeToViewStateChanges(viewState)
|
||||
viewState.descriptionVisibility.onValueChanged = { changeDescriptionVisibility(it) }
|
||||
}
|
||||
|
||||
protected open fun changeDescriptionVisibility(state: Int) {
|
||||
val tv = tvDescription ?: return
|
||||
val descriptionId = getResDescriptionId() ?: return
|
||||
val description = tv.context.getString(descriptionId)
|
||||
if (description.isEmpty()) return
|
||||
|
||||
tv.text = description
|
||||
(view.parent as ViewGroup).beginDelayedTransition()
|
||||
descriptionContainer.visibility = state
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.impl.EditTextItem
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.moveCursorToEnd
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class EditTextWidget(
|
||||
parent: ViewGroup,
|
||||
private val typedItem: EditTextItem
|
||||
) : DescriptionWidget(parent, typedItem) {
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.w_personalize_item_edit_text
|
||||
|
||||
private val tilItem = view.findViewById<TextInputLayout>(R.id.til_item)
|
||||
private val etItem = view.findViewById<TextInputEditText>(R.id.et_item)
|
||||
|
||||
private val watcher = object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
typedItem.viewModel.updateDataByView(stringOf(s))
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
}
|
||||
|
||||
init {
|
||||
tilItem.hint = getName()
|
||||
etItem.setText(typedItem.getTypedData())
|
||||
etItem.addTextChangedListener(watcher)
|
||||
item.viewModel.onDataUpdated = { silentUpdate(it as? String) }
|
||||
}
|
||||
|
||||
private fun silentUpdate(value: String?) {
|
||||
etItem.removeTextChangedListener(watcher)
|
||||
etItem.setText(value)
|
||||
etItem.moveCursorToEnd()
|
||||
etItem.addTextChangedListener(watcher)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.InputFilter
|
||||
import android.text.TextWatcher
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.impl.NumberItem
|
||||
import com.tangem.devkit.ucase.variants.personalize.CardNumber
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.addInputFilter
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.moveCursorToEnd
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class NumberWidget(
|
||||
parent: ViewGroup,
|
||||
private val typedItem: NumberItem
|
||||
) : DescriptionWidget(parent, typedItem) {
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.w_personalize_item_number
|
||||
|
||||
private val tilItem = view.findViewById<TextInputLayout>(R.id.til_item)
|
||||
private val etItem = view.findViewById<TextInputEditText>(R.id.et_item)
|
||||
|
||||
private val watcher = object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
typedItem.viewModel.updateDataByView(getValue(stringOf(s)))
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
}
|
||||
|
||||
init {
|
||||
tilItem.hint = getName()
|
||||
|
||||
//TODO: remove from Widget
|
||||
if (item.id == CardNumber.Number) etItem.addInputFilter(InputFilter.LengthFilter(13))
|
||||
|
||||
etItem.setText(stringOf(typedItem.getTypedData()))
|
||||
etItem.addTextChangedListener(watcher)
|
||||
item.viewModel.onDataUpdated = { silentUpdate(it as? Number) }
|
||||
}
|
||||
|
||||
private fun silentUpdate(value: Number?) {
|
||||
etItem.removeTextChangedListener(watcher)
|
||||
etItem.setText(stringOf(value))
|
||||
etItem.moveCursorToEnd()
|
||||
etItem.addTextChangedListener(watcher)
|
||||
}
|
||||
|
||||
private fun getValue(value: String): Long {
|
||||
return if (value.isEmpty()) 0L else value.toLong()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.devkit._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.devkit._arch.structure.impl.*
|
||||
import com.tangem.devkit._arch.widget.ItemWidgetBuilder
|
||||
import com.tangem.devkit._arch.widget.abstraction.ViewWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PersonalizationItemBuilder : ItemWidgetBuilder {
|
||||
override fun build(item: BaseItem, parent: ViewGroup): ViewWidget? {
|
||||
return when (item) {
|
||||
is TextItem -> TextHeaderWidget(parent, item)
|
||||
is EditTextItem -> EditTextWidget(parent, item)
|
||||
is NumberItem -> NumberWidget(parent, item)
|
||||
is BoolItem -> SwitchWidget(parent, item)
|
||||
is SpinnerItem -> SpinnerWidget(parent, item)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.abstraction.KeyValue
|
||||
import com.tangem.devkit._arch.structure.abstraction.ListViewModel
|
||||
import com.tangem.devkit._arch.structure.impl.SpinnerItem
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SpinnerWidget(
|
||||
parent: ViewGroup,
|
||||
typedItem: SpinnerItem
|
||||
) : DescriptionWidget(parent, typedItem) {
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.w_personalize_item_spinner
|
||||
|
||||
private val viewModel: ListViewModel = typedItem.viewModel as ListViewModel
|
||||
|
||||
private val spinner = view.findViewById<Spinner>(R.id.sp_item)
|
||||
private val spAdapter = SpItemAdapter(viewModel.itemList)
|
||||
|
||||
private val onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
viewModel.selectedItem = viewModel.itemList[position].value
|
||||
item.viewModel.updateDataByView(viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val name = view.findViewById<TextView>(R.id.tv_name)
|
||||
name.text = getName()
|
||||
spinner.adapter = spAdapter
|
||||
spAdapter.getItemPosition(viewModel.selectedItem)?.let {
|
||||
spinner.setSelection(it)
|
||||
}
|
||||
spinner.onItemSelectedListener = onItemSelectedListener
|
||||
item.viewModel.onDataUpdated = {
|
||||
spinner.onItemSelectedListener = null
|
||||
viewModel.itemList.firstOrNull { item -> item.value == it }?.let { keyValue ->
|
||||
val position = viewModel.itemList.indexOf(keyValue)
|
||||
spinner.setSelection(position)
|
||||
}
|
||||
spinner.onItemSelectedListener = onItemSelectedListener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SpItemAdapter(list: List<KeyValue>?) : BaseAdapter() {
|
||||
|
||||
private val itemList: List<KeyValue> = list ?: listOf()
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val view = convertView ?: parent.inflate(R.layout.vh_sp_item)
|
||||
|
||||
val tv = view.findViewById<TextView>(R.id.tv_sp_item)
|
||||
tv.text = extractData(itemList[position])
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val view = convertView ?: parent.inflate(R.layout.vh_sp_dropdown_item)
|
||||
|
||||
val tv = view.findViewById<TextView>(R.id.tv_sp_item)
|
||||
tv.text = extractData(itemList[position])
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun extractData(item: KeyValue): String? = item.key
|
||||
|
||||
override fun getItem(position: Int): String = stringOf(itemList[position])
|
||||
|
||||
override fun getItemId(position: Int): Long = position.toLong()
|
||||
|
||||
override fun getCount(): Int = itemList.size
|
||||
|
||||
fun getItemPosition(item: Any?): Int? {
|
||||
val selectedValue = item ?: return null
|
||||
val kv = itemList.firstOrNull { it.value == selectedValue } ?: return null
|
||||
return itemList.indexOf(kv)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CompoundButton
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.impl.BoolItem
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SwitchWidget(
|
||||
parent: ViewGroup,
|
||||
private val typedItem: BoolItem
|
||||
) : DescriptionWidget(parent, typedItem) {
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.w_personalize_item_switch
|
||||
|
||||
private val switchItem = view.findViewById<SwitchCompat>(R.id.sw_item)
|
||||
|
||||
private val changeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
|
||||
typedItem.viewModel.updateDataByView(isChecked)
|
||||
}
|
||||
|
||||
init {
|
||||
switchItem.text = getName()
|
||||
switchItem.isChecked = typedItem.getTypedData() ?: false
|
||||
switchItem.setOnCheckedChangeListener(changeListener)
|
||||
typedItem.viewModel.onDataUpdated = {
|
||||
switchItem.setOnCheckedChangeListener(null)
|
||||
switchItem.isChecked = it as? Boolean ?: false
|
||||
switchItem.setOnCheckedChangeListener(changeListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.devkit.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.impl.TextItem
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TextHeaderWidget(parent: ViewGroup, data: TextItem) : DescriptionWidget(parent, data) {
|
||||
override fun getLayoutId(): Int = R.layout.w_personalize_item_header
|
||||
|
||||
private val tvName = view.findViewById<TextView>(R.id.tv_name)
|
||||
|
||||
init {
|
||||
tvName.text = getName()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.devkit.ucase.variants.responses
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ResponseId : Id
|
||||
|
||||
enum class CardId : ResponseId {
|
||||
cardId,
|
||||
manufacturerName,
|
||||
status,
|
||||
firmwareVersion,
|
||||
cardPublicKey,
|
||||
settingsMask,
|
||||
cardData,
|
||||
issuerPublicKey,
|
||||
curve,
|
||||
maxSignatures,
|
||||
signingMethod,
|
||||
pauseBeforePin2,
|
||||
walletPublicKey,
|
||||
walletRemainingSignatures,
|
||||
walletSignedHashes,
|
||||
health,
|
||||
isActivated,
|
||||
activationSeed,
|
||||
paymentFlowVersion,
|
||||
userCounter,
|
||||
userProtectedCounter,
|
||||
empty
|
||||
}
|
||||
|
||||
enum class CardDataId : ResponseId {
|
||||
batchId,
|
||||
manufactureDateTime,
|
||||
issuerName,
|
||||
blockchainName,
|
||||
manufacturerSignature,
|
||||
productMask,
|
||||
tokenSymbol,
|
||||
tokenContractAddress,
|
||||
tokenDecimal,
|
||||
}
|
||||
|
||||
enum class SignId : ResponseId {
|
||||
cid,
|
||||
walletSignedHashes,
|
||||
walletRemainingSignatures,
|
||||
signature,
|
||||
}
|
||||
|
||||
enum class DepersonalizeId: ResponseId {
|
||||
isSuccess
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.devkit.ucase.variants.responses
|
||||
|
||||
import android.view.View
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.commands.personalization.DepersonalizeResponse
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.abstraction.ModelToItems
|
||||
import com.tangem.devkit._arch.structure.abstraction.iterate
|
||||
import com.tangem.devkit.ucase.variants.responses.converter.ConvertersStore
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ResponseViewModel : ViewModel() {
|
||||
|
||||
private val convertersHolder = ConvertersStore()
|
||||
private var itemList: List<Item>? = null
|
||||
|
||||
fun createItemList(response: CommandResponse?): List<Item> {
|
||||
Log.d(this, "createItemList: itemList size: ${itemList?.size ?: 0}")
|
||||
|
||||
val responseEvent = response ?: return emptyList()
|
||||
val type = responseEvent::class.java
|
||||
val converter = convertersHolder.get(type) as? ModelToItems<Any> ?: return emptyList()
|
||||
itemList = converter.convert(responseEvent)
|
||||
return itemList!!
|
||||
}
|
||||
|
||||
fun toggleDescriptionVisibility(state: Boolean) {
|
||||
itemList?.iterate {
|
||||
it.viewModel.viewState.descriptionVisibility.value = if (state) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
fun determineTitleId(response: CommandResponse?): Int {
|
||||
val responseEvent = response ?: return R.string.unknown
|
||||
|
||||
return when (responseEvent) {
|
||||
is Card -> R.string.fg_name_response_personalization
|
||||
is SignResponse -> R.string.fg_name_response_sign
|
||||
is DepersonalizeResponse -> R.string.fg_name_response_depersonalization
|
||||
else -> R.string.unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.devkit.ucase.variants.responses.converter
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.abstraction.*
|
||||
import com.tangem.devkit.ucase.domain.responses.ResponseFieldConverter
|
||||
import com.tangem.devkit.ucase.variants.responses.item.TextHeaderItem
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseResponseConverter<M> : ModelToItems<M> {
|
||||
protected val fieldConverter = ResponseFieldConverter()
|
||||
|
||||
protected open fun createGroup(id: Id, colorId: Int? = null, addHeaderItem: Boolean = true): ItemGroup {
|
||||
val group = if (colorId == null) SimpleItemGroup(id)
|
||||
else SimpleItemGroup(id, BaseItemViewModel(viewState = ViewState(bgColor = colorId)))
|
||||
|
||||
if (addHeaderItem) group.addItem(TextHeaderItem(id, ""))
|
||||
return group
|
||||
}
|
||||
|
||||
protected open fun valueToString(value: Any?): String? {
|
||||
if (value == null) return null
|
||||
return stringOf(value)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package com.tangem.devkit.ucase.variants.responses.converter
|
||||
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.StringId
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.abstraction.ItemGroup
|
||||
import com.tangem.devkit._arch.structure.abstraction.iterate
|
||||
import com.tangem.devkit._arch.structure.impl.BoolItem
|
||||
import com.tangem.devkit._arch.structure.impl.TextItem
|
||||
import com.tangem.devkit.ucase.variants.responses.CardDataId
|
||||
import com.tangem.devkit.ucase.variants.responses.CardId
|
||||
import com.tangem.devkit.ucase.variants.responses.item.TextHeaderItem
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CardConverter : BaseResponseConverter<Card>() {
|
||||
|
||||
override fun convert(from: Card): List<Item> {
|
||||
val itemList = mutableListOf<Item>()
|
||||
commonGroup(itemList, from)
|
||||
cardDataGroup(itemList, from.cardData)
|
||||
settingsMaskGroup(itemList, from.settingsMask)
|
||||
hideEmptyNullFields(itemList)
|
||||
|
||||
return itemList
|
||||
}
|
||||
|
||||
private fun hideEmptyNullFields(itemList: MutableList<Item>) {
|
||||
itemList.iterate {
|
||||
if (it is ItemGroup) return@iterate
|
||||
|
||||
if (it.getData<Any?>() == null) it.viewModel.viewState.isVisibleState.value = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun commonGroup(itemList: MutableList<Item>, from: Card) {
|
||||
val group = createGroup(CardId.empty, addHeaderItem = false)
|
||||
itemList.add(group)
|
||||
|
||||
group.addItem(TextItem(CardId.cardId, from.cardId))
|
||||
group.addItem(TextItem(CardId.manufacturerName, from.manufacturerName))
|
||||
group.addItem(TextItem(CardId.status, valueToString(from.status)))
|
||||
group.addItem(TextItem(CardId.firmwareVersion, from.firmwareVersion))
|
||||
group.addItem(TextItem(CardId.cardPublicKey, fieldConverter.byteArray(from.cardPublicKey)))
|
||||
group.addItem(TextItem(CardId.issuerPublicKey, fieldConverter.byteArray(from.issuerPublicKey)))
|
||||
group.addItem(TextItem(CardId.curve, valueToString(from.curve)))
|
||||
group.addItem(TextItem(CardId.maxSignatures, valueToString(from.maxSignatures)))
|
||||
group.addItem(TextItem(CardId.pauseBeforePin2, valueToString(from.pauseBeforePin2)))
|
||||
group.addItem(TextItem(CardId.walletPublicKey, fieldConverter.byteArray(from.walletPublicKey)))
|
||||
group.addItem(TextItem(CardId.walletRemainingSignatures, valueToString(from.walletRemainingSignatures)))
|
||||
group.addItem(TextItem(CardId.walletSignedHashes, valueToString(from.walletSignedHashes)))
|
||||
group.addItem(TextItem(CardId.health, valueToString(from.health)))
|
||||
group.addItem(TextItem(CardId.isActivated, valueToString(from.isActivated)))
|
||||
group.addItem(TextItem(CardId.activationSeed, valueToString(from.activationSeed)))
|
||||
group.addItem(TextItem(CardId.paymentFlowVersion, valueToString(from.paymentFlowVersion)))
|
||||
group.addItem(TextItem(CardId.userCounter, valueToString(from.userCounter)))
|
||||
|
||||
val signingMethodMask = from.signingMethods ?: return
|
||||
|
||||
group.addItem(TextHeaderItem(CardId.signingMethod, ""))
|
||||
SigningMethod.values().forEach { group.addItem(BoolItem(StringId(it.name), signingMethodMask.contains(it))) }
|
||||
}
|
||||
|
||||
private fun cardDataGroup(itemList: MutableList<Item>, cardData: CardData?) {
|
||||
val data = cardData ?: return
|
||||
|
||||
val group = createGroup(CardId.cardData, R.color.group_card_data)
|
||||
itemList.add(group)
|
||||
group.addItem(TextItem(CardDataId.batchId, data.batchId))
|
||||
group.addItem(TextItem(CardDataId.manufactureDateTime, valueToString(data.manufactureDateTime)))
|
||||
group.addItem(TextItem(CardDataId.issuerName, data.issuerName))
|
||||
group.addItem(TextItem(CardDataId.blockchainName, data.blockchainName))
|
||||
group.addItem(TextItem(CardDataId.manufacturerSignature, fieldConverter.byteArray(data.manufacturerSignature)))
|
||||
group.addItem(TextItem(CardDataId.tokenSymbol, data.tokenSymbol))
|
||||
group.addItem(TextItem(CardDataId.tokenContractAddress, data.tokenContractAddress))
|
||||
group.addItem(TextItem(CardDataId.tokenDecimal, data.tokenSymbol))
|
||||
|
||||
val productMask = data.productMask ?: return
|
||||
|
||||
group.addItem(TextHeaderItem(CardDataId.productMask, ""))
|
||||
Product.values().forEach { group.addItem(BoolItem(StringId(it.name), productMask.contains(it))) }
|
||||
}
|
||||
|
||||
private fun settingsMaskGroup(itemList: MutableList<Item>, from: SettingsMask?) {
|
||||
val data = from ?: return
|
||||
|
||||
val group = createGroup(CardId.settingsMask, R.color.group_settings_mask)
|
||||
itemList.add(group)
|
||||
|
||||
Settings.values().forEach { group.addItem(BoolItem(StringId(it.name), data.contains(it))) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.devkit.ucase.variants.responses.converter
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.commands.personalization.DepersonalizeResponse
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class ConvertersStore : BaseTypedHolder<Type, Any>() {
|
||||
init {
|
||||
// register(CompletionResult.Success<Card>::class.java, ReadEventConverter())
|
||||
register(SignResponse::class.java, SignResponseConverter())
|
||||
register(Card::class.java, CardConverter())
|
||||
register(DepersonalizeResponse::class.java, DepersonalizeResponseConverter())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.devkit.ucase.variants.responses.converter
|
||||
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.commands.personalization.DepersonalizeResponse
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit._arch.structure.impl.TextItem
|
||||
import com.tangem.devkit.ucase.variants.responses.DepersonalizeId
|
||||
import com.tangem.devkit.ucase.variants.responses.SignId
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SignResponseConverter : BaseResponseConverter<SignResponse>() {
|
||||
|
||||
override fun convert(from: SignResponse): List<Item> {
|
||||
return listOf(
|
||||
TextItem(SignId.cid, from.cardId),
|
||||
TextItem(SignId.walletSignedHashes, valueToString(from.walletSignedHashes)),
|
||||
TextItem(SignId.walletRemainingSignatures, valueToString(from.walletRemainingSignatures)),
|
||||
TextItem(SignId.signature, fieldConverter.byteArray(from.signature))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class DepersonalizeResponseConverter : BaseResponseConverter<DepersonalizeResponse>() {
|
||||
override fun convert(from: DepersonalizeResponse): List<Item> {
|
||||
return listOf(TextItem(DepersonalizeId.isSuccess, stringOf(from.success)))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.devkit.ucase.variants.responses.item
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.abstraction.BaseItemViewModel
|
||||
import com.tangem.devkit._arch.structure.abstraction.ItemViewModel
|
||||
import com.tangem.devkit._arch.structure.abstraction.ViewState
|
||||
import com.tangem.devkit._arch.structure.impl.TypedItem
|
||||
|
||||
open class TextHeaderItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
|
||||
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
|
||||
: this(id, BaseItemViewModel(value, viewState))
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package com.tangem.devkit.ucase.variants.responses.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.widget.WidgetBuilder
|
||||
import com.tangem.devkit._main.MainViewModel
|
||||
import com.tangem.devkit.extensions.shareText
|
||||
import com.tangem.devkit.ucase.domain.responses.ResponseJsonConverter
|
||||
import com.tangem.devkit.ucase.ui.BaseFragment
|
||||
import com.tangem.devkit.ucase.variants.responses.ResponseViewModel
|
||||
import com.tangem.devkit.ucase.variants.responses.ui.widget.ResponseItemBuilder
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
open class ResponseFragment : BaseFragment() {
|
||||
|
||||
private val mainActivityVM: MainViewModel by activityViewModels()
|
||||
private val selfVM: ResponseViewModel by viewModels()
|
||||
|
||||
private val itemContainer: ViewGroup by lazy { mainView.findViewById<LinearLayout>(R.id.ll_content_container) }
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_card_response
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
setTittle()
|
||||
}
|
||||
|
||||
private fun setTittle() {
|
||||
val titleId = getTittleId(arguments) ?: selfVM.determineTitleId(mainActivityVM.commandResponse)
|
||||
activity?.setTitle(titleId)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
buildWidgets()
|
||||
listenDescriptionSwitchChanges()
|
||||
}
|
||||
|
||||
private fun buildWidgets() {
|
||||
val builder = WidgetBuilder(ResponseItemBuilder())
|
||||
val itemList = selfVM.createItemList(mainActivityVM.commandResponse)
|
||||
itemList.forEach { builder.build(it, itemContainer) }
|
||||
}
|
||||
|
||||
private fun listenDescriptionSwitchChanges() {
|
||||
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
|
||||
selfVM.toggleDescriptionVisibility(it)
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_fg_response, menu)
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_share -> {
|
||||
shareText(ResponseJsonConverter().convertResponse(mainActivityVM.commandResponse))
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val argTittle = "tittle"
|
||||
|
||||
fun setTittle(@StringRes id: Int): Bundle = bundleOf(Pair(argTittle, id))
|
||||
|
||||
private fun getTittleId(args: Bundle?): Int? = args?.getInt(argTittle)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.devkit.ucase.variants.responses.ui.widget
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.checkbox.MaterialCheckBox
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.impl.BoolItem
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.colorFrom
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CheckBoxWidget(
|
||||
parent: ViewGroup,
|
||||
private val typedItem: BoolItem
|
||||
) : ResponseWidget(parent, typedItem) {
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.w_response_item_checkbox
|
||||
|
||||
private val switchItem = view.findViewById<MaterialCheckBox>(R.id.sw_item)
|
||||
|
||||
init {
|
||||
switchItem.text = getName()
|
||||
switchItem.isChecked = typedItem.getData() ?: false
|
||||
switchItem.isClickable = false
|
||||
switchItem.setTextColor(switchItem.colorFrom(R.color.action_name))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.devkit.ucase.variants.responses.ui.widget
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.devkit._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.devkit._arch.structure.impl.BoolItem
|
||||
import com.tangem.devkit._arch.structure.impl.TextItem
|
||||
import com.tangem.devkit._arch.widget.ItemWidgetBuilder
|
||||
import com.tangem.devkit._arch.widget.abstraction.ViewWidget
|
||||
import com.tangem.devkit.ucase.variants.responses.item.TextHeaderItem
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ResponseItemBuilder : ItemWidgetBuilder {
|
||||
override fun build(item: BaseItem, parent: ViewGroup): ViewWidget? {
|
||||
return when (item) {
|
||||
is TextHeaderItem -> ResponseHeaderWidget(parent, item)
|
||||
is TextItem -> ResponseTextWidget(parent, item)
|
||||
is BoolItem -> CheckBoxWidget(parent, item)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.devkit.ucase.variants.responses.ui.widget
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.impl.TextItem
|
||||
import com.tangem.devkit.ucase.variants.responses.item.TextHeaderItem
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ResponseTextWidget(
|
||||
parent: ViewGroup,
|
||||
private val typedItem: TextItem
|
||||
) : ResponseWidget(parent, typedItem) {
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.w_response_item
|
||||
|
||||
private val tvName: TextView = view.findViewById(R.id.tv_name)
|
||||
private val tvValue: TextView = view.findViewById(R.id.tv_value)
|
||||
|
||||
init {
|
||||
initWidgets()
|
||||
}
|
||||
|
||||
private fun initWidgets() {
|
||||
val data = typedItem.getData() as? String
|
||||
tvName.text = getName()
|
||||
tvValue.text = data
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseHeaderWidget(
|
||||
parent: ViewGroup,
|
||||
private val typedItem: TextHeaderItem
|
||||
) : ResponseWidget(parent, typedItem) {
|
||||
override fun getLayoutId(): Int = R.layout.w_response_item_header
|
||||
|
||||
private val tvName: TextView = view.findViewById(R.id.tv_name)
|
||||
|
||||
init {
|
||||
tvName.text = getName()
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.devkit.ucase.variants.responses.ui.widget
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit._arch.structure.abstraction.Item
|
||||
import com.tangem.devkit.ucase.variants.personalize.ui.widgets.DescriptionWidget
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.stringFrom
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.toast
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class ResponseWidget(parent: ViewGroup, item: Item) : DescriptionWidget(parent, item) {
|
||||
|
||||
init {
|
||||
view.setOnClickListener {
|
||||
val data = stringOf(item.getData<Any?>())
|
||||
val clipboard = view.context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
|
||||
?: return@setOnClickListener
|
||||
|
||||
val clipboardData = "${getName()} - $data"
|
||||
val clip: ClipData = ClipData.newPlainText("FieldValue", clipboardData)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
|
||||
val copyMessage = view.stringFrom(R.string.copy_to_clipboard)
|
||||
view.toast("$copyMessage\n$clipboardData")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.devkit.ucase.variants.scan.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.managers.ScanItemsManager
|
||||
import com.tangem.devkit.ucase.ui.BaseCardActionFragment
|
||||
import com.tangem.devkit.ucase.variants.responses.ui.ResponseFragment
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.dpToPx
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ScanActionFragment : BaseCardActionFragment() {
|
||||
|
||||
override val itemsManager: ItemsManager by lazy { ScanItemsManager() }
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val howToUseView = createHowToUse()
|
||||
contentContainer.layoutParams = FrameLayout.LayoutParams(-1, -1, Gravity.CENTER)
|
||||
contentContainer.addView(howToUseView)
|
||||
}
|
||||
|
||||
private fun createHowToUse(): View {
|
||||
val fl = FrameLayout(requireContext())
|
||||
fl.layoutParams = FrameLayout.LayoutParams(-1, -1, Gravity.CENTER)
|
||||
val tv = TextView(requireContext()).apply {
|
||||
val padding = dpToPx(16f).toInt()
|
||||
setPadding(padding, 0, padding, 0)
|
||||
gravity = Gravity.CENTER
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
|
||||
setText(R.string.htu_scan_action)
|
||||
}
|
||||
fl.addView(tv)
|
||||
return fl
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
swrLayout.isEnabled = false
|
||||
actionFab.setOnClickListener { actionVM.invokeMainAction() }
|
||||
}
|
||||
|
||||
override fun handleResponseCardData(card: Card) {
|
||||
super.handleResponseCardData(card)
|
||||
val bundle = ResponseFragment.setTittle(R.string.fg_name_response_scan)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen, bundle, null)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.devkit.ucase.variants.sign.ui
|
||||
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.managers.SignItemsManager
|
||||
import com.tangem.devkit.ucase.ui.BaseCardActionFragment
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SignActionFragment : BaseCardActionFragment() {
|
||||
|
||||
override val itemsManager: ItemsManager by lazy { SignItemsManager() }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue