Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement buttongrid #3546

Merged
merged 7 commits into from Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
@@ -0,0 +1 @@
.idea/ linguist-generated=false
1 change: 1 addition & 0 deletions .idea/dictionaries/openhab.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions mobile/src/main/java/org/openhab/habdroid/model/LabeledValue.kt
Expand Up @@ -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"))
}
3 changes: 2 additions & 1 deletion mobile/src/main/java/org/openhab/habdroid/model/Widget.kt
Expand Up @@ -116,6 +116,7 @@ data class Widget(
Video,
Webview,
Input,
Buttongrid,
Unknown
}

Expand Down Expand Up @@ -303,7 +304,7 @@ fun Node.collectWidgets(parent: Widget?): List<Widget> {
"label" -> mappingLabel = childNode.textContent
}
}
mappings.add(LabeledValue(mappingCommand, mappingLabel, null))
mappings.add(LabeledValue(mappingCommand, mappingLabel, null, 0, 0))
}
else -> {}
}
Expand Down
Expand Up @@ -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
Expand Down
62 changes: 60 additions & 2 deletions mobile/src/main/java/org/openhab/habdroid/ui/WidgetAdapter.kt
Expand Up @@ -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
Expand All @@ -43,11 +44,11 @@ import androidx.core.content.ContextCompat
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
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.RecyclerView
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.datasource.DataSource
Expand All @@ -58,6 +59,7 @@ import androidx.media3.exoplayer.hls.HlsMediaSource
import androidx.media3.exoplayer.source.LoadEventInfo
import androidx.media3.exoplayer.source.MediaLoadData
import androidx.media3.exoplayer.source.ProgressiveMediaSource
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.datepicker.MaterialDatePicker
Expand All @@ -74,6 +76,7 @@ import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.time.temporal.ChronoUnit
import java.util.Locale
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -780,6 +785,58 @@ 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)
private val spareViews = mutableListOf<MaterialButton>()
private val maxColumns = itemView.resources.getInteger(R.integer.section_switch_max_buttons)

override fun bind(widget: Widget) {
super.bind(widget)

val showLabelAndIcon = widget.label.isNotEmpty()
&& widget.labelSource == Widget.LabelSource.SitemapDefinition
labelView.isVisible = showLabelAndIcon
iconView.isVisible = showLabelAndIcon

val mappings = widget.mappings.filter { it.column != 0 && it.row != 0 }
spareViews.addAll(table.children.map { it as? MaterialButton }.filterNotNull())
table.removeAllViews()

table.rowCount = mappings.maxOfOrNull { it.row } ?: 0
table.columnCount = min(mappings.maxOfOrNull { it.column } ?: 0, maxColumns)
(0 until table.rowCount).forEach { row ->
(0 until table.columnCount).forEach { column ->
val buttonView = spareViews.removeFirstOrNull() ?:
initData.inflater.inflate(R.layout.widgetlist_sectionswitchitem_button, table, false)
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 }
// Create invisible buttons if there's no mapping so each cell has an equal size
buttonView.isInvisible = mapping == null
if (mapping != null) {
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 {
Expand Down Expand Up @@ -1555,7 +1612,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)
Expand Down
18 changes: 18 additions & 0 deletions mobile/src/main/res/layout/widgetlist_buttongriditem.xml
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
style="@style/WidgetListItemContainer">

<include layout="@layout/widgetlist_icontext_for_heavy_data" />

<GridLayout
android:id="@+id/widget_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:useDefaultMargins="true" />

</LinearLayout>
8 changes: 5 additions & 3 deletions mobile/src/test/java/org/openhab/habdroid/model/ItemTest.kt
Expand Up @@ -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
Expand Down Expand Up @@ -209,7 +209,9 @@ class ItemTest {
{
'command': '1',
'label': 'One',
'icon': 'switch'
'icon': 'switch',
'row': 1,
'column': 2
},
{
'command': '2',
Expand Down