Updated on 2026-08-14
This commit is contained in:
parent
91835911f1
commit
5aa06a3074
25 changed files with 565 additions and 97 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -49,4 +49,8 @@ open class SimpleItemGroup(
|
|||
ILog.d(this, "clear $id")
|
||||
itemList.clear()
|
||||
}
|
||||
|
||||
override fun update(value: Item) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = value.isVisibleState
|
||||
backgroundColor = value.backgroundColor
|
||||
descriptionVisibility = 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(
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.main_menu, menu)
|
||||
val switchMenu = menu.findItem(R.id.action_favorite)
|
||||
val switchMenu = menu.findItem(R.id.action_toggle_description_visibility)
|
||||
(switchMenu.actionView as? SwitchCompat)?.let {
|
||||
it.setOnCheckedChangeListener { buttonView, isChecked -> vm.switchToggled(isChecked) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(): Map<String, M>
|
||||
fun delete(key: String)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ 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
|
||||
|
|
@ -87,39 +88,45 @@ 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)
|
||||
}
|
||||
|
||||
protected open fun handleResponseCardData(card: Card) {}
|
||||
|
||||
protected open fun handleError(error: String) {
|
||||
showSnackbar(error)
|
||||
}
|
||||
|
||||
protected open fun handleDescriptionSwitchChanges(descriptionVisibilityState: Boolean) {
|
||||
actionVM.toggleDescriptionVisibility(descriptionVisibilityState)
|
||||
}
|
||||
|
||||
@Deprecated("Start to use itemViewModel")
|
||||
|
|
@ -132,12 +139,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)
|
||||
|
|
|
|||
|
|
@ -5,22 +5,46 @@ 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"
|
||||
private val sharedPreferencesKey = "personalization_presets"
|
||||
private val defaultKey = "default"
|
||||
|
||||
private val sp: SharedPreferences = (context.applicationContext as AppTangemDemo).sharedPreferences()
|
||||
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(): Map<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)
|
||||
}
|
||||
|
|
@ -6,20 +6,19 @@ package com.tangem.tangemtest.ucase.variants.personalize.dto
|
|||
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 +26,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 +43,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 = "ed25519"
|
||||
blockchain = "BTC/test"
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
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.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.transition.Fade
|
||||
import androidx.transition.TransitionManager
|
||||
import com.tangem.commands.Card
|
||||
|
|
@ -13,7 +16,9 @@ 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.ucase.domain.paramsManager.ItemsManager
|
||||
|
|
@ -24,6 +29,9 @@ 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 ru.dev.gbixahue.eu4d.lib.android._android.views.inflate
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
|
@ -33,12 +41,17 @@ 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_base_action_layout
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
setHasOptionsMenu(true)
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
lifecycle.addObserver(itemsManager as PersonalizationItemsManager)
|
||||
|
|
@ -102,6 +115,28 @@ class PersonalizationFragment : BaseCardActionFragment() {
|
|||
contentContainer.addView(btnContainer)
|
||||
}
|
||||
|
||||
override fun handleResponseCardData(card: Card) {
|
||||
super.handleResponseCardData(card)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen)
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
menu.setGroupVisible(R.id.menu_group_personalization_preset, true)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val store = PersonalizationConfigStore(requireContext())
|
||||
val presetManager = PersonalizationPresetManager(itemsManager, store, this)
|
||||
when (item.itemId) {
|
||||
R.id.action_reset -> presetManager.resetToDefault()
|
||||
R.id.action_save -> presetManager.savePreset()
|
||||
R.id.action_load -> presetManager.loadPreset()
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)?) {
|
||||
super.showSnackbar(id) {
|
||||
when (id) {
|
||||
|
|
@ -112,8 +147,31 @@ 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_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.show()
|
||||
}
|
||||
|
||||
override fun showLoadPresetDialog(namesList: List<String>, onChoose: SafeValueChanged<String>, onDelete: SafeValueChanged<String>) {
|
||||
val dlgController = DialogController()
|
||||
val dlg = dlgController.createAlert(requireActivity(), R.layout.dlg_personalization_preset_load)
|
||||
dlg.setTitle(R.string.menu_preset_load)
|
||||
val rvPresetNames: RecyclerView = dlgController.view?.findViewById(R.id.recycler_view) ?: return
|
||||
|
||||
rvPresetNames.layoutManager = LinearLayoutManager(context)
|
||||
rvPresetNames.adapter = RvPresetNamesAdapter({
|
||||
onChoose(it)
|
||||
dlgController.dismiss()
|
||||
}, onDelete).apply { setItemList(namesList.toMutableList()) }
|
||||
dlgController.show()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
val namesList = presets.map { it.key }.toMutableList()
|
||||
if (namesList.size <= 1) {
|
||||
view.showSnackbar(R.string.error_nothing_to_load)
|
||||
return
|
||||
}
|
||||
|
||||
namesList.removeAt(0)
|
||||
view.showLoadPresetDialog(namesList, {
|
||||
val converter = PersonalizationConfigConverter()
|
||||
val config = store.restore(it)
|
||||
itemsManager.updateByItemList(converter.convert(config))
|
||||
}, {
|
||||
store.delete(it)
|
||||
})
|
||||
}
|
||||
|
||||
fun savePreset() {
|
||||
view.showSavePresetDialog { name ->
|
||||
val converter = PersonalizationConfigConverter()
|
||||
val config = converter.convert(itemsManager.getItems(), PersonalizationConfig.default())
|
||||
store.save(name, config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.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>)
|
||||
}
|
||||
|
|
@ -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 ->
|
||||
onDeleteClicked(value)
|
||||
itemList.removeAt(position)
|
||||
this.notifyItemRemoved(position)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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<Button>(R.id.btn_delete)
|
||||
override fun onDataBound(data: String) {
|
||||
tvName.text = data
|
||||
tvName.isClickable = false
|
||||
itemView.setOnClickListener { onItemClicked(data) }
|
||||
btnDelete.setOnClickListener { onDeleteClicked(absoluteAdapterPosition, data) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ class ScanActionFragment : BaseCardActionFragment() {
|
|||
actionFab.setOnClickListener { actionVM.invokeMainAction() }
|
||||
}
|
||||
|
||||
override fun responseCardDataHandled(card: Card?) {
|
||||
super.responseCardDataHandled(card)
|
||||
override fun handleResponseCardData(card: Card) {
|
||||
super.handleResponseCardData(card)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@dimen/def_indent"/>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?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="32dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
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" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:text="@string/btn_delete"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?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">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_share"
|
||||
|
|
@ -10,9 +11,26 @@
|
|||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_favorite"
|
||||
android:id="@+id/action_toggle_description_visibility"
|
||||
android:title=""
|
||||
app:actionLayout="@layout/menu_item_switch"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<group
|
||||
android:id="@+id/menu_group_personalization_preset"
|
||||
android:visible="false"
|
||||
tools:visible="true">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_reset"
|
||||
android:title="@string/menu_preset_reset" />
|
||||
<item
|
||||
android:id="@+id/action_save"
|
||||
android:title="@string/menu_preset_save" />
|
||||
<item
|
||||
android:id="@+id/action_load"
|
||||
android:title="@string/menu_preset_load" />
|
||||
|
||||
</group>
|
||||
|
||||
</menu>
|
||||
|
|
@ -1,14 +1,21 @@
|
|||
<resources>
|
||||
<string name="app_name">Tangem Development Kit</string>
|
||||
|
||||
<string name="unknown">unknown</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="fg_name_entry_point">@string/app_name</string>
|
||||
<string name="fg_name_response_scan">Scan 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>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="menu_preset_reset">Reset configuration to default</string>
|
||||
<string name="menu_preset_save">Save configuration</string>
|
||||
<string name="menu_preset_load">Load configuration</string>
|
||||
<string name="error_nothing_to_load">Nothing to load</string>
|
||||
|
||||
|
||||
<string name="personalize">Personalize</string>
|
||||
<string name="depersonalize">Depersonalize</string>
|
||||
<string name="pers_block_card_number">Card number</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue