Update accent dialog

Completely redo the accent dialog to remove its dependence on MaterialDialogs and also to make it more ergonomic.
This commit is contained in:
OxygenCobalt 2021-03-25 19:21:05 -06:00
parent 194bcfcd05
commit ac1e686704
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
13 changed files with 267 additions and 102 deletions

View file

@ -1,4 +1,4 @@
package org.oxycblt.auxio.settings.ui
package org.oxycblt.auxio.settings
import android.content.Intent
import android.os.Bundle

View file

@ -6,7 +6,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.oxycblt.auxio.databinding.FragmentSettingsBinding
import org.oxycblt.auxio.settings.ui.AboutDialog
/**
* A container [Fragment] for the settings menu.

View file

@ -13,8 +13,8 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.settings.accent.AccentDialog
import org.oxycblt.auxio.settings.blacklist.BlacklistDialog
import org.oxycblt.auxio.settings.ui.AccentDialog
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.createToast
@ -35,6 +35,8 @@ class SettingsListFragment : PreferenceFragmentCompat() {
recursivelyHandleChildren(it)
}
preferenceManager.onDisplayPreferenceDialogListener = this
logD("Fragment created.")
}

View file

@ -1,4 +1,4 @@
package org.oxycblt.auxio.settings.ui
package org.oxycblt.auxio.settings.accent
import android.view.ViewGroup
import androidx.appcompat.widget.TooltipCompat
@ -13,11 +13,15 @@ import org.oxycblt.auxio.ui.toStateList
/**
* An adapter that displays the list of all possible accents, and highlights the current one.
* @author OxygenCobalt
* @param onConfirm What to do when an accent is confirmed.
* @param onSelect What to do when an accent is selected.
*/
class AccentAdapter(
private val onConfirm: (accent: Accent) -> Unit
private var curAccent: Accent,
private val onSelect: (accent: Accent) -> Unit
) : RecyclerView.Adapter<AccentAdapter.ViewHolder>() {
private var selectedViewHolder: ViewHolder? = null
override fun getItemCount(): Int = ACCENTS.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@ -28,31 +32,43 @@ class AccentAdapter(
holder.bind(ACCENTS[position])
}
private fun setAccent(accent: Accent) {
curAccent = accent
onSelect(accent)
}
inner class ViewHolder(
private val binding: ItemAccentBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(accent: Accent) {
setSelected(accent == curAccent)
binding.accent.apply {
contentDescription = context.getString(accent.name)
imageTintList = if (accent == Accent.get()) {
isEnabled = false
R.color.background.toStateList(context)
} else {
isEnabled = true
android.R.color.transparent.toStateList(context)
}
backgroundTintList = accent.getStateList(context)
TooltipCompat.setTooltipText(this, contentDescription)
}
setOnClickListener {
onConfirm(accent)
}
binding.accent.setOnClickListener {
setAccent(accent)
setSelected(true)
}
}
private fun setSelected(isSelected: Boolean) {
val context = binding.accent.context
binding.accent.isEnabled = !isSelected
binding.accent.imageTintList = if (isSelected) {
// Switch out the currently selected viewholder with this one.
selectedViewHolder?.setSelected(false)
selectedViewHolder = this
R.color.background.toStateList(context)
} else {
android.R.color.transparent.toStateList(context)
}
}
}

View file

@ -0,0 +1,87 @@
package org.oxycblt.auxio.settings.accent
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.oxycblt.auxio.databinding.DialogAccentBinding
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.ACCENTS
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.inflater
import org.oxycblt.auxio.ui.toColor
/**
* Dialog responsible for showing the list of accents to select.
* @author OxygenCobalt
*/
class AccentDialog : AppCompatDialogFragment() {
private val settingsManager = SettingsManager.getInstance()
private var pendingAccent = Accent.get()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = DialogAccentBinding.inflate(inflater)
savedInstanceState?.getInt(KEY_PENDING_ACCENT)?.let { index ->
pendingAccent = ACCENTS.getOrElse(index) { pendingAccent }
}
// --- UI SETUP ---
binding.accentRecycler.apply {
adapter = AccentAdapter(pendingAccent) { accent ->
pendingAccent = accent
updateAccent(binding)
}
}
binding.accentConfirm.setOnClickListener {
if (pendingAccent != Accent.get()) {
settingsManager.accent = pendingAccent
requireActivity().recreate()
}
dismiss()
}
binding.accentCancel.setOnClickListener {
dismiss()
}
updateAccent(binding)
return binding.root
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(KEY_PENDING_ACCENT, ACCENTS.indexOf(pendingAccent))
}
private fun updateAccent(binding: DialogAccentBinding) {
val accentColor = pendingAccent.color.toColor(requireContext())
binding.accentCancel.setTextColor(accentColor)
binding.accentConfirm.setTextColor(accentColor)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext(), theme)
.setView(onCreateView(requireActivity().inflater, null, savedInstanceState))
.create()
}
companion object {
const val KEY_PENDING_ACCENT = "AXKEY_PEND_ACCENT"
}
}

