Updated on 2026-08-14

This commit is contained in:
Tangem 2020-04-16 10:24:54 +00:00
commit 0b7d4ecdae
71 changed files with 1219 additions and 446 deletions

View file

@ -46,10 +46,11 @@ dependencies {
implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4"
implementation "androidx.navigation:navigation-fragment-ktx:2.2.1"
implementation "androidx.navigation:navigation-ui-ktx:2.2.1"
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha01"
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation "com.google.android.material:material:1.2.0-alpha05"
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.github.gbIxaHue:eu4d:0.3.7'
implementation 'com.github.gbIxaHue:eu4d:0.3.8'
}

View file

@ -20,7 +20,10 @@ fun List<Item>.iterate(func: (Item) -> Unit) {
forEach {
when (it) {
is BaseItem -> func(it)
is ItemGroup -> it.itemList.iterate(func)
is ItemGroup -> {
func(it)
it.itemList.iterate(func)
}
}
}
}

View file

@ -5,7 +5,12 @@ import com.tangem.tangemtest._arch.structure.Id
/**
[REDACTED_AUTHOR]
*/
interface Item {
interface UpdateBy<B>{
fun update(value: B)
}
interface Item: UpdateBy<Item> {
val id: Id
var parent: Item?
var viewModel: ItemViewModel
@ -36,4 +41,7 @@ open class BaseItem(
override var parent: Item? = null
override fun update(value: Item) {
viewModel.update(value.viewModel)
}
}

View file

@ -49,4 +49,8 @@ open class SimpleItemGroup(
ILog.d(this, "clear $id")
itemList.clear()
}
override fun update(value: Item) {
// nothing to do
}
}

View file

@ -16,7 +16,7 @@ class KeyValue(val key: String, val value: Any)
class ViewState(
isVisible: Boolean? = null,
bgColor: Int? = -1
) {
) : UpdateBy<ViewState> {
class State<T>(
stateValue: T,
@ -41,9 +41,15 @@ class ViewState(
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 {
interface ItemViewModel : PayloadHolder, UpdateBy<ItemViewModel> {
val viewState: ViewState
var data: Any?
var defaultData: Any?
@ -92,6 +98,14 @@ open class BaseItemViewModel(
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(

View file

@ -11,29 +11,29 @@ open class TypedItem<D>(id: Id, viewModel: ItemViewModel) : BaseItem(id, viewMod
open fun getTypedData(): D? = viewModel.data as? D
}
class TextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
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))
}
class NumberItem(id: Id, viewModel: ItemViewModel) : TypedItem<Number>(id, viewModel) {
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))
}
class BoolItem(id: Id, viewModel: ItemViewModel) : TypedItem<Boolean>(id, viewModel) {
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))
}
class EditTextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
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))
}
class SpinnerItem(id: Id, viewModel: ListViewModel) : TypedItem<ListViewModel>(id, viewModel) {
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

@ -3,9 +3,9 @@ package com.tangem.tangemtest._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.SwitchCompat
import androidx.appcompat.widget.Toolbar
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
@ -20,7 +20,7 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
*/
class MainActivity : AppCompatActivity() {
private val vm: MainViewModel by viewModels<MainViewModel>()
private val mainVM: MainViewModel by viewModels<MainViewModel>()
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
@ -52,11 +52,24 @@ class MainActivity : AppCompatActivity() {
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_menu, menu)
val switchMenu = menu.findItem(R.id.action_favorite)
(switchMenu.actionView as? SwitchCompat)?.let {
it.setOnCheckedChangeListener { buttonView, isChecked -> vm.switchToggled(isChecked) }
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 super.onCreateOptionsMenu(menu)
return if (result == null) super.onOptionsItemSelected(item) else true
}
}

View file

@ -10,10 +10,12 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
*/
class MainViewModel : ViewModel() {
val ldDescriptionSwitch = MutableLiveData<Boolean>(false)
var descriptionSwitchState = false
var commandResponse: CommandResponse? = null
fun switchToggled(state: Boolean) {
descriptionSwitchState = state
ldDescriptionSwitch.postValue(state)
}

View file

@ -2,16 +2,14 @@ package com.tangem.tangemtest._main.entryPoint
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
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 androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.tangem.tangemtest.R
import com.tangem.tangemtest._main.MainViewModel
import com.tangem.tangemtest.extensions.view.beginDelayedTransition
import com.tangem.tangemtest.ucase.getDefaultNavigationOptions
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
@ -47,7 +45,7 @@ class ActionListFragment : BaseFragment() {
}
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
vhDataWrapper.descriptionIsVisible = it
TransitionManager.beginDelayedTransition(rvActions as ViewGroup, AutoTransition())
rvActions.beginDelayedTransition()
rvActions.adapter?.notifyDataSetChanged()
})
}

View file

@ -59,8 +59,6 @@ class RvActionsVH(
containerDescription.visibility = if (wrapper.descriptionIsVisible) View.VISIBLE else View.GONE
}
override fun onDataBound(data: ActionType) {}
}
fun RecyclerView.ViewHolder.getString(@StringRes id: Int?, ifNull: String = ""): String {

View file

@ -4,6 +4,13 @@ package com.tangem.tangemtest.commons
[REDACTED_AUTHOR]
*/
interface Store<M> {
fun save(config: 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.tangemtest.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,13 @@
package com.tangem.tangemtest.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

@ -37,7 +37,7 @@ class PersonalizeAction : BaseAction() {
val acquirer = DefaultPersonalizationParams.acquirer()
val manufacturer = DefaultPersonalizationParams.manufacturer()
val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig())
val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig.default())
val cardConfig = PersonalizationConfigToCardConfig().convert(personalizeConfig)
attrs.tangemSdk.personalize(cardConfig, issuer, manufacturer, acquirer) {

View file

@ -13,10 +13,10 @@ import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
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? ByteArray ?: return
val hash = dataForHashing.getData() as? String ?: return
val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return
attrs.tangemSdk.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
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)? {

View file

@ -25,6 +25,7 @@ interface ItemsManager : PayloadHolder {
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 {

View file

@ -6,6 +6,7 @@ import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.findItem
import com.tangem.tangemtest._arch.structure.abstraction.iterate
import com.tangem.tangemtest.ucase.domain.actions.Action
import com.tangem.tangemtest.ucase.domain.actions.AttrForAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
@ -47,6 +48,10 @@ open class BaseItemsManager(protected val action: Action) : ItemsManager, Lifecy
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)
}

View file

@ -29,7 +29,7 @@ class PersonalizationItemsManager(
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
val config = converter.convert(itemList, PersonalizationConfig())
val config = converter.convert(itemList, PersonalizationConfig.default())
store.save(config)
}
}

View file

@ -4,6 +4,7 @@ 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.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.findItem
@ -40,7 +41,7 @@ class SignScanConsequence : ItemsChangeConsequence {
EllipticCurve.Ed25519 -> dataForHashing.calculateSha512()
else -> return null
}
hashItem.setData(hashedData)
hashItem.setData(hashedData.toHexString())
}
return affectedItems.toList()
}

