Update material components

This commit is contained in:
Koitharu
2022-02-12 14:07:06 +02:00
parent 98f723200b
commit 4098f06995
14 changed files with 363 additions and 81 deletions

View File

@@ -0,0 +1,42 @@
package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.annotation.AttrRes
import androidx.annotation.IdRes
import androidx.core.view.children
import com.google.android.material.button.MaterialButton
class CheckableButtonGroup @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = 0,
) : LinearLayout(context, attrs, defStyleAttr), View.OnClickListener {
var onCheckedChangeListener: OnCheckedChangeListener? = null
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
if (child is MaterialButton) {
child.setOnClickListener(this)
}
super.addView(child, index, params)
}
override fun onClick(v: View) {
setCheckedId(v.id)
}
fun setCheckedId(@IdRes viewRes: Int) {
children.forEach {
(it as? MaterialButton)?.isChecked = it.id == viewRes
}
onCheckedChangeListener?.onCheckedChanged(this, viewRes)
}
fun interface OnCheckedChangeListener {
fun onCheckedChanged(group: CheckableButtonGroup, checkedId: Int)
}
}

View File

@@ -4,31 +4,25 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.databinding.DialogListModeBinding
import org.koitharu.kotatsu.utils.ext.setValueRounded
import org.koitharu.kotatsu.utils.progress.IntPercentLabelFormatter
class ListModeSelectDialog : AlertDialogFragment<DialogListModeBinding>(), View.OnClickListener,
SeekBar.OnSeekBarChangeListener {
class ListModeSelectDialog : AlertDialogFragment<DialogListModeBinding>(),
CheckableButtonGroup.OnCheckedChangeListener, Slider.OnSliderTouchListener {
private val settings by inject<AppSettings>(mode = LazyThreadSafetyMode.NONE)
private var mode: ListMode = ListMode.GRID
private var pendingGridSize: Int = 100
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mode = settings.listMode
pendingGridSize = settings.gridSize
}
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
@@ -42,51 +36,42 @@ class ListModeSelectDialog : AlertDialogFragment<DialogListModeBinding>(), View.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val mode = settings.listMode
binding.buttonList.isChecked = mode == ListMode.LIST
binding.buttonListDetailed.isChecked = mode == ListMode.DETAILED_LIST
binding.buttonGrid.isChecked = mode == ListMode.GRID
binding.textViewGridTitle.isVisible = mode == ListMode.GRID
binding.seekbarGrid.isVisible = mode == ListMode.GRID
binding.sliderGrid.isVisible = mode == ListMode.GRID
with(binding.seekbarGrid) {
progress = pendingGridSize - 50
setOnSeekBarChangeListener(this@ListModeSelectDialog)
}
binding.sliderGrid.setLabelFormatter(IntPercentLabelFormatter())
binding.sliderGrid.setValueRounded(settings.gridSize.toFloat())
binding.sliderGrid.addOnSliderTouchListener(this)
binding.buttonList.setOnClickListener(this)
binding.buttonGrid.setOnClickListener(this)
binding.buttonListDetailed.setOnClickListener(this)
binding.checkableGroup.onCheckedChangeListener = this
}
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
pendingGridSize = progress + 50
}
override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit
override fun onStopTrackingTouch(seekBar: SeekBar?) {
settings.gridSize = pendingGridSize
}
override fun onClick(v: View) {
when (v.id) {
R.id.button_list -> mode = ListMode.LIST
R.id.button_list_detailed -> mode = ListMode.DETAILED_LIST
R.id.button_grid -> mode = ListMode.GRID
override fun onCheckedChanged(group: CheckableButtonGroup, checkedId: Int) {
val mode = when (checkedId) {
R.id.button_list -> ListMode.LIST
R.id.button_list_detailed -> ListMode.DETAILED_LIST
R.id.button_grid -> ListMode.GRID
else -> return
}
binding.textViewGridTitle.isVisible = mode == ListMode.GRID
binding.seekbarGrid.isVisible = mode == ListMode.GRID
binding.sliderGrid.isVisible = mode == ListMode.GRID
settings.listMode = mode
}
override fun onStartTrackingTouch(slider: Slider) = Unit
override fun onStopTrackingTouch(slider: Slider) {
settings.gridSize = slider.value.toInt()
}
companion object {
private const val TAG = "ListModeSelectDialog"
fun show(fm: FragmentManager) = ListModeSelectDialog()
.show(
fm,
TAG
)
fun show(fm: FragmentManager) = ListModeSelectDialog().show(fm, TAG)
}
}

View File

@@ -9,12 +9,13 @@ import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.databinding.DialogReaderConfigBinding
import org.koitharu.kotatsu.utils.ext.withArgs
class ReaderConfigDialog : AlertDialogFragment<DialogReaderConfigBinding>(),
View.OnClickListener {
CheckableButtonGroup.OnCheckedChangeListener {
private lateinit var mode: ReaderMode
@@ -42,9 +43,7 @@ class ReaderConfigDialog : AlertDialogFragment<DialogReaderConfigBinding>(),
binding.buttonReversed.isChecked = mode == ReaderMode.REVERSED
binding.buttonWebtoon.isChecked = mode == ReaderMode.WEBTOON
binding.buttonStandard.setOnClickListener(this)
binding.buttonReversed.setOnClickListener(this)
binding.buttonWebtoon.setOnClickListener(this)
binding.checkableGroup.onCheckedChangeListener = this
}
override fun onDismiss(dialog: DialogInterface) {
@@ -53,11 +52,12 @@ class ReaderConfigDialog : AlertDialogFragment<DialogReaderConfigBinding>(),
super.onDismiss(dialog)
}
override fun onClick(v: View) {
when (v.id) {
R.id.button_standard -> mode = ReaderMode.STANDARD
R.id.button_webtoon -> mode = ReaderMode.WEBTOON
R.id.button_reversed -> mode = ReaderMode.REVERSED
override fun onCheckedChanged(group: CheckableButtonGroup, checkedId: Int) {
mode = when (checkedId) {
R.id.button_standard -> ReaderMode.STANDARD
R.id.button_webtoon -> ReaderMode.WEBTOON
R.id.button_reversed -> ReaderMode.REVERSED
else -> return
}
}

View File

@@ -8,7 +8,10 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.*
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreference
import leakcanary.LeakCanary
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
@@ -17,6 +20,7 @@ import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity
import org.koitharu.kotatsu.settings.utils.SliderPreference
import org.koitharu.kotatsu.utils.ext.getStorageName
import org.koitharu.kotatsu.utils.ext.names
import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat
@@ -35,7 +39,7 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_main)
findPreference<SeekBarPreference>(AppSettings.KEY_GRID_SIZE)?.run {
findPreference<SliderPreference>(AppSettings.KEY_GRID_SIZE)?.run {
summary = "%d%%".format(value)
setOnPreferenceChangeListener { preference, newValue ->
preference.summary = "%d%%".format(newValue)

View File

@@ -0,0 +1,155 @@
package org.koitharu.kotatsu.settings.utils
import android.content.Context
import android.content.res.TypedArray
import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import android.view.View
import androidx.core.content.withStyledAttributes
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.google.android.material.slider.Slider
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.ext.setValueRounded
class SliderPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.sliderPreferenceStyle,
defStyleRes: Int = R.style.Preference_Slider,
) : Preference(context, attrs, defStyleAttr, defStyleRes) {
private var valueFrom: Int = 0
private var valueTo: Int = 100
private var stepSize: Int = 1
private var currentValue: Int = 0
var value: Int
get() = currentValue
set(value) = setValueInternal(value, notifyChanged = true)
private val sliderListener = Slider.OnChangeListener { _, value, fromUser ->
if (fromUser) {
syncValueInternal(value.toInt())
}
}
init {
context.withStyledAttributes(attrs,
R.styleable.SliderPreference,
defStyleAttr,
defStyleRes) {
valueFrom = getFloat(R.styleable.SliderPreference_android_valueFrom,
valueFrom.toFloat()).toInt()
valueTo =
getFloat(R.styleable.SliderPreference_android_valueTo, valueTo.toFloat()).toInt()
stepSize =
getFloat(R.styleable.SliderPreference_android_stepSize, stepSize.toFloat()).toInt()
}
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val slider = holder.findViewById(R.id.slider) as? Slider ?: return
slider.removeOnChangeListener(sliderListener)
slider.addOnChangeListener(sliderListener)
slider.valueFrom = valueFrom.toFloat()
slider.valueTo = valueTo.toFloat()
slider.stepSize = stepSize.toFloat()
slider.setValueRounded(currentValue.toFloat())
slider.isEnabled = isEnabled
}
override fun onSetInitialValue(defaultValue: Any?) {
value = getPersistedInt(defaultValue as? Int ?: 0)
}
override fun onGetDefaultValue(a: TypedArray, index: Int): Any {
return a.getInt(index, 0)
}
override fun onSaveInstanceState(): Parcelable? {
val superState = super.onSaveInstanceState()
if (superState == null || isPersistent) {
return superState
}
return SavedState(
superState = superState,
valueFrom = valueFrom,
valueTo = valueTo,
currentValue = currentValue,
)
}
override fun onRestoreInstanceState(state: Parcelable?) {
if (state !is SavedState) {
super.onRestoreInstanceState(state)
return
}
super.onRestoreInstanceState(state.superState)
valueFrom = state.valueFrom
valueTo = state.valueTo
currentValue = state.currentValue
notifyChanged()
}
private fun setValueInternal(sliderValue: Int, notifyChanged: Boolean) {
val newValue = sliderValue.coerceIn(valueFrom, valueTo)
if (newValue != currentValue) {
currentValue = newValue
persistInt(newValue)
if (notifyChanged) {
notifyChanged()
}
}
}
private fun syncValueInternal(sliderValue: Int) {
if (sliderValue != currentValue) {
if (callChangeListener(sliderValue)) {
setValueInternal(sliderValue, notifyChanged = false)
}
}
}
private class SavedState : View.BaseSavedState {
val valueFrom: Int
val valueTo: Int
val currentValue: Int
constructor(
superState: Parcelable,
valueFrom: Int,
valueTo: Int,
currentValue: Int,
) : super(superState) {
this.valueFrom = valueFrom
this.valueTo = valueTo
this.currentValue = currentValue
}
constructor(source: Parcel) : super(source) {
valueFrom = source.readInt()
valueTo = source.readInt()
currentValue = source.readInt()
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeInt(valueFrom)
out.writeInt(valueTo)
out.writeInt(currentValue)
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(`in`: Parcel) = SavedState(`in`)
override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)
}
}
}
}

View File

@@ -18,7 +18,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.progressindicator.BaseProgressIndicator
import com.google.android.material.slider.Slider
import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder
import kotlin.math.roundToInt
fun View.hideKeyboard() {
val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
@@ -200,4 +202,9 @@ fun resolveAdjustedSize(
// This should not happen
desiredSize
}
}
fun Slider.setValueRounded(newValue: Float) {
val step = stepSize
value = (newValue / step).roundToInt() * step
}

View File

@@ -0,0 +1,7 @@
package org.koitharu.kotatsu.utils.progress
import com.google.android.material.slider.LabelFormatter
class IntPercentLabelFormatter : LabelFormatter {
override fun getFormattedValue(value: Float) = "%d%%".format(value.toInt())
}