Updated on 2026-08-14

This commit is contained in:
Tangem 2020-04-23 13:20:46 +03:00
parent 5105528ddc
commit 0131e08b68
201 changed files with 600 additions and 693 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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