View file

@ -3,6 +3,7 @@ package com.tangem.tangemtest.ucase.domain.responses
import com.google.gson.*
import com.tangem.commands.*
import com.tangem.common.extensions.toHexString
import com.tangem.tangemtest.extensions.print
import java.lang.reflect.Type
import java.text.DateFormat
import java.util.*
@ -11,14 +12,17 @@ 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())
registerTypeAdapter(SigningMethodMask::class.java, SigningMethodTypeAdapter())
registerTypeAdapter(SettingsMask::class.java, SettingsMaskTypeAdapter())
registerTypeAdapter(ProductMask::class.java, ProductMaskTypeAdapter())
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()
@ -28,31 +32,41 @@ class ResponseJsonConverter {
fun convertResponse(response: CommandResponse?): String = gson.toJson(response)
}
class ByteTypeAdapter : JsonSerializer<ByteArray> {
class ByteTypeAdapter(
private val fieldConverter: ResponseFieldConverter
) : JsonSerializer<ByteArray> {
override fun serialize(src: ByteArray, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.toHexString())
return JsonPrimitive(fieldConverter.byteArray(src))
}
}
class SettingsMaskTypeAdapter : JsonSerializer<SettingsMask> {
class SettingsMaskTypeAdapter(
private val fieldConverter: ResponseFieldConverter
) : JsonSerializer<SettingsMask> {
override fun serialize(src: SettingsMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
val arrayElement = JsonArray()
Settings.values()
.filter { src.contains(it) }
.forEach { arrayElement.add(it.name) }
return arrayElement
return JsonArray().apply {
fieldConverter.settingsMaskList(src).forEach { add(it) }
}
}
}
class ProductMaskTypeAdapter : JsonSerializer<ProductMask> {
class ProductMaskTypeAdapter(
private val fieldConverter: ResponseFieldConverter
) : JsonSerializer<ProductMask> {
override fun serialize(src: ProductMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.rawValue.toString())
return JsonArray().apply {
fieldConverter.productMaskList(src).forEach { add(it) }
}
}
}
class SigningMethodTypeAdapter : JsonSerializer<SigningMethodMask> {
class SigningMethodTypeAdapter(
private val fieldConverter: ResponseFieldConverter
) : JsonSerializer<SigningMethodMask> {
override fun serialize(src: SigningMethodMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.rawValue.toString())
return JsonArray().apply {
fieldConverter.signingMethodList(src).forEach { add(it) }
}
}
}
@ -61,4 +75,41 @@ class DateTypeAdapter : JsonSerializer<Date> {
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

@ -5,6 +5,8 @@ import com.tangem.tangemtest.ucase.resources.MainResourceHolder
import com.tangem.tangemtest.ucase.resources.Resources
import com.tangem.tangemtest.ucase.variants.responses.CardDataId
import com.tangem.tangemtest.ucase.variants.responses.CardId
import com.tangem.tangemtest.ucase.variants.responses.DepersonalizeId
import com.tangem.tangemtest.ucase.variants.responses.SignId
/**
[REDACTED_AUTHOR]
@ -13,6 +15,8 @@ class ResponseResources {
fun init(holder: MainResourceHolder) {
initCard(holder)
initCardData(holder)
initSignResponse(holder)
initDepersonalizeResponse(holder)
}
private fun initCard(holder: MainResourceHolder) {
@ -26,7 +30,7 @@ class ResponseResources {
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.response_card_signing_method))
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))
@ -50,4 +54,15 @@ class ResponseResources {
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

@ -8,9 +8,11 @@ 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.tangem_sdk_new.extensions.init
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
@ -28,7 +30,8 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
*/
abstract class BaseCardActionFragment : BaseFragment(), ActionView {
protected lateinit var itemContainer: ViewGroup
protected lateinit var swrLayout: SwipeRefreshLayout
protected lateinit var contentContainer: ViewGroup
protected lateinit var actionFab: FloatingActionButton
protected abstract val itemsManager: ItemsManager
@ -38,6 +41,8 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
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")
@ -47,7 +52,7 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
actionVM.setCardManager(TangemSdk.init(requireActivity()))
actionVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView))
initFab()
initViews()
createWidgets {
widgetsWasCreated()
subscribeToViewModelChanges()
@ -57,11 +62,13 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
protected open fun widgetsWasCreated() {}
protected open fun bindViews() {
itemContainer = mainView.findViewById(R.id.ll_container)
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 initFab() {
protected open fun initViews() {
swrLayout.isEnabled = false
enableActionFab(false)
actionFab.setOnClickListener { actionVM.invokeMainAction() }
}
@ -70,7 +77,7 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
Log.d(this, "createWidgets")
actionVM.ldItemList.observe(viewLifecycleOwner, Observer { itemList ->
itemList.forEach { param ->
val widget = ParameterWidget(inflateParamView(itemContainer), param)
val widget = ParameterWidget(inflateParamView(contentContainer), param)
widget.onValueChanged = { id, value -> actionVM.userChangedItem(id, value) }
widget.onActionBtnClickListener = actionVM.getItemAction(param.id)
paramsWidgetList.add(widget)
@ -81,39 +88,46 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
protected open fun subscribeToViewModelChanges() {
Log.d(this, "subscribeToViewModelChanges")
listenResponse()
listenResponseData()
listenResponseCardData()
listenError()
listenChangedItems()
listenDescriptionSwitchChanges()
}
private fun listenResponse() {
actionVM.seResponse.observe(viewLifecycleOwner, Observer {
Log.d(this, "listen response: $it")
mainActivityVM.changeResponseEvent(it)
Log.d(this, "handle response: $it")
handleResponse(it)
})
}
protected open fun listenResponseData() {
actionVM.seResponseData.observe(viewLifecycleOwner, Observer {
Log.d(this, "listen responseData: $it")
navigateTo(R.id.action_nav_card_action_to_response_screen)
Log.d(this, "handle responseData: $it")
handleResponseData(it)
})
}
protected open fun listenResponseCardData() {
actionVM.seResponseCardData.observe(viewLifecycleOwner, Observer {
Log.d(this, "listen responseCardData: $it")
responseCardDataHandled(it)
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 responseCardDataHandled(card: Card?) {}
protected open fun handleResponse(response: CommandResponse) {
mainActivityVM.changeResponseEvent(response)
}
protected open fun listenError() {
actionVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) })
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")
@ -126,12 +140,6 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
})
}
protected open fun listenDescriptionSwitchChanges() {
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
actionVM.toggleDescriptionVisibility(it)
})
}
private fun inflateParamView(where: ViewGroup): ViewGroup {
val inflater = LayoutInflater.from(where.context)
val view = inflater.inflate(R.layout.w_card_incoming_param, where, false)

View file

@ -6,14 +6,13 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.impl.EditTextItem
import com.tangem.tangemtest.extensions.view.beginDelayedTransition
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
import com.tangem.tangemtest.ucase.resources.Resources
@ -73,7 +72,7 @@ class ParameterWidget(
}
fun toggleDescriptionVisibility(state: Boolean) {
TransitionManager.beginDelayedTransition(parent.parent as ViewGroup, AutoTransition())
(parent.parent as ViewGroup).beginDelayedTransition()
descriptionContainer.visibility = if (state) View.VISIBLE else View.GONE
}
@ -95,7 +94,7 @@ class ParameterWidget(
private fun toggleActionBtnVisibility() {
fun switchVisibilityState(newState: Int) {
actionBtnVisibilityState = newState
TransitionManager.beginDelayedTransition(parent, AutoTransition())
parent.beginDelayedTransition()
btnAction.visibility = actionBtnVisibilityState
}
when {

View file

@ -1,6 +1,5 @@
package com.tangem.tangemtest.ucase.variants.depersonalize.ui
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.DepersonalizeItemsManager
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
@ -11,6 +10,4 @@ import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
class DepersonalizeActionFragment : BaseCardActionFragment() {
override val itemsManager: ItemsManager by lazy { DepersonalizeItemsManager() }
override fun getLayoutId(): Int = R.layout.fg_depersonalize
}

View file

@ -5,22 +5,49 @@ import android.content.SharedPreferences
import androidx.core.content.edit
import com.google.gson.Gson
import com.tangem.tangemtest.AppTangemDemo
import com.tangem.tangemtest.commons.KeyedStore
import com.tangem.tangemtest.commons.Store
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig
class PersonalizationConfigStore(context: Context) : Store<PersonalizationConfig> {
class PersonalizationConfigStore(context: Context) : Store<PersonalizationConfig>, KeyedStore<PersonalizationConfig> {
private val key = "personalization_config"
companion object {
val defaultKey = "default"
}
private val sp: SharedPreferences = (context.applicationContext as AppTangemDemo).sharedPreferences()
private val sharedPreferencesKey = "personalization_presets"
private val sp: SharedPreferences = (context.applicationContext as AppTangemDemo).sharedPreferences(sharedPreferencesKey)
private val gson: Gson = Gson()
override fun save(config: PersonalizationConfig) {
sp.edit(true) { putString(key, gson.toJson(config)) }
override fun save(value: PersonalizationConfig) {
save(defaultKey, value)
}
override fun restore(): PersonalizationConfig {
val json = sp.getString(key, gson.toJson(PersonalizationConfig()))
return gson.fromJson(json, PersonalizationConfig::class.java)
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

@ -19,7 +19,7 @@ class ConfigValuesHolder : BaseTypedHolder<Id, Value>() {
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(""))
register(Common.BlockchainCustom, Value(default.blockchainCustom))
register(Common.MaxSignatures, Value(default.MaxSignatures))
register(Common.CreateWallet, Value(default.createWallet))
register(SigningMethod.SignTx, Value(default.SigningMethod0))
@ -66,7 +66,7 @@ class ConfigValuesHolder : BaseTypedHolder<Id, Value>() {
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.aar))
register(SettingsMaskNdef.AarCustom, Value(default.aarCustom))
register(SettingsMaskNdef.Uri, Value(default.uri))
register(Pins.Pin, Value(default.PIN))
register(Pins.Pin2, Value(default.PIN2))

View file

@ -52,6 +52,7 @@ class ItemsToPersonalizationConfig : ItemsToModel<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)

View file

@ -1,25 +1,26 @@
package com.tangem.tangemtest.ucase.variants.personalize.dto
import com.tangem.commands.EllipticCurve
/**
[REDACTED_AUTHOR]
*/
class PersonalizationConfig {
// Card number
var series = "BB"
var startNumber: Long = 300000000000
var batchId = "ffff"
var series = ""
var startNumber: Long = 0
var batchId = ""
// Common
var curveID = "ed25519"
var blockchain = "BTC/test"
var curveID = ""
var blockchain = ""
var blockchainCustom = ""
var MaxSignatures: Long = 999999
var createWallet = true
var MaxSignatures: Long = 0
var createWallet = false
// Signing method
var SigningMethod0 = true
var SigningMethod0 = false
var SigningMethod1 = false
var SigningMethod2 = false
var SigningMethod3 = false
@ -27,19 +28,16 @@ class PersonalizationConfig {
var SigningMethod5 = false
var SigningMethod6 = false
// Sign hash external properties
var pinLessFloorLimit: Long = 100000
var hexCrExKey = "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF"
var pinLessFloorLimit: Long = 0
var hexCrExKey = ""
var requireTerminalTxSignature = false
var requireTerminalCertSignature = false
var checkPIN3onCard = true
var checkPIN3onCard = false
// Denomination
var writeOnPersonalization = false
var denomination: Long = 1000000
var denomination: Long = 0
// Token
var itsToken = false
@ -47,49 +45,125 @@ class PersonalizationConfig {
var contractAddress = ""
var decimal: Long = 0
var cardData = CardData()
// Settings mask
var isReusable = true
var isReusable = false
var useActivation = false
var forbidPurgeWallet = false
var allowSelectBlockchain = false
var useBlock = false
var oneApdu = false
var useCVC = false
var allowSwapPIN = true
var allowSwapPIN2 = true
var allowSwapPIN = false
var allowSwapPIN2 = false
var forbidDefaultPIN = false
var smartSecurityDelay = true
var protectIssuerDataAgainstReplay = true
var skipSecurityDelayIfValidatedByIssuer = true
var skipCheckPIN2andCVCIfValidatedByIssuer = true
var skipSecurityDelayIfValidatedByLinkedTerminal = true
var smartSecurityDelay = false
var protectIssuerDataAgainstReplay = false
var skipSecurityDelayIfValidatedByIssuer = false
var skipCheckPIN2andCVCIfValidatedByIssuer = false
var skipSecurityDelayIfValidatedByLinkedTerminal = false
var restrictOverwriteIssuerDataEx = false
// Settings mask - protocol encryption
var protocolAllowUnencrypted = true
var protocolAllowStaticEncryption = true
var protocolAllowUnencrypted = false
var protocolAllowStaticEncryption = false
var useNDEF = true
var useDynamicNDEF = true
var useNDEF = false
var useDynamicNDEF = false
var disablePrecomputedNDEF = false
var aar = "com.tangem.wallet"
var aar = ""
var aarCustom = ""
var uri = "https://tangem.com"
var uri = ""
// Pins
var PIN = "000000"
var PIN2 = "000"
var PIN = ""
var PIN2 = ""
var PIN3 = ""
var CVC = "000"
var pauseBeforePIN2: Long = 5000L
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 {

View file

@ -1,18 +1,30 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
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.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.StringId
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.SafeValueChanged
import com.tangem.tangemtest._arch.widget.WidgetBuilder
import com.tangem.tangemtest.commons.DialogController
import com.tangem.tangemtest.commons.view.MultiActionView
import com.tangem.tangemtest.commons.view.ViewAction
import com.tangem.tangemtest.extensions.view.beginDelayedTransition
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.PersonalizationItemsManager
@ -21,28 +33,80 @@ import com.tangem.tangemtest.ucase.tunnel.ActionView
import com.tangem.tangemtest.ucase.tunnel.ItemError
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
import com.tangem.tangemtest.ucase.variants.personalize.PersonalizationConfigStore
import com.tangem.tangemtest.ucase.variants.personalize.ui.presets.PersonalizationPresetManager
import com.tangem.tangemtest.ucase.variants.personalize.ui.presets.PersonalizationPresetView
import com.tangem.tangemtest.ucase.variants.personalize.ui.presets.RvPresetNamesAdapter
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.PersonalizationItemBuilder
import com.tangem.tangemtest.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() {
class PersonalizationFragment : BaseCardActionFragment(), PersonalizationPresetView {
override val itemsManager: ItemsManager by lazy { PersonalizationItemsManager(PersonalizationConfigStore(requireContext())) }
override fun getLayoutId(): Int = R.layout.fg_personalization
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 = itemContainer.inflate<ViewGroup>(R.layout.view_simple_button)
val btnContainer = contentContainer.inflate<ViewGroup>(R.layout.view_simple_button)
val btn = btnContainer.findViewById<Button>(R.id.button)
val show = StringId("show")
@ -55,28 +119,30 @@ class PersonalizationFragment : BaseCardActionFragment() {
multiAction.state = if (it == show) hide else show
}
multiAction.performAction(hide)
itemContainer.addView(btnContainer)
contentContainer.addView(btnContainer)
}
override fun initFab() {
actionFab.setOnClickListener { actionVM.invokeMainAction() }
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 createWidgets(widgetCreatedCallback: () -> Unit) {
Log.d(this, "createWidgets")
val itemList = mutableListOf<Item>()
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_fg_peronalization, menu)
super.onCreateOptionsMenu(menu, inflater)
}
actionVM.ldItemList.observe(viewLifecycleOwner, Observer { list ->
Log.d(this, "ldBlockList size: ${list.size}")
itemList.clear()
itemList.addAll(list)
itemList.forEach { WidgetBuilder(PersonalizationItemBuilder()).build(it, itemContainer) }
actionVM.attachToPayload(mutableMapOf(
PayloadKey.actionView to this as ActionView,
PayloadKey.itemList to itemList
))
widgetCreatedCallback()
})
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)?) {
@ -89,8 +155,51 @@ class PersonalizationFragment : BaseCardActionFragment() {
}
}
override fun responseCardDataHandled(card: Card?) {
super.responseCardDataHandled(card)
navigateTo(R.id.action_nav_card_action_to_response_screen)
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.tangemtest.ucase.variants.personalize.ui.presets
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.variants.personalize.PersonalizationConfigStore
import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizationConfigConverter
import com.tangem.tangemtest.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.tangemtest.ucase.variants.personalize.ui.presets
import com.tangem.tangemtest._arch.structure.abstraction.SafeValueChanged
import com.tangem.tangemtest.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,47 @@
package com.tangem.tangemtest.ucase.variants.personalize.ui.presets
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import com.tangem.tangemtest.R
import com.tangem.tangemtest._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

@ -2,11 +2,10 @@ package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
import android.view.ViewGroup
import android.widget.TextView
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ViewState
import com.tangem.tangemtest.extensions.view.beginDelayedTransition
/**
[REDACTED_AUTHOR]
@ -31,7 +30,7 @@ abstract class DescriptionWidget(
if (description.isEmpty()) return
tv.text = description
TransitionManager.beginDelayedTransition(view.parent as ViewGroup, AutoTransition())
(view.parent as ViewGroup).beginDelayedTransition()
descriptionContainer.visibility = state
}
}

View file

@ -12,7 +12,7 @@ import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
class PersonalizationItemBuilder : ItemWidgetBuilder {
override fun build(item: BaseItem, parent: ViewGroup): ViewWidget? {
return when (item) {
is TextItem -> GroupTitleWidget(parent, item)
is TextItem -> TextHeaderWidget(parent, item)
is EditTextItem -> EditTextWidget(parent, item)
is NumberItem -> NumberWidget(parent, item)
is BoolItem -> SwitchWidget(parent, item)

View file

@ -46,9 +46,8 @@ class SpinnerWidget(
}
spinner.onItemSelectedListener = onItemSelectedListener
item.viewModel.onDataUpdated = {
val selectedItem = it as? String
spinner.onItemSelectedListener = null
viewModel.itemList.firstOrNull { item -> item.value == selectedItem }?.let { keyValue ->
viewModel.itemList.firstOrNull { item -> item.value == it }?.let { keyValue ->
val position = viewModel.itemList.indexOf(keyValue)
spinner.setSelection(position)
}

View file

@ -8,8 +8,8 @@ import com.tangem.tangemtest._arch.structure.impl.TextItem
/**
[REDACTED_AUTHOR]
*/
class GroupTitleWidget(parent: ViewGroup, data: TextItem) : DescriptionWidget(parent, data) {
override fun getLayoutId(): Int = R.layout.w_personalize_item_text
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)

View file

@ -29,6 +29,7 @@ enum class CardId : ResponseId {
paymentFlowVersion,
userCounter,
userProtectedCounter,
empty
}
enum class CardDataId : ResponseId {
@ -41,4 +42,15 @@ enum class CardDataId : ResponseId {
tokenSymbol,
tokenContractAddress,
tokenDecimal,
}
enum class SignId : ResponseId {
cid,
walletSignedHashes,
walletRemainingSignatures,
signature,
}
enum class DepersonalizeId: ResponseId {
isSuccess
}

View file

@ -0,0 +1,27 @@
package com.tangem.tangemtest.ucase.variants.responses.converter
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.*
import com.tangem.tangemtest.ucase.domain.responses.ResponseFieldConverter
import com.tangem.tangemtest.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

@ -1,33 +1,27 @@
package com.tangem.tangemtest.ucase.variants.responses.converter
import com.tangem.commands.Card
import com.tangem.commands.CardData
import com.tangem.commands.Settings
import com.tangem.commands.SettingsMask
import com.tangem.common.extensions.toHexString
import com.tangem.commands.*
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.StringId
import com.tangem.tangemtest._arch.structure.abstraction.*
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ItemGroup
import com.tangem.tangemtest._arch.structure.abstraction.iterate
import com.tangem.tangemtest._arch.structure.impl.BoolItem
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest.ucase.variants.personalize.BlockId
import com.tangem.tangemtest.ucase.variants.responses.CardDataId
import com.tangem.tangemtest.ucase.variants.responses.CardId
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
/**
[REDACTED_AUTHOR]
*/
class CardConverter : ModelToItems<Card> {
class CardConverter : BaseResponseConverter<Card>() {
override fun convert(from: Card): List<Item> {
val itemList = mutableListOf<Item>()
// val holder = GsonInitializer()
// itemList.add(TextItem(Additional.JSON_INCOMING, holder.gson.toJson(from)))
itemList.add(simpleFields(from))
itemList.add(cardData(from.cardData))
itemList.add(settingsMask(from.settingsMask))
commonGroup(itemList, from)
cardDataGroup(itemList, from.cardData)
settingsMaskGroup(itemList, from.settingsMask)
hideEmptyNullFields(itemList)
return itemList
@ -35,71 +29,66 @@ class CardConverter : ModelToItems<Card> {
private fun hideEmptyNullFields(itemList: MutableList<Item>) {
itemList.iterate {
val data = it.getData<Any?>()
val isHidden = if (data == null) true
else when (data) {
is String -> data.isEmpty()
else -> false
}
if (it is ItemGroup) return@iterate
if (isHidden) it.viewModel.viewState.isVisibleState.value = false
if (it.getData<Any?>() == null) it.viewModel.viewState.isVisibleState.value = false
}
}
private fun simpleFields(from: Card): Item {
val group = createGroup(BlockId.Common)
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, stringOf(from.status)))
group.addItem(TextItem(CardId.status, valueToString(from.status)))
group.addItem(TextItem(CardId.firmwareVersion, from.firmwareVersion))
group.addItem(TextItem(CardId.cardPublicKey, from.cardPublicKey?.toHexString()))
group.addItem(TextItem(CardId.issuerPublicKey, from.issuerPublicKey?.toHexString()))
group.addItem(TextItem(CardId.curve, stringOf(from.curve)))
group.addItem(TextItem(CardId.maxSignatures, stringOf(from.maxSignatures)))
group.addItem(TextItem(CardId.signingMethod, stringOf(from.signingMethods?.rawValue)))
group.addItem(TextItem(CardId.pauseBeforePin2, stringOf(from.pauseBeforePin2)))
group.addItem(TextItem(CardId.walletPublicKey, stringOf(from.walletPublicKey)))
group.addItem(TextItem(CardId.walletRemainingSignatures, stringOf(from.walletRemainingSignatures)))
group.addItem(TextItem(CardId.walletSignedHashes, stringOf(from.walletSignedHashes)))
group.addItem(TextItem(CardId.health, stringOf(from.health)))
group.addItem(TextItem(CardId.isActivated, stringOf(from.isActivated)))
group.addItem(TextItem(CardId.activationSeed, stringOf(from.activationSeed)))
group.addItem(TextItem(CardId.paymentFlowVersion, stringOf(from.paymentFlowVersion)))
group.addItem(TextItem(CardId.userCounter, stringOf(from.userCounter)))
// block.addItem(TextItem(CardId.UserProtectedCounter, stringOf(from.userProtectedCounter)))
return group
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 cardData(from: CardData?): Item {
val group = createGroup(BlockId.Common, R.color.group_card_data)
val data = from ?: return group
private fun cardDataGroup(itemList: MutableList<Item>, cardData: CardData?) {
val data = cardData ?: return
// group.addItem(TextItem(StringResId(R.string.response_card_card_data)))
val group = createGroup(CardId.cardData, R.color.group_card_data)
itemList.add(group)
group.addItem(TextItem(CardDataId.batchId, data.batchId))
// Format: Year (2 bytes) | Month (1 byte) | Day (1 byte)
group.addItem(TextItem(CardDataId.manufactureDateTime, stringOf(data.manufactureDateTime)))
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, data.manufacturerSignature?.toHexString()))
group.addItem(TextItem(CardDataId.productMask, stringOf(data.productMask?.rawValue)))
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))
return group
val productMask = data.productMask ?: return
group.addItem(TextHeaderItem(CardDataId.productMask, ""))
Product.values().forEach { group.addItem(BoolItem(StringId(it.name), productMask.contains(it))) }
}
private fun settingsMask(from: SettingsMask?): Item {
val group = createGroup(CardId.settingsMask, R.color.group_signing_method)
val data = from ?: return group
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)
// group.addItem(TextItem(StringResId(R.string.response_card_settings_mask)))
Settings.values().forEach { group.addItem(BoolItem(StringId(it.name), data.contains(it))) }
return group
}
private fun createGroup(id: Id, colorId: Int? = null): ItemGroup {
return if (colorId == null) SimpleItemGroup(id)
else SimpleItemGroup(id, BaseItemViewModel(viewState = ViewState(bgColor = colorId)))
}
}

View file

@ -1,36 +1,30 @@
package com.tangem.tangemtest.ucase.variants.responses.converter
import com.tangem.commands.Card
import com.tangem.commands.SignResponse
import com.tangem.commands.personalization.DepersonalizeResponse
import com.tangem.common.CompletionResult
import com.tangem.tangemtest._arch.structure.StringId
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ModelToItems
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest.ucase.variants.responses.DepersonalizeId
import com.tangem.tangemtest.ucase.variants.responses.SignId
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
class ReadEventConverter : ModelToItems<CompletionResult.Success<Card>> {
override fun convert(from: CompletionResult.Success<Card>): List<Item> = CardConverter().convert(from.data)
}
class SignResponseConverter : ModelToItems<SignResponse> {
class SignResponseConverter : BaseResponseConverter<SignResponse>() {
override fun convert(from: SignResponse): List<Item> {
return listOf(
TextItem(StringId("CID"), from.cardId),
TextItem(StringId("Wallet signed hashes"), stringOf(from.walletSignedHashes)),
TextItem(StringId("Wallet remaining signatures"), stringOf(from.walletRemainingSignatures)),
TextItem(StringId("Signature"), stringOf(from.signature))
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 : ModelToItems<DepersonalizeResponse> {
class DepersonalizeResponseConverter : BaseResponseConverter<DepersonalizeResponse>() {
override fun convert(from: DepersonalizeResponse): List<Item> {
return listOf(TextItem(StringId("Is success"), stringOf(from.success)))
return listOf(TextItem(DepersonalizeId.isSuccess, stringOf(from.success)))
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.tangemtest.ucase.variants.responses.item
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel
import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel
import com.tangem.tangemtest._arch.structure.abstraction.ViewState
import com.tangem.tangemtest._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

@ -3,6 +3,8 @@ package com.tangem.tangemtest.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
@ -23,24 +25,25 @@ 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_container) }
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 = selfVM.determineTitleId(mainActivityVM.commandResponse)
val titleId = getTittleId(arguments) ?: selfVM.determineTitleId(mainActivityVM.commandResponse)
activity?.setTitle(titleId)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
buildWidgets()
listenDescriptionSwitchChanges()
}
@ -58,10 +61,8 @@ open class ResponseFragment : BaseFragment() {
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_fg_response, menu)
super.onCreateOptionsMenu(menu, inflater)
val menuItem = menu.findItem(R.id.action_share)
menuItem.isVisible = true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -72,4 +73,12 @@ open class ResponseFragment : BaseFragment() {
}
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

@ -6,6 +6,7 @@ import com.tangem.tangemtest._arch.structure.impl.BoolItem
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest._arch.widget.ItemWidgetBuilder
import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
/**
[REDACTED_AUTHOR]
@ -13,6 +14,7 @@ import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
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

@ -4,6 +4,7 @@ import android.view.ViewGroup
import android.widget.TextView
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tangemtest.ucase.variants.responses.item.TextHeaderItem
/**
[REDACTED_AUTHOR]
@ -27,4 +28,18 @@ class ResponseTextWidget(
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

@ -14,7 +14,7 @@ import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
abstract class ResponseWidget(parent: ViewGroup, item: Item): DescriptionWidget(parent, item) {
abstract class ResponseWidget(parent: ViewGroup, item: Item) : DescriptionWidget(parent, item) {
init {
view.setOnClickListener {

View file

@ -1,10 +1,18 @@
package com.tangem.tangemtest.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.tangemtest.R
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.ScanItemsManager
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
import com.tangem.tangemtest.ucase.variants.responses.ui.ResponseFragment
import ru.dev.gbixahue.eu4d.lib.android._android.views.dpToPx
/**
[REDACTED_AUTHOR]
@ -13,14 +21,36 @@ class ScanActionFragment : BaseCardActionFragment() {
override val itemsManager: ItemsManager by lazy { ScanItemsManager() }
override fun getLayoutId(): Int = R.layout.fg_action_card_scan
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun initFab() {
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 responseCardDataHandled(card: Card?) {
super.responseCardDataHandled(card)
navigateTo(R.id.action_nav_card_action_to_response_screen)
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

@ -1,6 +1,5 @@
package com.tangem.tangemtest.ucase.variants.sign.ui
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.SignItemsManager
import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
@ -11,6 +10,4 @@ import com.tangem.tangemtest.ucase.ui.BaseCardActionFragment
class SignActionFragment : BaseCardActionFragment() {
override val itemsManager: ItemsManager by lazy { SignItemsManager() }
override fun getLayoutId(): Int = R.layout.fg_action_card_sign
}

View file

@ -10,7 +10,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"/>
<fragment
android:id="@+id/nav_host_fragment"

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="@dimen/def_indent"
android:paddingBottom="@dimen/def_half_indent">
<include
layout="@layout/m_divider_h"
android:layout_width="match_parent"
android:layout_height="2dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/def_indent"
android:layout_marginEnd="@dimen/def_indent" />
</LinearLayout>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_item"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
app:boxBackgroundColor="@android:color/transparent"
android:hint="Enter a preset name"
tools:hint="Field name">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Some text field" />
</com.google.android.material.textfield.TextInputLayout>
</FrameLayout>

View file

@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
<include layout="@layout/w_action_response_json" />
</LinearLayout>
</ScrollView>
<TextView
android:id="@+id/tv_screen_stub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:textSize="18sp"
android:layout_margin="@dimen/def_double_indent"
android:text="@string/empty_screen_stub" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/def_indent"
android:layout_marginBottom="@dimen/def_double_indent"
android:src="@drawable/ic_nfc" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
<include layout="@layout/w_action_response_json" />
</LinearLayout>
</ScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/def_indent"
android:layout_marginBottom="@dimen/def_double_indent"
android:src="@drawable/ic_nfc" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -5,29 +5,17 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
<include layout="@layout/v_content_container" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/def_indent"
android:layout_marginBottom="@dimen/def_double_indent"
android:src="@drawable/ic_nfc"
app:layout_anchor="@id/scroll_view"
app:layout_anchor="@id/swr_layout"
app:layout_anchorGravity="bottom|end" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -10,7 +10,7 @@
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_container"
android:id="@+id/ll_content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
<include layout="@layout/w_action_response_json" />
</LinearLayout>
</ScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/def_indent"
android:layout_marginBottom="@dimen/def_double_indent"
android:src="@drawable/ic_nfc" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swr_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_items"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/item_header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<LinearLayout
android:id="@+id/item_list_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<LinearLayout
android:id="@+id/item_footer_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</LinearLayout>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_name"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_delete"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:hint="Field name" />
<ImageView
android:id="@+id/btn_delete"
android:layout_width="26dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@android:drawable/ic_menu_delete" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/delimiter"
android:background="@color/field_header_background"
android:minHeight="@dimen/iw_layout_frame_header_min_height"
android:paddingTop="6dp"
android:paddingBottom="6dp">

View file

@ -4,7 +4,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/w_personalize_item_text" />
<include layout="@layout/w_personalize_item_header" />
<include layout="@layout/w_personalize_item_edit_text" />

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/field_header_background">
<LinearLayout
android:id="@+id/container_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/def_indent"
android:paddingBottom="8dp">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/field_content"
android:textSize="16sp"
tools:text="Custom blockchain" />
<include layout="@layout/w_field_description" />
</LinearLayout>
<include
layout="@layout/m_divider_h"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom" />
</FrameLayout>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_toggle_description_visibility"
android:checkable="true"
android:title="@string/menu_main_description"
app:showAsAction="never" />
</menu>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/menu_group_personalization_preset">
<item
android:id="@+id/action_reset"
android:title="@string/menu_personalization_preset_reset" />
<item
android:id="@+id/action_save"
android:title="@string/menu_personalization_preset_save" />
<item
android:id="@+id/action_load"
android:title="@string/menu_personalization_preset_load" />
</group>
</menu>

View file

@ -5,14 +5,7 @@
<item
android:id="@+id/action_share"
android:icon="@drawable/ic_share_white_18dp"
android:title="Share"
android:visible="false"
app:showAsAction="always" />
<item
android:id="@+id/action_favorite"
android:title=""
app:actionLayout="@layout/menu_item_switch"
app:showAsAction="always" />
android:title="@string/menu_response_share"
app:showAsAction="ifRoom" />
</menu>

View file

@ -6,7 +6,13 @@
<action
android:id="@+id/action_nav_card_action_to_response_screen"
app:destination="@+id/nav_card_response" />
app:destination="@+id/nav_card_response"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:popUpTo="@id/nav_entry_point"
app:popUpToInclusive="false" />
<fragment
android:id="@+id/nav_entry_point"
@ -57,25 +63,25 @@
android:id="@+id/nav_scan"
android:name="com.tangem.tangemtest.ucase.variants.scan.ui.ScanActionFragment"
android:label="@string/action_card_scan"
tools:layout="@layout/fg_action_card_scan" />
tools:layout="@layout/fg_base_action_layout" />
<fragment
android:id="@+id/nav_sign"
android:name="com.tangem.tangemtest.ucase.variants.sign.ui.SignActionFragment"
android:label="@string/action_card_sign"
tools:layout="@layout/fg_action_card_sign" />
tools:layout="@layout/fg_base_action_layout" />
<fragment
android:id="@+id/nav_personalize"
android:name="com.tangem.tangemtest.ucase.variants.personalize.ui.PersonalizationFragment"
android:label="@string/action_personalize"
tools:layout="@layout/fg_personalization" />
tools:layout="@layout/fg_base_action_layout" />
<fragment
android:id="@+id/nav_depersonalize"
android:name="com.tangem.tangemtest.ucase.variants.depersonalize.ui.DepersonalizeActionFragment"
android:label="@string/action_depersonalize"
tools:layout="@layout/fg_depersonalize" />
tools:layout="@layout/fg_base_action_layout" />
<fragment
android:id="@+id/nav_issuer_read_data"

View file

@ -9,12 +9,13 @@
<color name="action_name">#2B2B2B</color>
<color name="field_header_background">#C3C3C3</color>
<color name="field_info">#888888</color>
<color name="field_content">#303030</color>
<color name="field_description">@color/field_info</color>
<color name="group_card_data">#D7E4F3</color>
<color name="group_signing_method">#D7F3E6</color>
<color name="group_settings_mask">#D7F3E6</color>
<color name="switchTrack">#C6C6C6</color>

View file

@ -1,14 +1,25 @@
<resources>
<string name="app_name">Tangem Development Kit</string>
<string name="menu_main_description">Description</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="btn_delete">Delete</string>
<string name="btn_ok">OK</string>
<string name="btn_cancel">Cancel</string>
<string name="btn_save">Save</string>
<string name="btn_load">Load</string>
<string name="unknown">unknown</string>
<string name="error_nothing_to_load">Nothing to load</string>
<string name="fg_name_entry_point">@string/app_name</string>
<string name="fg_name_response_scan">Scan response</string>
<string name="fg_name_response_scan">Read response</string>
<string name="fg_name_response_sign">Sign response</string>
<string name="fg_name_response_personalization">Personalization response</string>
<string name="fg_name_response_depersonalization">Depersonalization response</string>
<string name="unknown">unknown</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="switch_description">Docs</string>
<string name="card_error_not_personalized">Your card hasn\'t been personalized yet. You need to run personalize command first.</string>
@ -18,7 +29,7 @@
<string name="stub">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit</string>
<!-- <string name="stub">Sed ut perspiciatis unde omnis iste natus</string>-->
<string name="show_rare_fields">Show rarely used fields</string>
<string name="hide_rare_fields">Hide</string>
<string name="show_rare_fields">Show all fields</string>
<string name="hide_rare_fields">Hide fields</string>
</resources>

View file

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="menu_personalization_preset_reset">Reset to default</string>
<string name="menu_personalization_preset_save">Save configuration</string>
<string name="menu_personalization_preset_load">Load configuration</string>
<string name="personalize">Personalize</string>
<string name="depersonalize">Depersonalize</string>
<string name="pers_block_card_number">Card number</string>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="menu_response_share">Share</string>
<!-- Field names - Response: Card -->
<string name="response_card_cid">CID</string>
@ -56,7 +57,7 @@
<string name="info_response_card_status">Current status of the card</string>
<string name="info_response_card_firmware_version">Version of Tangem COS</string>
<string name="info_response_card_public_key">Public key that is used to authenticate the card against manufacturers database. It is generated one time during card manufacturing. See Security section for more details.</string>
<string name="info_response_card_settings_mask">Card settings defined by personalization (bit mask: 0 Enabled, 1 Disabled):</string>
<string name="info_response_card_settings_mask">Card settings defined by personalization.</string>
<string name="info_response_card_is_reusable">Defines what happens when user calls PURGE_WALLET command:
\n0 - Card will switch to Purged state
\n1 - Card will switch to Empty state and let create a new wallet again</string>
@ -71,29 +72,29 @@
<string name="info_response_card_one_apdu_at_time">Card will execute only one command during one communication session, thus requiring user to physically take the card away from the host after each action (all commands except for READ_CARD).</string>
<string name="info_response_card_use_ndef">Whether the card should emulate NDEF. In default configuration, two NDEF records are loaded during personalization: (1) Tangem web site address, (2) name of Android App package in Google Play Store.</string>
<string name="info_response_card_use_dynamic_ndef">0 Disable dynamic generation of NDEF for iOS. See Dynamic NDEF section for more details.
/n1 Enable dynamic NDEF for iOS.</string>
\n1 Enable dynamic NDEF for iOS.</string>
<string name="info_response_card_smart_security_delay">Security delay Pause_Before_PIN2 will not be applied if PIN2 is not default.</string>
<string name="info_response_card_allow_unencrypted">Whether the card supports unencrypted NFC communication. See NFC communication section for more details.</string>
<string name="info_response_card_allow_fast_encryption">Whether the card supports fast encrypted NFC communication. See NFC communication section for more details.</string>
<string name="info_response_card_protect_issuer_data_against_replay">0 No replay protection on write issuer data
/n1 Enable replay protection on write issuer data (card will require additional Issuer_Data_Counter incremented on each write)</string>
\n1 Enable replay protection on write issuer data (card will require additional Issuer_Data_Counter incremented on each write)</string>
<string name="info_response_card_allow_select_blockchain">0 Wallet elliptic curve and blockchain information stored during PERSONALIZE command and never change
/n1 Wallet elliptic curve and blockchain information can be changed on CREATE_WALLET command</string>
\n1 Wallet elliptic curve and blockchain information can be changed on CREATE_WALLET command</string>
<string name="info_response_card_disable_precomputed_ndef">0 Enable precomputed dynamic NDEF to work around iPhone 7+ NFC bug.
/n1 Disable precomputed dynamic NDEF. See Dynamic NDEF section for more details.</string>
\n1 Disable precomputed dynamic NDEF. See Dynamic NDEF section for more details.</string>
<string name="info_response_card_security_delay_if_validated">0 Enforce security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).
/n1 Skip security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).</string>
\n1 Skip security delay in SIGN command if the issuer validates the transaction (for signing methods 2, 3, 4 and 5).</string>
<string name="info_response_card_skip_pin2_cvc_if_validated_by_issuer">0 Require and check PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).
/n1 Skip checking PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).</string>
\n1 Skip checking PIN2 and CVC in SIGN command if the issuer validates the transaction (for signing method 2, 3, 4 and 5).</string>
<string name="info_response_card_skip_security_delay_if_validated_by_linked_terminal">1 - Store Terminal_PublicKey public key of linked terminal no each SIGN command, skip security delay if valid signature of transaction is made with Terminal_PrivateKey is provided in SIGN command</string>
<string name="info_response_card_restrict_overwrite_issuer_ex_data"></string>
<string name="info_info_response_card_prohibit_overwriting_issuer_ex_data"></string>
<string name="info_response_card_require_terminal_tx_sig">0 Skip checking terminals signature when signing POS transaction
/n1 Check terminals signature when signing POS transaction</string>
\n1 Check terminals signature when signing POS transaction</string>
<string name="info_response_card_require_terminal_cert_sig">0 Skip checking acquirers signature of terminal certificate when signing POS transaction
/n1 Check acquirers signature of terminal certificate when signing POS transaction</string>
\n1 Check acquirers signature of terminal certificate when signing POS transaction</string>
<string name="info_response_card_check_pin3">0 Additionally encrypt POS transaction signature with key derived from PIN3 when the transaction amount exceeds PIN3 floor limit
/n1 Require terminal to send PIN3 to card when the POS transaction amount exceeds PIN3_Floor_Limit</string>
\n1 Require terminal to send PIN3 to card when the POS transaction amount exceeds PIN3_Floor_Limit</string>
<string name="info_response_card_card_data">Detailed information about card contents. Format is defined by the card issuer. Cards complaint with Tangem Wallet application should have TLV format described in Personalization section.</string>
<string name="info_response_card_issuer_data_public_key">Public key that is used by the card issuer to sign Issuer_Data field. See Security section for more details.</string>
<string name="info_response_card_curve">Explicit text name of the elliptic curve used for all wallet key operations.</string>
@ -105,12 +106,12 @@
<string name="info_response_card_wallet_signed_hashes">Total number of signed single hashes returned by the card in SIGN command responses since card personalization. Sums up array elements within all SIGN commands.</string>
<string name="info_response_card_health">Any non-zero value indicates that the card experiences some hardware problems. User should withdraw the value to other blockchain wallet as soon as possible. Non-zero Health tag will also appear in responses of all other commands.</string>
<string name="info_response_card_is_activated">Whether the card requires issuers confirmation of activation.
/n0 card will require issuers confirmation of activation,
/notherwise this field will not be returned (card is activated and operational).</string>
\n0 card will require issuers confirmation of activation,
\notherwise this field will not be returned (card is activated and operational).</string>
<string name="info_response_card_activation_seed">A random challenge generated by PERSONALIZE command that should be signed and returned to COS by the issuer to confirm the card has been activated. See ACTIVATE_CARD command for more details.
/nThis field will not be returned if the card is activated.</string>
\nThis field will not be returned if the card is activated.</string>
<string name="info_response_card_payment_flow_version">Version of POS payment scheme supported by COS ([0x02,0x01] for version 2.30)
/nReturned only if SigningMethod 6 enabling POS transactions is supported by card.</string>
\nReturned only if SigningMethod 6 enabling POS transactions is supported by card.</string>
<string name="info_response_card_user_counter">This value can be initialized by App and will be increased by COS with the execution of each SIGN command. For example, this field can store blockchain “nonce” for a quick one-touch transaction on POS terminals. Returned only if SigningMethod =6.</string>
<string name="info_response_card_user_protected_counter">This value can be initialized by App (with PIN2 confirmation) and will be increased by COS with the execution of each SIGN command. For example, this field can store blockchain “nonce” for a quick one-touch transaction on POS terminals. Returned only if SigningMethod =6.</string>
@ -135,4 +136,23 @@
<string name="info_response_card_card_data_token_symbol"></string>
<string name="info_response_card_card_data_token_contract_address"></string>
<string name="info_response_card_card_data_token_decimal"></string>
<!-- Field names - Response: Sign -->
<string name="response_sign_cid">CID</string>
<string name="response_sign_wallet_signed_hashes">Wallet signed hashes</string>
<string name="response_sign_wallet_remaining_signatures">Wallet remaining signatures</string>
<string name="response_sign_signature">Signature</string>
<string name="info_response_sign_cid">@string/info_response_card_cid</string>
<string name="info_response_sign_wallet_signed_hashes">Total number of signed single hashes returned by the card in SIGN command responses since card personalization. Sums up array elements within all SIGN commands.</string>
<string name="info_response_sign_wallet_remaining_signatures">Remaining number of SIGN operations before the wallet will stop signing transactions.</string>
<string name="info_response_sign_signature">Array of resulting signatures that App should embed into a raw transaction according to a transaction format of an appropriate blockchain.</string>
<!-- Field names - Response: Depersonalize -->
<string name="response_depersonalize_is_success">Is success</string>
<string name="info_response_depersonalize_is_success">Is success</string>
</resources>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="empty_screen_stub">To read the card, press the button at the right bottom corner of the screen</string>
<!-- How to use -->
<string name="htu_scan_action">To read the card, press the button at the right bottom corner of the screen</string>
</resources>

View file

@ -0,0 +1,73 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- You can change the parent around to whatever you normally use -->
<style name="DebugColors" parent="Theme.AppCompat.Light.NoActionBar">
<!-- System colors -->
<item name="android:windowBackground">@color/__debugWindowBackground</item>
<item name="android:colorPressedHighlight">#FF4400</item>
<item name="android:colorLongPressedHighlight">#FF0044</item>
<item name="android:colorFocusedHighlight">#44FF00</item>
<item name="android:colorActivatedHighlight">#00FF44</item>
<item name="android:colorMultiSelectHighlight">#4400FF</item>
<item name="android:statusBarColor">#FFF000</item>
<item name="android:navigationBarColor">#000FFF</item>
<item name="android:colorForeground">#440000</item>
<item name="android:colorForegroundInverse">#004400</item>
<item name="android:colorBackground">#444400</item>
<item name="android:colorBackgroundCacheHint">#440044</item>
//Only for >21
<item name="android:textColorPrimary">#FFFF00</item>
<item name="android:textColorSecondary">#FF00FF</item>
<item name="android:textColorTertiary">#00FFFF</item> <!-- Overrides a TextView textColor-->
<item name="android:textColorPrimaryInverse">#CCCC00</item>
<item name="android:textColorSecondaryInverse">#CC00CC</item>
<item name="android:textColorTertiaryInverse">#00CCCC</item>
<item name="android:textColorPrimaryDisableOnly">#FFCC00</item>
<item name="android:textColorPrimaryInverseDisableOnly">#FF00CC</item>
<item name="android:textColorPrimaryNoDisable">#CCFF00</item>
<item name="android:textColorSecondaryNoDisable">#00FFCC</item>
<item name="android:textColorPrimaryInverseNoDisable">#CC00FF</item>
<item name="android:textColorSecondaryInverseNoDisable">#00CCFF</item>
<item name="android:textColorHint">#FF8800</item>
<item name="android:textColorHintInverse">#FF0088</item>
<item name="android:textColorHighlight">#88FF00</item>
<item name="android:textColorHighlightInverse">#00FF88</item>
<item name="android:textColorLink">#8800FF</item>
<item name="android:textColorLinkInverse">#0088FF</item>
<item name="android:textColorAlertDialogListItem">#444444</item>
<!-- Color palette (via app-compat) -->
<item name="colorPrimary">#FF0000</item>
<item name="colorPrimaryDark">#00FF00</item>
<item name="colorAccent">#0000FF</item>
<item name="colorControlNormal">#CC0000</item>
<item name="colorControlActivated">#00CC00</item>
<item name="colorControlHighlight">#0000CC</item>
<item name="colorButtonNormal">#880000</item>
<item name="colorSwitchThumbNormal">#008800</item>
<!-- Random other things found in app-compat -->
<item name="actionMenuTextColor">#440000</item>
<item name="editTextColor">#FF4400</item> <!-- Overrides textColorPrimary-->
<item name="textColorSearchUrl">#000044</item>
</style>
<!-- Also needed, since windowBackground is a reference, not a color -->
<color name="__debugWindowBackground">#888888</color>
</resources>