From 9f6c987dd594a8011844105c7fb1c4033219a854 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Thu, 21 Dec 2023 15:50:55 +0100 Subject: [PATCH] Implement buttongrid Closes #3494 Signed-off-by: mueller-ma --- .idea/dictionaries/openhab.xml | 1 + .../openhab/habdroid/model/LabeledValue.kt | 10 +++- .../java/org/openhab/habdroid/model/Widget.kt | 3 +- .../org/openhab/habdroid/ui/ViewExtensions.kt | 1 + .../org/openhab/habdroid/ui/WidgetAdapter.kt | 47 ++++++++++++++++++- .../res/layout/widgetlist_buttongriditem.xml | 18 +++++++ .../org/openhab/habdroid/model/ItemTest.kt | 8 ++-- 7 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 mobile/src/main/res/layout/widgetlist_buttongriditem.xml diff --git a/.idea/dictionaries/openhab.xml b/.idea/dictionaries/openhab.xml index 0dfc1db2e2c..be0bf34f64b 100644 --- a/.idea/dictionaries/openhab.xml +++ b/.idea/dictionaries/openhab.xml @@ -5,6 +5,7 @@ addr basicui basicuidark + buttongrid colorpicker datasource demomode diff --git a/mobile/src/main/java/org/openhab/habdroid/model/LabeledValue.kt b/mobile/src/main/java/org/openhab/habdroid/model/LabeledValue.kt index b648841ddd2..1ae91c9379f 100644 --- a/mobile/src/main/java/org/openhab/habdroid/model/LabeledValue.kt +++ b/mobile/src/main/java/org/openhab/habdroid/model/LabeledValue.kt @@ -21,12 +21,18 @@ import org.json.JSONObject import org.openhab.habdroid.util.optStringOrNull @Parcelize -data class LabeledValue internal constructor(val value: String, val label: String, val icon: IconResource?) : Parcelable +data class LabeledValue internal constructor( + val value: String, + val label: String, + val icon: IconResource?, + val row: Int, + val column: Int +) : Parcelable @Throws(JSONException::class) fun JSONObject.toLabeledValue(valueKey: String, labelKey: String): LabeledValue { val value = getString(valueKey) val label = optString(labelKey, value) val icon = optStringOrNull("icon")?.toOH2IconResource() - return LabeledValue(value, label, icon) + return LabeledValue(value, label, icon, optInt("row"), optInt("column")) } diff --git a/mobile/src/main/java/org/openhab/habdroid/model/Widget.kt b/mobile/src/main/java/org/openhab/habdroid/model/Widget.kt index 845ddaca3eb..6f524a1dfd3 100644 --- a/mobile/src/main/java/org/openhab/habdroid/model/Widget.kt +++ b/mobile/src/main/java/org/openhab/habdroid/model/Widget.kt @@ -116,6 +116,7 @@ data class Widget( Video, Webview, Input, + Buttongrid, Unknown } @@ -303,7 +304,7 @@ fun Node.collectWidgets(parent: Widget?): List { "label" -> mappingLabel = childNode.textContent } } - mappings.add(LabeledValue(mappingCommand, mappingLabel, null)) + mappings.add(LabeledValue(mappingCommand, mappingLabel, null, 0, 0)) } else -> {} } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/ViewExtensions.kt b/mobile/src/main/java/org/openhab/habdroid/ui/ViewExtensions.kt index 04eb5e276ba..fd09116e105 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/ViewExtensions.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/ViewExtensions.kt @@ -124,6 +124,7 @@ fun RemoteViews.duplicate(): RemoteViews { } fun MaterialButton.setTextAndIcon(connection: Connection, mapping: LabeledValue) { + contentDescription = mapping.label val iconUrl = mapping.icon?.toUrl(context, true) if (iconUrl == null) { icon = null diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/WidgetAdapter.kt b/mobile/src/main/java/org/openhab/habdroid/ui/WidgetAdapter.kt index 1ece3b4ca81..3639598ca86 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/WidgetAdapter.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/WidgetAdapter.kt @@ -32,6 +32,7 @@ import android.view.inputmethod.EditorInfo import android.webkit.WebChromeClient import android.webkit.WebView import android.widget.Button +import android.widget.GridLayout import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView @@ -40,9 +41,11 @@ import androidx.annotation.LayoutRes import androidx.annotation.StringRes import androidx.annotation.VisibleForTesting import androidx.core.content.ContextCompat +import androidx.core.view.allViews import androidx.core.view.children import androidx.core.view.get import androidx.core.view.isGone +import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.widget.ContentLoadingProgressBar import androidx.core.widget.doAfterTextChanged @@ -221,6 +224,7 @@ class WidgetAdapter( TYPE_LOCATION -> MapViewHelper.createViewHolder(initData) TYPE_INPUT -> InputViewHolder(initData) TYPE_DATETIMEINPUT -> DateTimeInputViewHolder(initData) + TYPE_BUTTONGRID -> ButtongridViewHolder(initData) TYPE_INVISIBLE -> InvisibleWidgetViewHolder(initData) else -> throw IllegalArgumentException("View type $viewType is not known") } @@ -345,6 +349,7 @@ class WidgetAdapter( Widget.Type.Colorpicker -> TYPE_COLOR Widget.Type.Mapview -> TYPE_LOCATION Widget.Type.Input -> if (widget.shouldUseDateTimePickerForInput()) TYPE_DATETIMEINPUT else TYPE_INPUT + Widget.Type.Buttongrid -> TYPE_BUTTONGRID else -> TYPE_GENERICITEM } return toInternalViewType(actualViewType, compactMode) @@ -780,6 +785,45 @@ class WidgetAdapter( } } + class ButtongridViewHolder internal constructor(private val initData: ViewHolderInitData) : + LabeledItemBaseViewHolder(initData, R.layout.widgetlist_buttongriditem), View.OnClickListener { + private val table: GridLayout = itemView.findViewById(R.id.widget_content) + + override fun bind(widget: Widget) { + super.bind(widget) + val mappings = widget.mappings.filter { it.column != 0 && it.row != 0 } + table.rowCount = mappings.maxOfOrNull { it.row } ?: 0 + table.columnCount = mappings.maxOfOrNull { it.column } ?: 0 + (0.. + (0.. + val buttonView = initData.inflater.inflate(R.layout.widgetlist_sectionswitchitem_button, null) as MaterialButton + // Rows and columns start with 1 in Sitemap definition, thus decrement them here + val mapping = mappings.firstOrNull { it.row - 1 == row && it.column - 1 == column } + if (mapping == null) { + buttonView.visibility = View.INVISIBLE + } else { + buttonView.setOnClickListener(this) + buttonView.setTextAndIcon(connection, mapping) + buttonView.tag = mapping.value + buttonView.visibility = View.VISIBLE + } + + table.addView( + buttonView, + GridLayout.LayoutParams( + GridLayout.spec(row, GridLayout.FILL, 1f), + GridLayout.spec(column, GridLayout.FILL, 1f) + ) + ) + } + } + } + + override fun onClick(view: View) { + connection.httpClient.sendItemCommand(boundWidget?.item, view.tag as String) + } + } + class SliderViewHolder internal constructor(initData: ViewHolderInitData) : LabeledItemBaseViewHolder(initData, R.layout.widgetlist_slideritem, R.layout.widgetlist_slideritem_compact), WidgetSlider.UpdateListener { @@ -1555,7 +1599,8 @@ class WidgetAdapter( private const val TYPE_LOCATION = 18 private const val TYPE_INPUT = 19 private const val TYPE_DATETIMEINPUT = 20 - private const val TYPE_INVISIBLE = 21 + private const val TYPE_BUTTONGRID = 21 + private const val TYPE_INVISIBLE = 22 private fun toInternalViewType(viewType: Int, compactMode: Boolean): Int { return viewType or (if (compactMode) 0x100 else 0) diff --git a/mobile/src/main/res/layout/widgetlist_buttongriditem.xml b/mobile/src/main/res/layout/widgetlist_buttongriditem.xml new file mode 100644 index 00000000000..cab703e2a0d --- /dev/null +++ b/mobile/src/main/res/layout/widgetlist_buttongriditem.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/mobile/src/test/java/org/openhab/habdroid/model/ItemTest.kt b/mobile/src/test/java/org/openhab/habdroid/model/ItemTest.kt index 01adf9c0e0a..de1b8904f4b 100644 --- a/mobile/src/test/java/org/openhab/habdroid/model/ItemTest.kt +++ b/mobile/src/test/java/org/openhab/habdroid/model/ItemTest.kt @@ -107,8 +107,8 @@ class ItemTest { @Test fun getCommandOptions() { val sut = itemWithCommandOptions.toItem() - assertEquals(LabeledValue("1", "One", "switch".toOH2IconResource()), sut.options!!.component1()) - assertEquals(LabeledValue("2", "Two", null), sut.options!!.component2()) + assertEquals(LabeledValue("1", "One", "switch".toOH2IconResource(), 1, 2), sut.options!!.component1()) + assertEquals(LabeledValue("2", "Two", null, 0, 0), sut.options!!.component2()) } @Test @@ -209,7 +209,9 @@ class ItemTest { { 'command': '1', 'label': 'One', - 'icon': 'switch' + 'icon': 'switch', + 'row': 1, + 'column': 2 }, { 'command': '2',