View file

@ -0,0 +1,47 @@
package org.oxycblt.auxio.settings.accent
import android.content.Context
import android.util.AttributeSet
import android.util.TypedValue
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.max
/**
* A sub-class of [GridLayoutManager] that automatically sets the spans so that they fit the width
* of this dialog.
*/
@Suppress("UNUSED")
class AutoGridLayoutManager(
context: Context,
attrs: AttributeSet,
defStyleAttr: Int,
defStyleRes: Int
) : GridLayoutManager(context, attrs, defStyleAttr, defStyleRes) {
private var columnWidth: Int = 0
private var lastWidth = -1
private var lastHeight = -1
init {
// We use 72dp here since that's the rough size of the accent item, give or take.
// This will need to be modified if this is used beyond the accent dialog.
columnWidth = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 72F,
context.resources.displayMetrics
).toInt()
}
override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
if (width > 0 && height > 0 && (lastWidth != width || lastHeight != height)) {
val totalSpace = width - paddingRight - paddingLeft
val spanCount = max(1, totalSpace / columnWidth)
setSpanCount(spanCount)
}
lastWidth = width
lastHeight = height
super.onLayoutChildren(recycler, state)
}
}

View file

@ -24,7 +24,9 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogBlacklistBinding
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.createToast
import org.oxycblt.auxio.ui.toColor
import java.io.File
import kotlin.system.exitProcess
@ -48,29 +50,48 @@ class BlacklistDialog : BottomSheetDialogFragment() {
): View {
val binding = DialogBlacklistBinding.inflate(inflater)
val accent = Accent.get().color.toColor(requireContext())
val adapter = BlacklistEntryAdapter { path ->
blacklistModel.removePath(path)
}
requireContext().setTheme(Accent.get().theme)
// --- UI SETUP ---
binding.blacklistRecycler.adapter = adapter
binding.blacklistAdd.setOnClickListener {
showFileDialog()
// Dialogs don't know how to into theming, so I have to manually set the accent color
// to each of the buttons since the overall fragment theme is Neutral.
binding.blacklistAdd.apply {
setTextColor(accent)
setOnClickListener {
showFileDialog()
}
}
binding.blacklistCancel.setOnClickListener {
dismiss()
}
binding.blacklistCancel.apply {
setTextColor(accent)
binding.blacklistConfirm.setOnClickListener {
if (blacklistModel.isModified()) {
saveAndRestart()
} else {
setOnClickListener {
dismiss()
}
}
binding.blacklistConfirm.apply {
setTextColor(accent)
setOnClickListener {
if (blacklistModel.isModified()) {
saveAndRestart()
} else {
dismiss()
}
}
}
// --- VIEWMODEL SETUP ---
blacklistModel.paths.observe(viewLifecycleOwner) { paths ->

View file

@ -1,68 +0,0 @@
package org.oxycblt.auxio.settings.ui
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.utils.invalidateDividers
import org.oxycblt.auxio.R
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.ACCENTS
import org.oxycblt.auxio.ui.Accent
/**
* Dialog responsible for showing the list of accents to select.
* TODO: Move this to a Bottom Sheet and eliminate the MaterialDialogs dependency
* @author OxygenCobalt
*/
class AccentDialog : DialogFragment() {
private val settingsManager = SettingsManager.getInstance()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Roll my own RecyclerView since [To no surprise whatsoever] Material Dialogs
// has a bug where ugly dividers will show with the RecyclerView even if you disable them.
// This is why I hate using third party libraries.
val recycler = RecyclerView(requireContext()).apply {
adapter = AccentAdapter { accent ->
if (accent != Accent.get()) {
settingsManager.accent = accent
requireActivity().recreate()
}
dismiss()
}
layoutManager = LinearLayoutManager(
requireContext(), LinearLayoutManager.HORIZONTAL, false
)
post {
// Combine the width of the recyclerview with the width of an item in order
// to center the currently selected accent.
val childWidth = getChildAt(0).width / 2
(layoutManager as LinearLayoutManager)
.scrollToPositionWithOffset(
ACCENTS.indexOf(Accent.get()),
(width / 2) - childWidth
)
}
}
return MaterialDialog(requireActivity())
.title(R.string.setting_accent)
.negativeButton(android.R.string.cancel)
.customView(view = recycler)
.noDividers()
}
private fun MaterialDialog.noDividers(): MaterialDialog {
invalidateDividers(showTop = false, showBottom = false)
return this
}
}

View file

@ -2,7 +2,7 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".settings.ui.AboutDialog">
tools:context=".settings.AboutDialog">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:paddingBottom="@dimen/margin_medium"
android:theme="@style/Theme.Neutral">
<TextView
android:id="@+id/accent_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/inter_exbold"
android:padding="@dimen/padding_medium"
android:text="@string/setting_accent"
android:textAlignment="viewStart"
android:textColor="?attr/colorPrimary"
android:textSize="@dimen/text_size_toolbar_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/accent_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foregroundGravity="center"
android:overScrollMode="never"
app:layoutManager="org.oxycblt.auxio.settings.accent.AutoGridLayoutManager"
app:layout_constraintTop_toBottomOf="@+id/accent_header"
tools:spanCount="6"
tools:itemCount="18"
tools:listitem="@layout/item_accent" />
<Button
android:id="@+id/accent_cancel"
style="@style/Widget.Button.Dialog"
android:layout_marginTop="@dimen/margin_medium"
android:layout_marginEnd="@dimen/padding_small"
android:text="@android:string/cancel"
app:layout_constraintEnd_toStartOf="@+id/accent_confirm"
app:layout_constraintTop_toBottomOf="@+id/accent_recycler" />
<Button
android:id="@+id/accent_confirm"
style="@style/Widget.Button.Dialog"
android:layout_marginEnd="@dimen/margin_medium"
android:text="@android:string/ok"
app:layout_constraintBottom_toBottomOf="@+id/accent_cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/accent_cancel" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,17 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".settings.ui.AccentAdapter.ViewHolder">
tools:context=".settings.pref.AccentAdapter.ViewHolder">
<FrameLayout
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_mid_small">
android:padding="@dimen/margin_small">
<ImageButton
android:id="@+id/accent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/ui_circle_ripple"
android:padding="@dimen/margin_medium"
android:scaleType="fitCenter"

View file

@ -19,6 +19,7 @@
<item name="colorControlHighlight">@color/selection_color</item>
<item name="colorControlActivated">?attr/colorPrimary</item>
<item name="cornerRadius">0dp</item>
<item name="colorSurface">@color/background</item>
<item name="md_background_color">@color/background</item>
<item name="md_corner_radius">0dp</item>
@ -231,5 +232,6 @@
<item name="android:paddingEnd">@dimen/padding_mid_small</item>
<item name="android:minWidth">@dimen/width_dialog_button_min</item>
<item name="android:background">@drawable/ui_ripple</item>
<item name="android:fontFamily">@font/inter_semibold</item>
</style>
</resources>

View file

@ -143,5 +143,6 @@
app:iconSpaceReserved="false"
app:key="KEY_BLACKLIST"
app:summary="@string/setting_content_blacklist_desc" />
</PreferenceCategory>
</PreferenceScreen>