settings: improve UI

Make settings follow both edge-to-edge and the liftOnScroll idioms.
This has some minor issues with state, but these should be fixed when
I'm able to make a smooth transition for theme changes.
This commit is contained in:
OxygenCobalt 2021-08-28 18:12:14 -06:00
parent 34ac629659
commit 1b67f6f846
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 118 additions and 89 deletions

View file

@ -18,23 +18,20 @@
package org.oxycblt.auxio.playback.queue
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.applyEdge
import org.oxycblt.auxio.util.isEdgeOn
/**
@ -78,7 +75,13 @@ class QueueFragment : Fragment() {
helper.attachToRecyclerView(this)
}
setupEdgeForQueue(binding)
if (isEdgeOn()) {
binding.applyEdge()
binding.queueAppbar.applyEdge()
binding.queueRecycler.applyEdge()
} else {
binding.root.fitsSystemWindows = true
}
// --- VIEWMODEL SETUP ----
@ -111,64 +114,6 @@ class QueueFragment : Fragment() {
return binding.root
}
private fun setupEdgeForQueue(binding: FragmentQueueBinding) {
if (isEdgeOn()) {
binding.root.setOnApplyWindowInsetsListener { v, insets ->
// Account for the side navigation bar if required.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val bars = insets.getInsets(WindowInsets.Type.systemBars())
v.updatePadding(
left = bars.left,
right = bars.right
)
} else {
@Suppress("DEPRECATION")
v.updatePadding(
left = insets.systemWindowInsetLeft,
right = insets.systemWindowInsetRight
)
}
insets
}
binding.queueAppbar.setOnApplyWindowInsetsListener { v, insets ->
val top = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
insets.getInsets(WindowInsets.Type.systemBars()).top
} else {
@Suppress("DEPRECATION")
insets.systemWindowInsetTop
}
v.updatePadding(top = top)
insets
}
binding.queueRecycler.setOnApplyWindowInsetsListener { v, insets ->
val bottom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
insets.getInsets(WindowInsets.Type.systemBars()).bottom
} else {
@Suppress("DEPRECATION")
insets.systemWindowInsetBottom
}
// Apply bottom padding to make sure that the last queue item isnt incorrectly lost,
// but also make sure that the added padding wont clip the child views entirely.
(v as RecyclerView).apply {
clipToPadding = false
updatePadding(bottom = bottom)
overScrollMode = RecyclerView.OVER_SCROLL_IF_CONTENT_SCROLLS
}
insets
}
} else {
binding.root.fitsSystemWindows = true
}
}
// --- QUEUE DATA ---
/**

View file

@ -25,6 +25,8 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.databinding.FragmentSettingsBinding
import org.oxycblt.auxio.util.applyEdge
import org.oxycblt.auxio.util.isEdgeOn
/**
* A container [Fragment] for the settings menu.
@ -52,6 +54,15 @@ class SettingsFragment : Fragment() {
}
}
if (isEdgeOn()) {
binding.applyEdge()
binding.settingsAppbar.applyEdge()
// We can't apply edge to the RecyclerView from here. Do that in SettingsListFragment.
} else {
binding.root.fitsSystemWindows = true
}
return binding.root
}
}

View file

@ -26,12 +26,15 @@ import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.children
import androidx.recyclerview.widget.RecyclerView
import coil.Coil
import org.oxycblt.auxio.R
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.accent.AccentDialog
import org.oxycblt.auxio.excluded.ExcludedDialog
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.applyEdge
import org.oxycblt.auxio.util.isEdgeOn
import org.oxycblt.auxio.util.isNight
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
@ -54,6 +57,10 @@ class SettingsListFragment : PreferenceFragmentCompat() {
preferenceManager.onDisplayPreferenceDialogListener = this
if (isEdgeOn()) {
view.findViewById<RecyclerView>(androidx.preference.R.id.recycler_view).applyEdge()
}
logD("Fragment created.")
}
@ -117,29 +124,13 @@ class SettingsListFragment : PreferenceFragmentCompat() {
summary = Accent.get().getDetailedSummary(context)
}
SettingsManager.KEY_SHOW_COVERS -> {
SettingsManager.KEY_SHOW_COVERS, SettingsManager.KEY_QUALITY_COVERS -> {
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
Coil.imageLoader(requireContext()).apply {
bitmapPool.clear()
memoryCache.clear()
}
requireActivity().recreate()
true
}
}
SettingsManager.KEY_QUALITY_COVERS -> {
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
// Clear out any cached images, before recreating the activity
Coil.imageLoader(requireContext()).apply {
bitmapPool.clear()
memoryCache.clear()
}
requireActivity().recreate()
true
}
}

View file

@ -23,6 +23,7 @@ import android.content.res.ColorStateList
import android.content.res.Resources
import android.os.Build
import android.util.TypedValue
import android.view.WindowInsets
import android.widget.ImageButton
import android.widget.TextView
import androidx.annotation.AttrRes
@ -30,8 +31,11 @@ import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.google.android.material.appbar.AppBarLayout
import org.oxycblt.auxio.R
/**
@ -139,3 +143,69 @@ fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
* Check if edge-to-edge is on. Really a glorified version check.
*/
fun isEdgeOn(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
/**
* Apply edge-to-edge tweaks to the root view of a layout. This is largely for handling
* edge-to-edge on phone landscape modes.
*/
fun ViewBinding.applyEdge() {
root.setOnApplyWindowInsetsListener { v, insets ->
// Account for the side navigation bar if required.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val bars = insets.getInsets(WindowInsets.Type.systemBars())
v.updatePadding(
left = bars.left,
right = bars.right
)
} else {
@Suppress("DEPRECATION")
v.updatePadding(
left = insets.systemWindowInsetLeft,
right = insets.systemWindowInsetRight
)
}
insets
}
}
/**
* Apply edge-to-edge tweaks to an [AppBarLayout].
*/
fun AppBarLayout.applyEdge() {
setOnApplyWindowInsetsListener { v, insets ->
val top = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
insets.getInsets(WindowInsets.Type.systemBars()).top
} else {
@Suppress("DEPRECATION")
insets.systemWindowInsetTop
}
v.updatePadding(top = top)
insets
}
}
/**
* Apply edge-to-edge tweaks to a [RecyclerView].
*/
fun RecyclerView.applyEdge() {
clipToPadding = false
setOnApplyWindowInsetsListener { v, insets ->
val bottom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
insets.getInsets(WindowInsets.Type.systemBars()).bottom
} else {
@Suppress("DEPRECATION")
insets.systemWindowInsetBottom
}
// Apply bottom padding to make sure that the last queue item isnt incorrectly lost,
// but also make sure that the added padding wont clip the child views entirely.
v.updatePadding(bottom = bottom)
insets
}
}

View file

@ -4,24 +4,36 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context=".settings.SettingsFragment">
<LinearLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/settings_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="?attr/colorSurface"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/settings_toolbar"
style="@style/Widget.Toolbar.Icon.Down"
app:menu="@menu/menu_settings"
app:title="@string/set_title" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/settings_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
android:clickable="true"
android:focusable="true"
app:liftOnScroll="true">
<androidx.appcompat.widget.Toolbar
android:id="@+id/settings_toolbar"
style="@style/Widget.Toolbar.Icon.Down"
app:menu="@menu/menu_settings"
app:title="@string/set_title" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settings_list_fragment"
android:name="org.oxycblt.auxio.settings.SettingsListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>