Updated on 2026-08-14
This commit is contained in:
parent
86f20cb1b9
commit
72616738fe
27 changed files with 406 additions and 174 deletions
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tangemtest._arch.widget
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ItemWidgetBuilder {
|
||||
fun build(item: BaseItem<*>, parent: ViewGroup): ViewWidget?
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.tangemtest._arch.widget
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Block
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ListItemBlock
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
|
||||
import com.tangem.tangemtest._arch.widget.impl.LinearBlockWidget
|
||||
import com.tangem.tangemtest._arch.widget.impl.StubWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WidgetBuilder(
|
||||
private val itemBuilder: ItemWidgetBuilder
|
||||
) {
|
||||
|
||||
fun build(item: Item, parent: ViewGroup): ViewWidget? {
|
||||
return when (item) {
|
||||
is Block -> buildBlock(item, parent)
|
||||
is BaseItem<*> -> itemBuilder.build(item, parent)
|
||||
else -> StubWidget(parent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildBlock(block: Block, parent: ViewGroup): ViewWidget {
|
||||
return when (block) {
|
||||
is ListItemBlock -> {
|
||||
val linearBlock = LinearBlockWidget(parent, block)
|
||||
block.getItems().forEach { build(it, linearBlock.view as ViewGroup) }
|
||||
linearBlock
|
||||
}
|
||||
else -> StubWidget(parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction
|
||||
package com.tangem.tangemtest._arch.widget.abstraction
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.block
|
||||
package com.tangem.tangemtest._arch.widget.impl
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ListItemBlock
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseBlockWidget
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.iterate
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.BaseBlockWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -17,5 +17,11 @@ class LinearBlockWidget(
|
|||
override fun getLayoutId(): Int = R.layout.w_personilize_block
|
||||
|
||||
override var viewModel: List<ItemViewModel<*>> = listOf()
|
||||
get() = children.itemList.map { it as BaseItem<*> }.map { it.viewModel }
|
||||
get() {
|
||||
val vmList = mutableListOf<ItemViewModel<*>>()
|
||||
children.itemList.iterate { item ->
|
||||
(item as? ItemViewModel<*>)?.let { vmList.add(it) }
|
||||
}
|
||||
return vmList.toList()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl
|
||||
package com.tangem.tangemtest._arch.widget.impl
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ItemViewModel
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseBlockWidget
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.BaseBlockWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -1,14 +1,10 @@
|
|||
package com.tangem.tangemtest._main.entryPoint
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
|
@ -19,21 +15,19 @@ import com.tangem.tangemtest._main.MainViewModel
|
|||
import com.tangem.tangemtest.ucase.getDefaultNavigationOptions
|
||||
import com.tangem.tangemtest.ucase.resources.ActionType
|
||||
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
|
||||
import com.tangem.tangemtest.ucase.ui.BaseFragment
|
||||
import kotlinx.android.synthetic.main.fg_entry_point.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ActionListFragment : Fragment() {
|
||||
class ActionListFragment : BaseFragment() {
|
||||
|
||||
private val navController: NavController by lazy { findNavController() }
|
||||
private lateinit var rvActions: RecyclerView
|
||||
|
||||
private val mainActivityVM: MainViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fg_entry_point, container, false)
|
||||
}
|
||||
override fun getLayoutId(): Int = R.layout.fg_entry_point
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
|
@ -48,7 +42,9 @@ class ActionListFragment : Fragment() {
|
|||
|
||||
val vhDataWrapper = VhExDataWrapper(MainResourceHolder, false)
|
||||
|
||||
rvActions.adapter = RvActionsAdapter(vhDataWrapper) { type, position, data -> navigate(data) }
|
||||
rvActions.adapter = RvActionsAdapter(vhDataWrapper) { type, position, data ->
|
||||
navigateTo(data, options = getDefaultNavigationOptions())
|
||||
}
|
||||
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
|
||||
vhDataWrapper.descriptionIsVisible = it
|
||||
TransitionManager.beginDelayedTransition(rvActions as ViewGroup, AutoTransition())
|
||||
|
|
@ -64,10 +60,6 @@ class ActionListFragment : Fragment() {
|
|||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun navigate(destinationId: Int) {
|
||||
navController.navigate(destinationId, null, getDefaultNavigationOptions())
|
||||
}
|
||||
|
||||
private fun getNavigateOptions(): MutableList<ActionType> {
|
||||
return mutableListOf(
|
||||
ActionType.Scan,
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ class AfterScanModifier : AfterActionModification {
|
|||
|
||||
return if (isNotPersonalized(card)) {
|
||||
actionView.showSnackbar(CardError.NotPersonalized)
|
||||
postUI { actionView.showActionFab(false) }
|
||||
postUI { actionView.enableActionFab(false) }
|
||||
listOf()
|
||||
} else {
|
||||
payload.set(PayloadKey.card, card)
|
||||
foundItem.setData(card.cardId)
|
||||
postUI { actionView.showActionFab(true) }
|
||||
postUI { actionView.enableActionFab(true) }
|
||||
listOf(foundItem)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class SignScanConsequence: ItemsChangeConsequence {
|
|||
val card = payload.remove(PayloadKey.card) as Card?
|
||||
if (card == null) {
|
||||
hashItem.restoreDefaultData()
|
||||
postUI { (payload.get(PayloadKey.actionView) as? ActionView)?.showActionFab(false) }
|
||||
postUI { (payload.get(PayloadKey.actionView) as? ActionView)?.enableActionFab(false) }
|
||||
} else {
|
||||
val dataForHashing = hashItem.getData() as? String ?: "Any data mother...s"
|
||||
val hashedData = when(card.curve) {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
package com.tangem.tangemtest.ucase.tunnel
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
|
||||
interface SnackbarHolder {
|
||||
fun showSnackbar(message: String, length: Int = Snackbar.LENGTH_SHORT)
|
||||
fun showSnackbar(id: Int, length: Int = Snackbar.LENGTH_SHORT)
|
||||
}
|
||||
|
||||
interface ViewScreen
|
||||
|
||||
interface SnackbarHolder {
|
||||
fun showSnackbar(message: String)
|
||||
fun showSnackbar(id: Int)
|
||||
fun showSnackbar(id: Id)
|
||||
}
|
||||
|
||||
interface ActionView : ViewScreen, SnackbarHolder {
|
||||
fun showActionFab(show: Boolean)
|
||||
fun enableActionFab(enable: Boolean)
|
||||
fun showSnackbar(id: Id)
|
||||
}
|
||||
|
|
@ -5,14 +5,11 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tangemtest.R
|
||||
|
|
@ -26,18 +23,18 @@ import com.tangem.tangemtest.ucase.resources.ActionType
|
|||
import com.tangem.tangemtest.ucase.tunnel.ActionView
|
||||
import com.tangem.tangemtest.ucase.tunnel.CardError
|
||||
import com.tangem.tangemtest.ucase.ui.widgets.ParameterWidget
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.enable
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseCardActionFragment : Fragment(), LayoutHolder, ActionView {
|
||||
abstract class BaseCardActionFragment : BaseFragment(), LayoutHolder, ActionView {
|
||||
|
||||
protected val incomingParamsContainer: ViewGroup by lazy {
|
||||
mainView.findViewById<LinearLayout>(R.id.ll_incoming_params_container)
|
||||
}
|
||||
protected lateinit var mainView: View
|
||||
protected val actionFab: FloatingActionButton by lazy { mainView.findViewById<FloatingActionButton>(R.id.fab_action) }
|
||||
|
||||
protected val paramsVM: ParamsViewModel by viewModels { ActionViewModelFactory(itemsManager) }
|
||||
|
|
@ -45,12 +42,6 @@ abstract class BaseCardActionFragment : Fragment(), LayoutHolder, ActionView {
|
|||
|
||||
private val itemsManager: ItemsManager by lazy { ParamsManagerFactory.createFactory().get(getAction())!! }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
Log.d(this, "onCreateView")
|
||||
mainView = inflater.inflate(getLayoutId(), container, false)
|
||||
return mainView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
Log.d(this, "onViewCreated")
|
||||
|
|
@ -59,18 +50,17 @@ abstract class BaseCardActionFragment : Fragment(), LayoutHolder, ActionView {
|
|||
paramsVM.setCardManager(CardManager.init(requireActivity()))
|
||||
paramsVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView))
|
||||
initFab()
|
||||
showActionFab(false)
|
||||
createWidgets { subscribeToViewModelChanges() }
|
||||
}
|
||||
|
||||
private fun initFab() {
|
||||
Log.d(this, "initFab")
|
||||
enableActionFab(false)
|
||||
actionFab.setOnClickListener { paramsVM.invokeMainAction() }
|
||||
}
|
||||
|
||||
private fun createWidgets(widgetCreatedCallback: () -> Unit) {
|
||||
Log.d(this, "createWidgets")
|
||||
paramsVM.ldParams.observe(viewLifecycleOwner, Observer { itemList ->
|
||||
paramsVM.ldItemList.observe(viewLifecycleOwner, Observer { itemList ->
|
||||
itemList.forEach { param ->
|
||||
val widget = ParameterWidget(inflateParamView(incomingParamsContainer), param)
|
||||
widget.onValueChanged = { id, value -> paramsVM.userChangedItem(id, value) }
|
||||
|
|
@ -83,17 +73,19 @@ abstract class BaseCardActionFragment : Fragment(), LayoutHolder, ActionView {
|
|||
|
||||
private fun subscribeToViewModelChanges() {
|
||||
Log.d(this, "subscribeToViewModelChanges")
|
||||
listenReadResponse()
|
||||
listenResponse()
|
||||
listenError()
|
||||
listenChangedItems()
|
||||
listenDescriptionSwitchChanges()
|
||||
}
|
||||
|
||||
protected open fun listenReadResponse() {}
|
||||
|
||||
protected open fun listenResponse() {
|
||||
val tvResponse by lazy { mainView.findViewById<TextView>(R.id.tv_action_response_json) }
|
||||
paramsVM.ldResponse.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "action response: ${if (it.length > 50) it.substring(0..50) else it}")
|
||||
tvResponse.text = it
|
||||
paramsVM.seResponse.observe(viewLifecycleOwner, Observer {
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen,
|
||||
bundleOf(Pair(BaseCardResponseFragment.response, it)))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -126,8 +118,8 @@ abstract class BaseCardActionFragment : Fragment(), LayoutHolder, ActionView {
|
|||
return view as ViewGroup
|
||||
}
|
||||
|
||||
override fun showActionFab(show: Boolean) {
|
||||
if (show) actionFab.show() else actionFab.hide()
|
||||
override fun enableActionFab(enable: Boolean) {
|
||||
actionFab.enable(enable)
|
||||
}
|
||||
|
||||
override fun showSnackbar(id: Id) {
|
||||
|
|
@ -138,13 +130,5 @@ abstract class BaseCardActionFragment : Fragment(), LayoutHolder, ActionView {
|
|||
}
|
||||
}
|
||||
|
||||
override fun showSnackbar(id: Int) {
|
||||
showSnackbar(requireContext().getString(id))
|
||||
}
|
||||
|
||||
override fun showSnackbar(message: String) {
|
||||
Snackbar.make(mainView, message, BaseTransientBottomBar.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
abstract fun getAction(): ActionType
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.tangem.tangemtest.ucase.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ListItemBlock
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.iterate
|
||||
import com.tangem.tangemtest._arch.structure.impl.TextItem
|
||||
import com.tangem.tangemtest._arch.widget.WidgetBuilder
|
||||
import com.tangem.tangemtest._main.MainViewModel
|
||||
import com.tangem.tangemtest.ucase.resources.StringId
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class BaseCardResponseFragment : BaseFragment() {
|
||||
|
||||
companion object Arg {
|
||||
val response = "response"
|
||||
}
|
||||
|
||||
private val mainActivityVM: MainViewModel by activityViewModels()
|
||||
private val blockContainer: ViewGroup by lazy { mainView.findViewById<LinearLayout>(R.id.ll_container) }
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_card_response
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val response = requireArguments().getString(Arg.response) ?: return
|
||||
if (response.isEmpty()) return
|
||||
|
||||
val itemList = createItemList(response)
|
||||
itemList.forEach { WidgetBuilder(ResponseItemBuilder()).build(it, blockContainer) }
|
||||
|
||||
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
|
||||
itemList.iterate { item ->
|
||||
val baseItem = item as? BaseItem<*> ?: return@iterate
|
||||
baseItem.viewModel.viewState.descriptionVisibility = if (it) View.VISIBLE else View.GONE
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
protected open fun createItemList(response: String): List<Item> {
|
||||
return listOf(
|
||||
ListItemBlock(StringId("")).apply {
|
||||
addItem(TextItem(StringId("resp.fieldName"), response))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.tangemtest.ucase.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.fragment.NavHostFragment.findNavController
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.tangemtest.ucase.getDefaultNavigationOptions
|
||||
import com.tangem.tangemtest.ucase.tunnel.SnackbarHolder
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseFragment : Fragment(), LayoutHolder, SnackbarHolder {
|
||||
|
||||
protected lateinit var mainView: View
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
mainView = inflater.inflate(getLayoutId(), container, false)
|
||||
return mainView
|
||||
}
|
||||
|
||||
protected fun navigateTo(navigationId: Int, bundle: Bundle? = null, options: NavOptions? = getDefaultNavigationOptions()) {
|
||||
findNavController(this).navigate(navigationId, bundle, options)
|
||||
}
|
||||
|
||||
override fun showSnackbar(id: Int, length: Int) {
|
||||
showSnackbar(requireContext().getString(id), length)
|
||||
}
|
||||
|
||||
override fun showSnackbar(message: String, length: Int) {
|
||||
Snackbar.make(mainView, message, length).show()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import androidx.annotation.UiThread
|
|||
import androidx.lifecycle.*
|
||||
import com.google.gson.Gson
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.tangemtest._arch.SingleLiveEvent
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
import com.tangem.tangemtest._arch.structure.Payload
|
||||
|
|
@ -27,12 +26,10 @@ class ActionViewModelFactory(private val manager: ItemsManager) : ViewModelProvi
|
|||
|
||||
class ParamsViewModel(private val itemsManager: ItemsManager) : ViewModel(), LifecycleObserver {
|
||||
|
||||
val ldCard = MutableLiveData<Card>()
|
||||
val ldIsVerified = MutableLiveData<Boolean>()
|
||||
val ldResponse = MutableLiveData<String>()
|
||||
val ldReadResponse = MutableLiveData<String>()
|
||||
val ldParams = MutableLiveData(itemsManager.getItems())
|
||||
val seReadResponse = SingleLiveEvent<String>()
|
||||
val seResponse = SingleLiveEvent<String>()
|
||||
|
||||
val ldItemList = MutableLiveData(itemsManager.getItems())
|
||||
val seError: MutableLiveData<String> = SingleLiveEvent()
|
||||
val seChangedItems: MutableLiveData<List<Item>> = SingleLiveEvent()
|
||||
|
||||
|
|
@ -109,13 +106,13 @@ internal class Notifier(private val vm: ParamsViewModel) {
|
|||
private fun handleDataEvent(event: Any?) {
|
||||
when (event) {
|
||||
is ScanEvent.OnReadEvent -> {
|
||||
vm.ldCard.postValue(event.card)
|
||||
vm.ldReadResponse.postValue(gson.toJson(event))
|
||||
// vm.ldCard.postValue(event.card)
|
||||
vm.seReadResponse.postValue(gson.toJson(event))
|
||||
}
|
||||
is ScanEvent.OnVerifyEvent -> {
|
||||
vm.ldIsVerified.postValue(true)
|
||||
// vm.ldIsVerified.postValue(true)
|
||||
}
|
||||
else -> vm.ldResponse.postValue(gson.toJson(event))
|
||||
else -> vm.seResponse.postValue(gson.toJson(event))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.tangemtest.ucase.ui
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
|
||||
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.ui.widgets.ResponseTextWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ResponseItemBuilder : ItemWidgetBuilder {
|
||||
override fun build(item: BaseItem<*>, parent: ViewGroup): ViewWidget? {
|
||||
return when (item) {
|
||||
is TextItem -> ResponseTextWidget(parent, item)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.tangemtest.ucase.ui.widgets
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
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._arch.widget.abstraction.getResNameId
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.DescriptionWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ResponseTextWidget(parent: ViewGroup, textItem: TextItem) : DescriptionWidget<String>(parent, textItem) {
|
||||
override fun getLayoutId(): Int = R.layout.w_response_item
|
||||
|
||||
private val tvName: TextView by lazy { view.findViewById<TextView>(R.id.tv_name) }
|
||||
private val tvValue: TextView by lazy { view.findViewById<TextView>(R.id.tv_value) }
|
||||
|
||||
init {
|
||||
tvName.setText(getResNameId())
|
||||
tvValue.text = dataItem.getData()
|
||||
|
||||
view.setOnClickListener {
|
||||
val clipboard = view.context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
|
||||
?: return@setOnClickListener
|
||||
|
||||
val clip: ClipData = ClipData.newPlainText("FieldValue", "${dataItem.getData()}")
|
||||
clipboard.setPrimaryClip(clip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,20 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
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.widget.WidgetBuilder
|
||||
import com.tangem.tangemtest._main.MainViewModel
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.ParamsManagerFactory
|
||||
|
|
@ -26,67 +24,89 @@ import com.tangem.tangemtest.ucase.tunnel.ActionView
|
|||
import com.tangem.tangemtest.ucase.tunnel.CardError
|
||||
import com.tangem.tangemtest.ucase.tunnel.ItemError
|
||||
import com.tangem.tangemtest.ucase.ui.ActionViewModelFactory
|
||||
import com.tangem.tangemtest.ucase.ui.BaseCardResponseFragment
|
||||
import com.tangem.tangemtest.ucase.ui.BaseFragment
|
||||
import com.tangem.tangemtest.ucase.ui.ParamsViewModel
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.WidgetBuilder
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.PersonalizeItemBuilder
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PersonalizeFragment : Fragment(), ActionView {
|
||||
class PersonalizeFragment : BaseFragment(), ActionView {
|
||||
|
||||
private val mainActivityVM: MainViewModel by activityViewModels()
|
||||
private val personalizeVM: PersonalizeViewModel by viewModels { PersonalizeViewModelFactory(PersonalizeConfig()) }
|
||||
|
||||
private val itemsManager: ItemsManager by lazy { ParamsManagerFactory.createFactory().get(ActionType.Personalize)!! }
|
||||
private val paramsVM: ParamsViewModel by viewModels() { ActionViewModelFactory(itemsManager) }
|
||||
|
||||
private lateinit var mainView: View
|
||||
private val blockContainer: ViewGroup by lazy { mainView.findViewById<LinearLayout>(R.id.ll_container) }
|
||||
private val actionFab: FloatingActionButton by lazy { mainView.findViewById<FloatingActionButton>(R.id.fab_action) }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
Log.d(this, "onCreateView")
|
||||
mainView = inflater.inflate(R.layout.fg_personalize, container, false)
|
||||
return mainView
|
||||
}
|
||||
private val itemsManager: ItemsManager by lazy { ParamsManagerFactory.createFactory().get(ActionType.Personalize)!! }
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_personalize
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
paramsVM.setCardManager(CardManager.init(requireActivity()))
|
||||
paramsVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView))
|
||||
|
||||
initFab()
|
||||
createWidgets { subscribeToViewModelChanges() }
|
||||
}
|
||||
|
||||
private fun initFab() {
|
||||
enableActionFab(true)
|
||||
actionFab.setOnClickListener { paramsVM.invokeMainAction() }
|
||||
}
|
||||
|
||||
private fun createWidgets(widgetCreatedCallback: () -> Unit) {
|
||||
val blockList = mutableListOf<Item>()
|
||||
personalizeVM.ldBlockList.observe(viewLifecycleOwner, Observer { list ->
|
||||
blockList.clear()
|
||||
blockList.addAll(list)
|
||||
blockList.forEach { WidgetBuilder().build(it, blockContainer) }
|
||||
blockList.forEach { WidgetBuilder(PersonalizeItemBuilder()).build(it, blockContainer) }
|
||||
paramsVM.attachToPayload(mutableMapOf(
|
||||
PayloadKey.actionView to this as ActionView,
|
||||
PayloadKey.itemList to blockList
|
||||
))
|
||||
|
||||
widgetCreatedCallback()
|
||||
})
|
||||
}
|
||||
|
||||
override fun enableActionFab(enable: Boolean) {
|
||||
if (enable) actionFab.show() else actionFab.hide()
|
||||
}
|
||||
|
||||
private fun subscribeToViewModelChanges() {
|
||||
Log.d(this, "subscribeToViewModelChanges")
|
||||
listenReadResponse()
|
||||
listenResponse()
|
||||
listenError()
|
||||
// listenChangedItems()
|
||||
listenDescriptionSwitchChanges()
|
||||
}
|
||||
|
||||
private fun listenReadResponse() {}
|
||||
|
||||
private fun listenResponse() {
|
||||
paramsVM.seResponse.observe(viewLifecycleOwner, Observer {
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen,
|
||||
bundleOf(Pair(BaseCardResponseFragment.response, it)))
|
||||
})
|
||||
}
|
||||
|
||||
private fun listenError() {
|
||||
paramsVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) })
|
||||
|
||||
}
|
||||
|
||||
private fun listenDescriptionSwitchChanges() {
|
||||
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
|
||||
personalizeVM.toggleDescriptionVisibility(it)
|
||||
})
|
||||
|
||||
val cardManager = CardManager.init(requireActivity())
|
||||
paramsVM.setCardManager(cardManager)
|
||||
paramsVM.ldResponse.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "action response: ${if (it.length > 50) it.substring(0..50) else it}")
|
||||
showSnackbarMessage(it)
|
||||
})
|
||||
paramsVM.seError.observe(viewLifecycleOwner, Observer { showSnackbarMessage(it) })
|
||||
|
||||
actionFab.setOnClickListener { paramsVM.invokeMainAction() }
|
||||
}
|
||||
|
||||
protected fun showSnackbarMessage(message: String) {
|
||||
Snackbar.make(mainView, message, BaseTransientBottomBar.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun showActionFab(show: Boolean) {
|
||||
if (show) actionFab.show() else actionFab.hide()
|
||||
}
|
||||
|
||||
override fun showSnackbar(id: Id) {
|
||||
|
|
@ -98,14 +118,6 @@ class PersonalizeFragment : Fragment(), ActionView {
|
|||
else -> showSnackbar(requireContext().getString(R.string.unknown))
|
||||
}
|
||||
}
|
||||
|
||||
override fun showSnackbar(id: Int) {
|
||||
showSnackbar(requireContext().getString(id))
|
||||
}
|
||||
|
||||
override fun showSnackbar(message: String) {
|
||||
Snackbar.make(mainView, message, BaseTransientBottomBar.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
|
||||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
|
||||
|
||||
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.personalize.ui.widgets.abstraction.getResNameId
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.getResNameId
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TextWidget(parent: ViewGroup, data: TextItem) : DescriptionWidget<String>(parent, data) {
|
||||
class BlockHeadWidget(parent: ViewGroup, data: TextItem) : DescriptionWidget<String>(parent, data) {
|
||||
override fun getLayoutId(): Int = R.layout.w_personalize_item_text
|
||||
|
||||
private val tvName = view.findViewById<TextView>(R.id.tv_name)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
|
||||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
|
|
@ -6,8 +6,8 @@ import androidx.transition.AutoTransition
|
|||
import androidx.transition.TransitionManager
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.BaseWidget
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResDescription
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.BaseWidget
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.getResDescription
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
|
||||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
|
|
@ -7,7 +7,7 @@ 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.impl.EditTextItem
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.getResNameId
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.moveCursorToEnd
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
|
||||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.InputFilter
|
||||
|
|
@ -8,8 +8,8 @@ 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.impl.NumberItem
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.getResNameId
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.CardNumber
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.addInputFilter
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.moveCursorToEnd
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.tangemtest._arch.structure.impl.*
|
||||
import com.tangem.tangemtest._arch.widget.ItemWidgetBuilder
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class PersonalizeItemBuilder : ItemWidgetBuilder {
|
||||
override fun build(item: BaseItem<*>, parent: ViewGroup): ViewWidget? {
|
||||
return when (item) {
|
||||
is TextItem -> BlockHeadWidget(parent, item)
|
||||
is EditTextItem -> EditTextWidget(parent, item)
|
||||
is NumberItem -> NumberWidget(parent, item)
|
||||
is BoolItem -> SwitchWidget(parent, item)
|
||||
is ListItem -> SpinnerWidget(parent, item)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
|
||||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -10,7 +10,7 @@ import com.tangem.tangemtest.R
|
|||
import com.tangem.tangemtest._arch.structure.impl.KeyValue
|
||||
import com.tangem.tangemtest._arch.structure.impl.ListItem
|
||||
import com.tangem.tangemtest._arch.structure.impl.ListValueWrapper
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.getResNameId
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item
|
||||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CompoundButton
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.impl.BoolItem
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.getResNameId
|
||||
import com.tangem.tangemtest._arch.widget.abstraction.getResNameId
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.ui.widgets
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Block
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ListItemBlock
|
||||
import com.tangem.tangemtest._arch.structure.impl.*
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.abstraction.ViewWidget
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.StubWidget
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.block.LinearBlockWidget
|
||||
import com.tangem.tangemtest.ucase.variants.personalize.ui.widgets.impl.item.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class WidgetBuilder {
|
||||
|
||||
fun build(item: Item, parent: ViewGroup): ViewWidget? {
|
||||
return when (item) {
|
||||
is Block -> buildBlock(item, parent)
|
||||
is BaseItem<*> -> buildUnitItem(item, parent)
|
||||
else -> StubWidget(parent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildBlock(block: Block, parent: ViewGroup): ViewWidget {
|
||||
return when (block) {
|
||||
is ListItemBlock -> {
|
||||
val linearBlock = LinearBlockWidget(parent, block)
|
||||
block.getItems().forEach { build(it, linearBlock.view as ViewGroup) }
|
||||
linearBlock
|
||||
}
|
||||
else -> StubWidget(parent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildUnitItem(item: BaseItem<*>, parent: ViewGroup): ViewWidget? {
|
||||
return when (item) {
|
||||
is TextItem -> TextWidget(parent, item)
|
||||
is EditTextItem -> EditTextWidget(parent, item)
|
||||
is NumberItem -> NumberWidget(parent, item)
|
||||
is BoolItem -> SwitchWidget(parent, item)
|
||||
is ListItem -> SpinnerWidget(parent, item)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
22
tangem-demo/src/main/res/layout/fg_card_response.xml
Normal file
22
tangem-demo/src/main/res/layout/fg_card_response.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?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">
|
||||
|
||||
<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>
|
||||
|
||||
</LinearLayout>
|
||||
34
tangem-demo/src/main/res/layout/w_response_item.xml
Normal file
34
tangem-demo/src/main/res/layout/w_response_item.xml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?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:padding="@dimen/def_indent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/container_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18sp"
|
||||
tools:text="Custom blockchain" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-2dp"
|
||||
android:textSize="14sp"
|
||||
tools:text="Not Defined" />
|
||||
|
||||
<include layout="@layout/w_field_description" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
|
@ -4,6 +4,10 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
app:startDestination="@+id/nav_entry_point">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_nav_card_action_to_response_screen"
|
||||
app:destination="@+id/nav_card_response" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_entry_point"
|
||||
android:name="com.tangem.tangemtest._main.entryPoint.ActionListFragment"
|
||||
|
|
@ -121,4 +125,10 @@
|
|||
android:label="@string/action_wallet_purge"
|
||||
tools:layout="@layout/fg_action_wallet_purge" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_card_response"
|
||||
android:name="com.tangem.tangemtest.ucase.ui.BaseCardResponseFragment"
|
||||
android:label="@string/unknown"
|
||||
tools:layout="@layout/fg_card_response" />
|
||||
|
||||
</navigation>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue