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:
parent
34ac629659
commit
1b67f6f846
5 changed files with 118 additions and 89 deletions
|
@ -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 ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/settings_toolbar"
|
android:id="@+id/settings_appbar"
|
||||||
style="@style/Widget.Toolbar.Icon.Down"
|
android:layout_width="match_parent"
|
||||||
app:menu="@menu/menu_settings"
|
android:layout_height="wrap_content"
|
||||||
app:title="@string/set_title" />
|
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
|
<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>
|
Loading…
Reference in a new issue