Skip to content

Commit

Permalink
Merge pull request chenxiaolong#55 from chenxiaolong/chip-group
Browse files Browse the repository at this point in the history
Use material3 chips instead of a button group for the output format selector
  • Loading branch information
chenxiaolong authored May 30, 2022
2 parents b5273b5 + dcc87ae commit ef6ccb5
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 33 deletions.
4 changes: 4 additions & 0 deletions app/magisk/updates/release/changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### Unreleased

* Change output format button group to material chips to prevent text from being cut off with narrower screen widths (Issue: #52, PR: #55, @chenxiaolong)

### Version 1.6

* Enable minification (without obfuscation) to shrink the download size by ~64% (PR: #45, @chenxiaolong)
Expand Down
140 changes: 140 additions & 0 deletions app/src/main/java/com/chiller3/bcr/ChipGroupCentered.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.chiller3.bcr

import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.core.view.ViewCompat
import com.google.android.material.chip.ChipGroup
import java.lang.Integer.max
import java.lang.Integer.min

/** Hacky wrapper around [ChipGroup] to make every row individually centered. */
class ChipGroupCentered(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
ChipGroup(context, attrs, defStyleAttr) {
private val _rowCountField = javaClass.superclass.superclass.getDeclaredField("rowCount")
private var rowCountField
get() = _rowCountField.getInt(this)
set(value) = _rowCountField.setInt(this, value)

init {
_rowCountField.isAccessible = true
}

constructor(context: Context, attrs: AttributeSet?) :
this(context, attrs, com.google.android.material.R.attr.chipGroupStyle)

constructor(context: Context) : this(context, null)

@SuppressLint("RestrictedApi")
override fun onLayout(sizeChanged: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
if (isSingleLine) {
return super.onLayout(sizeChanged, left, top, right, bottom)
}

val maxWidth = right - left - paddingRight - paddingLeft
var offsetTop = paddingTop
var rowStartIndex = 0

while (rowStartIndex < childCount) {
val (rowEndIndex, rowWidth, rowHeight) = getFittingRow(rowStartIndex, maxWidth)

layoutRow(
rowStartIndex..rowEndIndex,
paddingLeft + (maxWidth - rowWidth) / 2,
offsetTop,
rowCountField,
)

offsetTop += rowHeight + lineSpacing
rowStartIndex = rowEndIndex + 1
rowCountField += 1
}
}

/**
* Find the last index starting from [indexStart] that will fit in the row.
*
* @return (Index of last fitting element, width of row, height of row)
*/
@SuppressLint("RestrictedApi")
private fun getFittingRow(indexStart: Int, maxWidth: Int): Triple<Int, Int, Int> {
var indexEnd = indexStart
var childStart = 0
var rowHeight = 0

while (true) {
val child = getChildAt(indexEnd)
if (child.visibility == GONE) {
continue
}

val (marginStart, marginEnd) = getMargins(child)
val childWidth = marginStart + child.measuredWidth + marginEnd
val separator = if (indexEnd > indexStart) { itemSpacing } else { 0 }

// If even one child can't fit, force it to do so anyway
if (indexEnd != indexStart && childStart + separator + childWidth > maxWidth) {
--indexEnd
break
}

childStart += separator + childWidth
rowHeight = max(rowHeight, child.measuredHeight)

if (indexEnd == childCount - 1) {
break
} else {
++indexEnd
}
}

return Triple(indexEnd, min(childStart, maxWidth), rowHeight)
}

/**
* Lay out [childIndices] children in a row positioned at [offsetLeft] and [offsetTop].
*/
@SuppressLint("RestrictedApi")
private fun layoutRow(childIndices: IntRange, offsetLeft: Int, offsetTop: Int, rowIndex: Int) {
val range = if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
childIndices.reversed()
} else {
childIndices
}
var childStart = offsetLeft

for (i in range) {
val child = getChildAt(i)
if (child.visibility == GONE) {
child.setTag(com.google.android.material.R.id.row_index_key, -1)
continue
} else {
child.setTag(com.google.android.material.R.id.row_index_key, rowIndex)
}

val (marginStart, marginEnd) = getMargins(child)

child.layout(
childStart + marginStart,
offsetTop,
childStart + marginStart + child.measuredWidth,
offsetTop + child.measuredHeight,
)

childStart += marginStart + child.measuredWidth + marginEnd + itemSpacing
}
}

companion object {
private fun getMargins(view: View): Pair<Int, Int> {
val lp = view.layoutParams

return if (lp is MarginLayoutParams) {
Pair(lp.marginStart, lp.marginEnd)
} else {
Pair(0, 0)
}
}
}
}
46 changes: 20 additions & 26 deletions app/src/main/java/com/chiller3/bcr/FormatBottomSheetFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,24 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import com.chiller3.bcr.databinding.FormatBottomSheetBinding
import com.chiller3.bcr.databinding.FormatBottomSheetButtonBinding
import com.chiller3.bcr.databinding.FormatBottomSheetChipBinding
import com.chiller3.bcr.format.*
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.chip.ChipGroup
import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider

class FormatBottomSheetFragment : BottomSheetDialogFragment(),
MaterialButtonToggleGroup.OnButtonCheckedListener, LabelFormatter, Slider.OnChangeListener,
ChipGroup.OnCheckedStateChangeListener, LabelFormatter, Slider.OnChangeListener,
View.OnClickListener {
private var _binding: FormatBottomSheetBinding? = null
private val binding
get() = _binding!!

private val buttonIdToFormat = HashMap<Int, Format>()
private val formatToButtonId = HashMap<Format, Int>()
private val chipIdToFormat = HashMap<Int, Format>()
private val formatToChipId = HashMap<Format, Int>()
private lateinit var formatParamInfo: FormatParamInfo

override fun onCreateView(
Expand All @@ -42,17 +41,18 @@ class FormatBottomSheetFragment : BottomSheetDialogFragment(),
continue
}

val buttonBinding = FormatBottomSheetButtonBinding.inflate(
val chipBinding = FormatBottomSheetChipBinding.inflate(
inflater, binding.nameGroup, false)
val id = ViewCompat.generateViewId()
buttonBinding.root.id = id
buttonBinding.root.text = format.name
binding.nameGroup.addView(buttonBinding.root)
buttonIdToFormat[id] = format
formatToButtonId[format] = id
val id = View.generateViewId()
chipBinding.root.id = id
chipBinding.root.text = format.name
chipBinding.root.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
binding.nameGroup.addView(chipBinding.root)
chipIdToFormat[id] = format
formatToChipId[format] = id
}

binding.nameGroup.addOnButtonCheckedListener(this)
binding.nameGroup.setOnCheckedStateChangeListener(this)

refreshFormat()

Expand All @@ -67,11 +67,11 @@ class FormatBottomSheetFragment : BottomSheetDialogFragment(),
/**
* Update UI based on currently selected format in the preferences.
*
* Calls [refreshParam] via [onButtonChecked].
* Calls [refreshParam] via [onCheckedChanged].
*/
private fun refreshFormat() {
val (format, _) = Formats.fromPreferences(requireContext())
binding.nameGroup.check(formatToButtonId[format]!!)
binding.nameGroup.check(formatToChipId[format]!!)
}

/**
Expand Down Expand Up @@ -109,15 +109,9 @@ class FormatBottomSheetFragment : BottomSheetDialogFragment(),
}
}

override fun onButtonChecked(
group: MaterialButtonToggleGroup?,
checkedId: Int,
isChecked: Boolean
) {
if (isChecked) {
Preferences.setFormatName(requireContext(), buttonIdToFormat[checkedId]!!.name)
refreshParam()
}
override fun onCheckedChanged(group: ChipGroup, checkedIds: MutableList<Int>) {
Preferences.setFormatName(requireContext(), chipIdToFormat[checkedIds.first()]!!.name)
refreshParam()
}

override fun getFormattedValue(value: Float): String =
Expand All @@ -126,7 +120,7 @@ class FormatBottomSheetFragment : BottomSheetDialogFragment(),
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
when (slider) {
binding.paramSlider -> {
val format = buttonIdToFormat[binding.nameGroup.checkedButtonId]!!
val format = chipIdToFormat[binding.nameGroup.checkedChipId]!!
Preferences.setFormatParam(requireContext(), format.name, value.toUInt())
}
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/res/layout/format_bottom_sheet.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
android:text="@string/bottom_sheet_output_format"
android:textAppearance="?attr/textAppearanceHeadline6" />

<com.google.android.material.button.MaterialButtonToggleGroup
<com.chiller3.bcr.ChipGroupCentered
android:id="@+id/name_group"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:selectionRequired="true"
app:singleSelection="true" />
Expand Down
5 changes: 0 additions & 5 deletions app/src/main/res/layout/format_bottom_sheet_button.xml

This file was deleted.

5 changes: 5 additions & 0 deletions app/src/main/res/layout/format_bottom_sheet_chip.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.chip.Chip xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

0 comments on commit ef6ccb5

Please sign in to comment.