Change library sort menu to checkable behavior
Switch to a checkable behavior for the library sorting menu instead of manually highlighting the menu items
This commit is contained in:
parent
eab260a9c1
commit
e236eff997
6 changed files with 116 additions and 148 deletions
|
@ -4,7 +4,6 @@ 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 androidx.core.view.forEach
|
|
||||||
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
|
||||||
|
@ -20,13 +19,9 @@ import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.ActionMenu
|
import org.oxycblt.auxio.ui.ActionMenu
|
||||||
import org.oxycblt.auxio.ui.accent
|
|
||||||
import org.oxycblt.auxio.ui.applyColor
|
|
||||||
import org.oxycblt.auxio.ui.getLandscapeSpans
|
import org.oxycblt.auxio.ui.getLandscapeSpans
|
||||||
import org.oxycblt.auxio.ui.isLandscape
|
import org.oxycblt.auxio.ui.isLandscape
|
||||||
import org.oxycblt.auxio.ui.requireCompatActivity
|
import org.oxycblt.auxio.ui.requireCompatActivity
|
||||||
import org.oxycblt.auxio.ui.resolveAttr
|
|
||||||
import org.oxycblt.auxio.ui.toColor
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [Fragment] that shows a custom list of [Genre], [Artist], or [Album] data. Also allows for
|
* A [Fragment] that shows a custom list of [Genre], [Artist], or [Album] data. Also allows for
|
||||||
|
@ -47,16 +42,17 @@ class LibraryFragment : Fragment() {
|
||||||
ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_NONE)
|
ActionMenu(requireCompatActivity(), view, data, ActionMenu.FLAG_NONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
val sortAction = binding.libraryToolbar.menu.findItem(R.id.submenu_sorting)
|
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
|
||||||
binding.libraryToolbar.apply {
|
binding.libraryToolbar.apply {
|
||||||
|
menu.findItem(libraryModel.sortMode.toMenuId()).isChecked = true
|
||||||
|
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.submenu_sorting -> {}
|
R.id.submenu_sorting -> {}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
it.isChecked = true
|
||||||
libraryModel.updateSortMode(it.itemId)
|
libraryModel.updateSortMode(it.itemId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,22 +76,6 @@ class LibraryFragment : Fragment() {
|
||||||
libraryAdapter.updateData(it)
|
libraryAdapter.updateData(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
libraryModel.sortMode.observe(viewLifecycleOwner) { mode ->
|
|
||||||
logD("Updating sort mode to $mode")
|
|
||||||
|
|
||||||
val modeId = mode.toMenuId()
|
|
||||||
|
|
||||||
// Highlight the item instead of using a checkable since the checkables just...wont
|
|
||||||
// respond to any attempts to make them checked or not.
|
|
||||||
sortAction.subMenu.forEach {
|
|
||||||
if (it.itemId == modeId) {
|
|
||||||
it.applyColor(accent.first.toColor(requireContext()))
|
|
||||||
} else {
|
|
||||||
it.applyColor(resolveAttr(requireContext(), android.R.attr.textColorPrimary))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner) {
|
detailModel.navToItem.observe(viewLifecycleOwner) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
libraryModel.updateNavigationStatus(false)
|
libraryModel.updateNavigationStatus(false)
|
||||||
|
|
|
@ -17,15 +17,15 @@ import org.oxycblt.auxio.settings.SettingsManager
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class LibraryViewModel : ViewModel(), SettingsManager.Callback {
|
class LibraryViewModel : ViewModel(), SettingsManager.Callback {
|
||||||
private val mSortMode = MutableLiveData(SortMode.ALPHA_DOWN)
|
|
||||||
val sortMode: LiveData<SortMode> get() = mSortMode
|
|
||||||
|
|
||||||
private val mLibraryData = MutableLiveData(listOf<BaseModel>())
|
private val mLibraryData = MutableLiveData(listOf<BaseModel>())
|
||||||
val libraryData: LiveData<List<BaseModel>> get() = mLibraryData
|
val libraryData: LiveData<List<BaseModel>> get() = mLibraryData
|
||||||
|
|
||||||
private var mIsNavigating = false
|
private var mIsNavigating = false
|
||||||
val isNavigating: Boolean get() = mIsNavigating
|
val isNavigating: Boolean get() = mIsNavigating
|
||||||
|
|
||||||
|
private var mSortMode = SortMode.ALPHA_DOWN
|
||||||
|
val sortMode: SortMode get() = mSortMode
|
||||||
|
|
||||||
private var mDisplayMode = DisplayMode.SHOW_ARTISTS
|
private var mDisplayMode = DisplayMode.SHOW_ARTISTS
|
||||||
|
|
||||||
private val settingsManager = SettingsManager.getInstance()
|
private val settingsManager = SettingsManager.getInstance()
|
||||||
|
@ -36,7 +36,7 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback {
|
||||||
|
|
||||||
// Set up the display/sort modes
|
// Set up the display/sort modes
|
||||||
mDisplayMode = settingsManager.libraryDisplayMode
|
mDisplayMode = settingsManager.libraryDisplayMode
|
||||||
mSortMode.value = settingsManager.librarySortMode
|
mSortMode = settingsManager.librarySortMode
|
||||||
|
|
||||||
updateLibraryData()
|
updateLibraryData()
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,8 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback {
|
||||||
else -> SortMode.NONE
|
else -> SortMode.NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode != mSortMode.value) {
|
if (mode != mSortMode) {
|
||||||
mSortMode.value = mode
|
mSortMode = mode
|
||||||
settingsManager.librarySortMode = mode
|
settingsManager.librarySortMode = mode
|
||||||
|
|
||||||
updateLibraryData()
|
updateLibraryData()
|
||||||
|
@ -92,15 +92,15 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback {
|
||||||
private fun updateLibraryData() {
|
private fun updateLibraryData() {
|
||||||
mLibraryData.value = when (mDisplayMode) {
|
mLibraryData.value = when (mDisplayMode) {
|
||||||
DisplayMode.SHOW_GENRES -> {
|
DisplayMode.SHOW_GENRES -> {
|
||||||
mSortMode.value!!.getSortedGenreList(musicStore.genres)
|
mSortMode.getSortedGenreList(musicStore.genres)
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayMode.SHOW_ARTISTS -> {
|
DisplayMode.SHOW_ARTISTS -> {
|
||||||
mSortMode.value!!.getSortedBaseModelList(musicStore.artists)
|
mSortMode.getSortedBaseModelList(musicStore.artists)
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayMode.SHOW_ALBUMS -> {
|
DisplayMode.SHOW_ALBUMS -> {
|
||||||
mSortMode.value!!.getSortedAlbumList(musicStore.albums)
|
mSortMode.getSortedAlbumList(musicStore.albums)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> error("DisplayMode $mDisplayMode is unsupported.")
|
else -> error("DisplayMode $mDisplayMode is unsupported.")
|
||||||
|
|
|
@ -34,7 +34,6 @@ import org.oxycblt.auxio.ui.toColor
|
||||||
/**
|
/**
|
||||||
* A [Fragment] that allows for the searching of the entire music library.
|
* A [Fragment] that allows for the searching of the entire music library.
|
||||||
* TODO: Add "Recent Searches" & No Results indicator
|
* TODO: Add "Recent Searches" & No Results indicator
|
||||||
* TODO: Filtering
|
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class SearchFragment : Fragment() {
|
class SearchFragment : Fragment() {
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
@file:TargetApi(Build.VERSION_CODES.O_MR1)
|
|
||||||
|
|
||||||
package org.oxycblt.auxio.ui
|
|
||||||
|
|
||||||
import android.annotation.TargetApi
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Point
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.view.WindowManager
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if we are in the "Irregular" landscape mode [e.g landscape, but nav bar is on the sides]
|
|
||||||
* Used to disable most of edge-to-edge if that's the case, as I cant get it to work on this mode.
|
|
||||||
* @return True if we are in the irregular landscape mode, false if not.
|
|
||||||
*/
|
|
||||||
fun Activity.isIrregularLandscape(): Boolean {
|
|
||||||
return isLandscape(resources) &&
|
|
||||||
!isSystemBarOnBottom(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if edge is on. Really a glorified version check.
|
|
||||||
* @return Whether edge is on.
|
|
||||||
*/
|
|
||||||
fun isEdgeOn(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the system bars are on the bottom.
|
|
||||||
* @return If the system bars are on the bottom, false if no.
|
|
||||||
*/
|
|
||||||
private fun isSystemBarOnBottom(activity: Activity): Boolean {
|
|
||||||
val realPoint = Point()
|
|
||||||
val metrics = DisplayMetrics()
|
|
||||||
|
|
||||||
var width = 0
|
|
||||||
var height = 0
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
activity.display?.let { display ->
|
|
||||||
display.getRealSize(realPoint)
|
|
||||||
|
|
||||||
activity.windowManager.currentWindowMetrics.bounds.also {
|
|
||||||
width = it.width()
|
|
||||||
height = it.height()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager).apply {
|
|
||||||
defaultDisplay.getRealSize(realPoint)
|
|
||||||
defaultDisplay.getMetrics(metrics)
|
|
||||||
|
|
||||||
width = metrics.widthPixels
|
|
||||||
height = metrics.heightPixels
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val config = activity.resources.configuration
|
|
||||||
val canMove = (width != height && config.smallestScreenWidthDp < 600)
|
|
||||||
|
|
||||||
return (!canMove || width < height)
|
|
||||||
}
|
|
|
@ -1,13 +1,18 @@
|
||||||
package org.oxycblt.auxio.ui
|
package org.oxycblt.auxio.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.os.Build
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
|
import android.util.DisplayMetrics
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import android.view.WindowManager
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
@ -20,16 +25,7 @@ import androidx.fragment.app.Fragment
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
|
||||||
/**
|
// --- VIEW CONFIGURATION ---
|
||||||
* Apply a text color to a [MenuItem]
|
|
||||||
* @param color The text color that should be applied.
|
|
||||||
*/
|
|
||||||
fun MenuItem.applyColor(@ColorInt color: Int) {
|
|
||||||
SpannableString(title).apply {
|
|
||||||
setSpan(ForegroundColorSpan(color), 0, length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
title = this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable an image button.
|
* Disable an image button.
|
||||||
|
@ -44,40 +40,6 @@ fun ImageButton.disable() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if the device is currently in landscape.
|
|
||||||
* @param resources [Resources] required
|
|
||||||
*/
|
|
||||||
fun isLandscape(resources: Resources): Boolean {
|
|
||||||
return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the span count for most RecyclerViews when in landscape mode.
|
|
||||||
* @return 3 if landscape mode is tablet, 2 if landscape mode is phone
|
|
||||||
*/
|
|
||||||
fun getLandscapeSpans(resources: Resources): Int {
|
|
||||||
return if (resources.configuration.screenLayout == Configuration.SCREENLAYOUT_SIZE_LARGE) 3 else 2
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [Toast] from a [String]
|
|
||||||
* @param context [Context] required to create the toast
|
|
||||||
*/
|
|
||||||
fun String.createToast(context: Context) {
|
|
||||||
Toast.makeText(context.applicationContext, this, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* "Render" a [Spanned] using [HtmlCompat].
|
|
||||||
* @return A [Spanned] that actually works.
|
|
||||||
*/
|
|
||||||
fun Spanned.render(): Spanned {
|
|
||||||
return HtmlCompat.fromHtml(
|
|
||||||
this.toString(), HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a [TextView] text color, without having to resolve the resource.
|
* Set a [TextView] text color, without having to resolve the resource.
|
||||||
*/
|
*/
|
||||||
|
@ -100,6 +62,26 @@ fun MaterialButton.applyAccents(highlighted: Boolean) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- CONVENIENCE ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for getting a plural.
|
||||||
|
* @param pluralsRes Resource for the plural
|
||||||
|
* @param value Int value for the plural.
|
||||||
|
* @return The formatted string requested
|
||||||
|
*/
|
||||||
|
fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String {
|
||||||
|
return resources.getQuantityString(pluralsRes, value, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [Toast] from a [String]
|
||||||
|
* @param context [Context] required to create the toast
|
||||||
|
*/
|
||||||
|
fun String.createToast(context: Context) {
|
||||||
|
Toast.makeText(context.applicationContext, this, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Require an [AppCompatActivity]
|
* Require an [AppCompatActivity]
|
||||||
*/
|
*/
|
||||||
|
@ -114,11 +96,81 @@ fun Fragment.requireCompatActivity(): AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a plural.
|
* "Render" a [Spanned] using [HtmlCompat].
|
||||||
* @param pluralsRes Resource for the plural
|
* @return A [Spanned] that actually works.
|
||||||
* @param value Int value for the plural.
|
|
||||||
* @return The formatted string requested
|
|
||||||
*/
|
*/
|
||||||
fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String {
|
fun Spanned.render(): Spanned {
|
||||||
return resources.getQuantityString(pluralsRes, value, value)
|
return HtmlCompat.fromHtml(
|
||||||
|
this.toString(), HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CONFIGURATION ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if edge is on. Really a glorified version check.
|
||||||
|
* @return Whether edge is on.
|
||||||
|
*/
|
||||||
|
fun isEdgeOn(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the device is currently in landscape.
|
||||||
|
* @param resources [Resources] required
|
||||||
|
*/
|
||||||
|
fun isLandscape(resources: Resources): Boolean {
|
||||||
|
return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the span count for most RecyclerViews when in landscape mode.
|
||||||
|
* @return 3 if landscape mode is tablet, 2 if landscape mode is phone
|
||||||
|
*/
|
||||||
|
fun getLandscapeSpans(resources: Resources): Int {
|
||||||
|
return if (resources.configuration.screenLayout == Configuration.SCREENLAYOUT_SIZE_LARGE) 3 else 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we are in the "Irregular" landscape mode [e.g landscape, but nav bar is on the sides]
|
||||||
|
* Used to disable most of edge-to-edge if that's the case, as I cant get it to work on this mode.
|
||||||
|
* @return True if we are in the irregular landscape mode, false if not.
|
||||||
|
*/
|
||||||
|
fun Activity.isIrregularLandscape(): Boolean {
|
||||||
|
return isLandscape(resources) &&
|
||||||
|
!isSystemBarOnBottom(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the system bars are on the bottom.
|
||||||
|
* @return If the system bars are on the bottom, false if no.
|
||||||
|
*/
|
||||||
|
private fun isSystemBarOnBottom(activity: Activity): Boolean {
|
||||||
|
val realPoint = Point()
|
||||||
|
val metrics = DisplayMetrics()
|
||||||
|
|
||||||
|
var width = 0
|
||||||
|
var height = 0
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
activity.display?.let { display ->
|
||||||
|
display.getRealSize(realPoint)
|
||||||
|
|
||||||
|
activity.windowManager.currentWindowMetrics.bounds.also {
|
||||||
|
width = it.width()
|
||||||
|
height = it.height()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager).apply {
|
||||||
|
defaultDisplay.getRealSize(realPoint)
|
||||||
|
defaultDisplay.getMetrics(metrics)
|
||||||
|
|
||||||
|
width = metrics.widthPixels
|
||||||
|
height = metrics.heightPixels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val config = activity.resources.configuration
|
||||||
|
val canMove = (width != height && config.smallestScreenWidthDp < 600)
|
||||||
|
|
||||||
|
return (!canMove || width < height)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
android:title="@string/label_sort"
|
android:title="@string/label_sort"
|
||||||
app:showAsAction="always">
|
app:showAsAction="always">
|
||||||
<menu>
|
<menu>
|
||||||
<group android:id="@+id/group_sorting">
|
<group android:id="@+id/group_sorting"
|
||||||
|
android:checkableBehavior="single">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/option_sort_none"
|
android:id="@+id/option_sort_none"
|
||||||
android:contentDescription="@string/description_sort_none"
|
android:contentDescription="@string/description_sort_none"
|
||||||
|
|
Loading…
Reference in a new issue