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 package org.oxycblt.auxio.playback.queue
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowInsets
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentQueueBinding import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.applyEdge
import org.oxycblt.auxio.util.isEdgeOn import org.oxycblt.auxio.util.isEdgeOn
/** /**
@ -78,7 +75,13 @@ class QueueFragment : Fragment() {
helper.attachToRecyclerView(this) helper.attachToRecyclerView(this)
} }
setupEdgeForQueue(binding) if (isEdgeOn()) {
binding.applyEdge()
binding.queueAppbar.applyEdge()
binding.queueRecycler.applyEdge()
} else {
binding.root.fitsSystemWindows = true
}
// --- VIEWMODEL SETUP ---- // --- VIEWMODEL SETUP ----
@ -111,64 +114,6 @@ class QueueFragment : Fragment() {
return binding.root 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 --- // --- QUEUE DATA ---
/** /**

View file

@ -25,6 +25,8 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.databinding.FragmentSettingsBinding 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. * 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 return binding.root
} }
} }

View file

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

View file

@ -23,6 +23,7 @@ import android.content.res.ColorStateList
import android.content.res.Resources import android.content.res.Resources
import android.os.Build import android.os.Build
import android.util.TypedValue import android.util.TypedValue
import android.view.WindowInsets
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.TextView import android.widget.TextView
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
@ -30,8 +31,11 @@ import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.google.android.material.appbar.AppBarLayout
import org.oxycblt.auxio.R 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. * Check if edge-to-edge is on. Really a glorified version check.
*/ */
fun isEdgeOn(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 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" xmlns:tools="http://schemas.android.com/tools"
tools:context=".settings.SettingsFragment"> tools:context=".settings.SettingsFragment">
<LinearLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/settings_coordinator"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="?attr/colorSurface" android:background="?attr/colorSurface"
android:orientation="vertical"> android:orientation="vertical">
<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 <androidx.appcompat.widget.Toolbar
android:id="@+id/settings_toolbar" android:id="@+id/settings_toolbar"
style="@style/Widget.Toolbar.Icon.Down" style="@style/Widget.Toolbar.Icon.Down"
app:menu="@menu/menu_settings" app:menu="@menu/menu_settings"
app:title="@string/set_title" /> app:title="@string/set_title" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/settings_list_fragment" android:id="@+id/settings_list_fragment"
android:name="org.oxycblt.auxio.settings.SettingsListFragment" android:name="org.oxycblt.auxio.settings.SettingsListFragment"
android:layout_width="match_parent" 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> </layout>