From e34e4d6906695a5ee6145f4c904e9cacfe43e269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 26 Feb 2021 17:27:17 +0100 Subject: [PATCH] [Lab] Add JSON editor. --- .../ui/modules/debug/LabJsonAdapter.kt | 39 ++++-- .../ui/modules/debug/LabProfileFragment.kt | 131 +++++++++++++++--- .../debug/viewholder/JsonArrayViewHolder.kt | 7 +- .../debug/viewholder/JsonElementViewHolder.kt | 6 +- .../debug/viewholder/JsonObjectViewHolder.kt | 7 +- .../viewholder/JsonSubObjectViewHolder.kt | 46 ++++++ app/src/main/res/layout/lab_item_object.xml | 3 +- .../main/res/layout/lab_item_sub_object.xml | 48 +++++++ 8 files changed, 248 insertions(+), 39 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonSubObjectViewHolder.kt create mode 100644 app/src/main/res/layout/lab_item_sub_object.xml diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabJsonAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabJsonAdapter.kt index d1543cfc..a4a332c5 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabJsonAdapter.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabJsonAdapter.kt @@ -26,6 +26,7 @@ import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonObject import pl.szczodrzynski.edziennik.ui.modules.debug.viewholder.JsonArrayViewHolder import pl.szczodrzynski.edziennik.ui.modules.debug.viewholder.JsonElementViewHolder import pl.szczodrzynski.edziennik.ui.modules.debug.viewholder.JsonObjectViewHolder +import pl.szczodrzynski.edziennik.ui.modules.debug.viewholder.JsonSubObjectViewHolder import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder import kotlin.coroutines.CoroutineContext @@ -35,14 +36,20 @@ class LabJsonAdapter( var onJsonElementClick: ((item: LabJsonElement) -> Unit)? = null ) : RecyclerView.Adapter(), CoroutineScope { companion object { - private const val TAG = "AttendanceAdapter" + private const val TAG = "LabJsonAdapter" private const val ITEM_TYPE_OBJECT = 0 - private const val ITEM_TYPE_ARRAY = 1 - private const val ITEM_TYPE_ELEMENT = 2 + private const val ITEM_TYPE_SUB_OBJECT = 1 + private const val ITEM_TYPE_ARRAY = 2 + private const val ITEM_TYPE_ELEMENT = 3 const val STATE_CLOSED = 0 const val STATE_OPENED = 1 fun expand(item: Any, level: Int): MutableList { + val path = when (item) { + is LabJsonObject -> item.key + ":" + is LabJsonArray -> item.key + ":" + else -> "" + } val json = when (item) { is LabJsonObject -> item.jsonObject is LabJsonArray -> item.jsonArray @@ -53,17 +60,16 @@ class LabJsonAdapter( } return when (json) { - is JsonObject -> json.entrySet().mapNotNull { wrap(it.key, it.value, level) } - is JsonArray -> json.mapIndexedNotNull { index, jsonElement -> wrap(index.toString(), jsonElement, level) } - else -> listOf(LabJsonElement("?", json, level)) + is JsonObject -> json.entrySet().mapNotNull { wrap(path + it.key, it.value, level) } + is JsonArray -> json.mapIndexedNotNull { index, jsonElement -> wrap(path + index.toString(), jsonElement, level) } + else -> listOf(LabJsonElement("$path?", json, level)) }.toMutableList() } - fun wrap(key: String, item: JsonElement, level: Int = 0): Any? { + fun wrap(key: String, item: JsonElement, level: Int = 0): Any { return when (item) { is JsonObject -> LabJsonObject(key, item, level + 1) is JsonArray -> LabJsonArray(key, item, level + 1) - is JsonElement -> LabJsonElement(key, item, level + 1) - else -> null + else -> LabJsonElement(key, item, level + 1) } } } @@ -80,6 +86,7 @@ class LabJsonAdapter( val inflater = LayoutInflater.from(parent.context) return when (viewType) { ITEM_TYPE_OBJECT -> JsonObjectViewHolder(inflater, parent) + ITEM_TYPE_SUB_OBJECT -> JsonSubObjectViewHolder(inflater, parent) ITEM_TYPE_ARRAY -> JsonArrayViewHolder(inflater, parent) ITEM_TYPE_ELEMENT -> JsonElementViewHolder(inflater, parent) else -> throw IllegalArgumentException("Incorrect viewType") @@ -87,8 +94,10 @@ class LabJsonAdapter( } override fun getItemViewType(position: Int): Int { - return when (items[position]) { - is LabJsonObject -> ITEM_TYPE_OBJECT + return when (val item = items[position]) { + is LabJsonObject -> + if (item.level == 1) ITEM_TYPE_OBJECT + else ITEM_TYPE_SUB_OBJECT is LabJsonArray -> ITEM_TYPE_ARRAY is LabJsonElement -> ITEM_TYPE_ELEMENT else -> throw IllegalArgumentException("Incorrect viewType") @@ -118,7 +127,7 @@ class LabJsonAdapter( View.ROTATION, if (model.state == STATE_CLOSED) 0f else 180f, if (model.state == STATE_CLOSED) 180f else 0f - ).setDuration(200).start(); + ).setDuration(200).start() } // hide the preview, show summary @@ -143,7 +152,9 @@ class LabJsonAdapter( var end: Int = items.size for (i in start until items.size) { val model1 = items[i] - val level = (model1 as? ExpandableItemModel<*>)?.level ?: 3 + val level = (model1 as? ExpandableItemModel<*>)?.level + ?: (model1 as? LabJsonElement)?.level + ?: model.level if (level <= model.level) { end = i break @@ -170,6 +181,7 @@ class LabJsonAdapter( val viewType = when (holder) { is JsonObjectViewHolder -> ITEM_TYPE_OBJECT + is JsonSubObjectViewHolder -> ITEM_TYPE_SUB_OBJECT is JsonArrayViewHolder -> ITEM_TYPE_ARRAY is JsonElementViewHolder -> ITEM_TYPE_ELEMENT else -> throw IllegalArgumentException("Incorrect viewType") @@ -180,6 +192,7 @@ class LabJsonAdapter( when { holder is JsonObjectViewHolder && item is LabJsonObject -> holder.onBind(activity, app, item, position, this) + holder is JsonSubObjectViewHolder && item is LabJsonObject -> holder.onBind(activity, app, item, position, this) holder is JsonArrayViewHolder && item is LabJsonArray -> holder.onBind(activity, app, item, position, this) holder is JsonElementViewHolder && item is LabJsonElement -> holder.onBind(activity, app, item, position, this) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabProfileFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabProfileFragment.kt index cea59402..50c64762 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabProfileFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabProfileFragment.kt @@ -10,16 +10,14 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager -import com.google.gson.JsonObject -import com.google.gson.JsonParser +import com.afollestad.materialdialogs.MaterialDialog +import com.google.gson.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.MainActivity -import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.models.ApiError import pl.szczodrzynski.edziennik.databinding.TemplateListPageFragmentBinding -import pl.szczodrzynski.edziennik.startCoroutineTimer import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import kotlin.coroutines.CoroutineContext @@ -38,6 +36,10 @@ class LabProfileFragment : LazyFragment(), CoroutineScope { get() = job + Dispatchers.Main // local/private variables go here + private lateinit var adapter: LabJsonAdapter + private val loginStore by lazy { + app.db.loginStoreDao().getByIdNow(app.profile.loginStoreId) + } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as MainActivity?) ?: return null @@ -48,22 +50,96 @@ class LabProfileFragment : LazyFragment(), CoroutineScope { } override fun onPageCreated(): Boolean { startCoroutineTimer(100L) { - val adapter = LabJsonAdapter(activity) - val json = JsonObject().also { json -> - json.add("app.profile", app.profile.studentData) - json.add("app.config", JsonParser().parse(app.gson.toJson(app.config.values))) - EdziennikTask.profile?.let { - json.add("API.profile", it.studentData) - } ?: { - json.addProperty("API.profile", "null") - }() - EdziennikTask.loginStore?.let { - json.add("API.loginStore", it.data) - } ?: { - json.addProperty("API.loginStore", "null") - }() - } - adapter.items = LabJsonAdapter.expand(json, 0) + adapter = LabJsonAdapter(activity, onJsonElementClick = { item -> + try { + var parent: Any = Unit + var obj: Any = Unit + var objName: String = "" + item.key.split(":").forEach { el -> + parent = obj + obj = when (el) { + "App.profile" -> app.profile + "App.profile.studentData" -> app.profile.studentData + "App.profile.loginStore" -> loginStore?.data ?: JsonObject() + "App.config" -> app.config.values + else -> when (obj) { + is JsonObject -> (obj as JsonObject).get(el) + is JsonArray -> (obj as JsonArray).get(el.toInt()) + is HashMap<*, *> -> (obj as HashMap)[el].toString() + else -> { + val field = obj::class.java.getDeclaredField(el) + field.isAccessible = true + field.get(obj) ?: return@forEach + } + } + } + objName = el + } + + val objVal = obj + val value = when (objVal) { + is JsonPrimitive -> when { + objVal.isString -> objVal.asString + objVal.isNumber -> objVal.asNumber.toString() + objVal.isBoolean -> objVal.asBoolean.toString() + else -> objVal.asString + } + else -> objVal.toString() + } + + MaterialDialog.Builder(activity) + .input("value", value, false) { _, input -> + val input = input.toString() + when (parent) { + is JsonObject -> { + val v = objVal as JsonPrimitive + when { + v.isString -> (parent as JsonObject)[objName] = input + v.isNumber -> (parent as JsonObject)[objName] = input.toLong() + v.isBoolean -> (parent as JsonObject)[objName] = input.toBoolean() + } + } + is JsonArray -> { + + } + is HashMap<*, *> -> app.config.set(objName, input) + else -> { + val field = parent::class.java.getDeclaredField(objName) + field.isAccessible = true + val newVal = when (objVal) { + is Int -> input.toInt() + is Boolean -> input.toBoolean() + is Float -> input.toFloat() + is Char -> input.toCharArray()[0] + is String -> input + is Long -> input.toLong() + is Double -> input.toDouble() + else -> input + } + field.set(parent, newVal) + } + } + + when (item.key.substringBefore(":")) { + "App.profile" -> app.profileSave() + "App.profile.studentData" -> app.profileSave() + "App.profile.loginStore" -> app.db.loginStoreDao().add(loginStore) + } + + showJson() + + } + .title(item.key) + .positiveText(R.string.ok) + .negativeText(R.string.cancel) + .show() + } + catch (e: Exception) { + activity.error(ApiError.fromThrowable(TAG, e)) + } + }) + + showJson() b.list.adapter = adapter b.list.apply { @@ -79,4 +155,15 @@ class LabProfileFragment : LazyFragment(), CoroutineScope { b.noData.isVisible = false }; return true } + + private fun showJson() { + val json = JsonObject().also { json -> + json.add("App.profile", app.gson.toJsonTree(app.profile)) + json.add("App.profile.studentData", app.profile.studentData) + json.add("App.profile.loginStore", loginStore?.data ?: JsonObject()) + json.add("App.config", JsonParser().parse(app.gson.toJson(app.config.values))) + } + adapter.items = LabJsonAdapter.expand(json, 0) + adapter.notifyDataSetChanged() + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonArrayViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonArrayViewHolder.kt index 08d4cd3c..56645c28 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonArrayViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonArrayViewHolder.kt @@ -13,6 +13,7 @@ import androidx.core.view.isInvisible import androidx.recyclerview.widget.RecyclerView import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.databinding.LabItemObjectBinding +import pl.szczodrzynski.edziennik.dp import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter import pl.szczodrzynski.edziennik.ui.modules.debug.LabJsonAdapter import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonArray @@ -32,6 +33,10 @@ class JsonArrayViewHolder( override fun onBind(activity: AppCompatActivity, app: App, item: LabJsonArray, position: Int, adapter: LabJsonAdapter) { val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + b.root.setPadding(item.level * 8.dp + 8.dp, 8.dp, 8.dp, 8.dp) + + b.type.text = "Array" + b.dropdownIcon.rotation = when (item.state) { AttendanceAdapter.STATE_CLOSED -> 0f else -> 180f @@ -39,7 +44,7 @@ class JsonArrayViewHolder( b.previewContainer.isInvisible = item.state != AttendanceAdapter.STATE_CLOSED b.summaryContainer.isInvisible = item.state == AttendanceAdapter.STATE_CLOSED - b.key.text = item.key + b.key.text = item.key.substringAfterLast(":") b.previewContainer.text = item.jsonArray.toString().take(200) b.summaryContainer.text = item.jsonArray.size().toString() + " elements" } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonElementViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonElementViewHolder.kt index 8ce6b461..20b1c2c8 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonElementViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonElementViewHolder.kt @@ -32,6 +32,8 @@ class JsonElementViewHolder( override fun onBind(activity: AppCompatActivity, app: App, item: LabJsonElement, position: Int, adapter: LabJsonAdapter) { val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + b.root.setPadding(item.level * 8.dp + 8.dp, 8.dp, 8.dp, 8.dp) + b.type.text = when (item.jsonElement) { is JsonPrimitive -> when { item.jsonElement.isNumber -> "Number" @@ -45,7 +47,9 @@ class JsonElementViewHolder( val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) b.key.text = listOf( - item.key.asColoredSpannable(colorSecondary), + item.key + .substringAfterLast(":") + .asColoredSpannable(colorSecondary), ": ", item.jsonElement.toString().asItalicSpannable() ).concat("") diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonObjectViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonObjectViewHolder.kt index 3131b0ea..b4de8efe 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonObjectViewHolder.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonObjectViewHolder.kt @@ -13,6 +13,7 @@ import androidx.core.view.isInvisible import androidx.recyclerview.widget.RecyclerView import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.databinding.LabItemObjectBinding +import pl.szczodrzynski.edziennik.dp import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter import pl.szczodrzynski.edziennik.ui.modules.debug.LabJsonAdapter import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonObject @@ -32,6 +33,10 @@ class JsonObjectViewHolder( override fun onBind(activity: AppCompatActivity, app: App, item: LabJsonObject, position: Int, adapter: LabJsonAdapter) { val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + b.root.setPadding(item.level * 8.dp + 8.dp, 8.dp, 8.dp, 8.dp) + + b.type.text = "Object" + b.dropdownIcon.rotation = when (item.state) { AttendanceAdapter.STATE_CLOSED -> 0f else -> 180f @@ -39,7 +44,7 @@ class JsonObjectViewHolder( b.previewContainer.isInvisible = item.state != AttendanceAdapter.STATE_CLOSED b.summaryContainer.isInvisible = item.state == AttendanceAdapter.STATE_CLOSED - b.key.text = item.key + b.key.text = item.key.substringAfterLast(":") b.previewContainer.text = item.jsonObject.toString().take(200) b.summaryContainer.text = item.jsonObject.size().toString() + " elements" } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonSubObjectViewHolder.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonSubObjectViewHolder.kt new file mode 100644 index 00000000..dbc71ca2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/viewholder/JsonSubObjectViewHolder.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kuba SzczodrzyƄski 2021-2-26. + */ + +package pl.szczodrzynski.edziennik.ui.modules.debug.viewholder + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.databinding.LabItemSubObjectBinding +import pl.szczodrzynski.edziennik.dp +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter +import pl.szczodrzynski.edziennik.ui.modules.debug.LabJsonAdapter +import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonObject +import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder +import pl.szczodrzynski.edziennik.utils.Themes + +class JsonSubObjectViewHolder( + inflater: LayoutInflater, + parent: ViewGroup, + val b: LabItemSubObjectBinding = LabItemSubObjectBinding.inflate(inflater, parent, false) +) : RecyclerView.ViewHolder(b.root), BindableViewHolder { + companion object { + private const val TAG = "JsonSubObjectViewHolder" + } + + @SuppressLint("SetTextI18n") + override fun onBind(activity: AppCompatActivity, app: App, item: LabJsonObject, position: Int, adapter: LabJsonAdapter) { + val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme) + + b.root.setPadding(item.level * 8.dp + 8.dp, 8.dp, 8.dp, 8.dp) + + b.type.text = "Object" + + b.dropdownIcon.rotation = when (item.state) { + AttendanceAdapter.STATE_CLOSED -> 0f + else -> 180f + } + + b.key.text = item.key.substringAfterLast(":") + } +} diff --git a/app/src/main/res/layout/lab_item_object.xml b/app/src/main/res/layout/lab_item_object.xml index 88fc19fa..76800f1a 100644 --- a/app/src/main/res/layout/lab_item_object.xml +++ b/app/src/main/res/layout/lab_item_object.xml @@ -30,11 +30,12 @@ tools:text="lessonRanges" /> + + + + + + + + + + + + +