Update library searching

Reorder how library items are displayed when searching, no longer hiding genres/artists depending on the DisplayMode.
This commit is contained in:
OxygenCobalt 2021-01-07 09:25:58 -07:00
parent 22ab5ad255
commit 406ba212f8
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
13 changed files with 102 additions and 94 deletions

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.oxycblt.auxio">
<application
android:label="Auxio Debug"
tools:replace="android:label"/>
</manifest>

View file

@ -1,27 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="M54 29.83v28.333c-1.585-0.913-3.41-1.477-5.371-1.477-5.935 0-10.743 4.807-10.743 10.743 0 5.935 4.807 10.742 10.743 10.742 5.935 0 10.743-4.807 10.743-10.742V40.573h10.742V29.831z"
android:strokeWidth="0.890156"
android:strokeColor="#ffffff">
<aapt:attr name="android:fillColor">
<gradient
android:endX="64.75"
android:endY="28.5"
android:startX="60"
android:startY="61.5"
android:type="linear">
<item
android:color="#2196f3"
android:offset="0.0" />
<item
android:color="#90caf9"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
</vector>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Auxio Debug</string>
</resources>

View file

@ -41,6 +41,8 @@ import org.oxycblt.auxio.ui.toColor
* search functionality. * search functionality.
* FIXME: Heisenleak when navving from search * FIXME: Heisenleak when navving from search
* FIXME: Heisenleak on older versions * FIXME: Heisenleak on older versions
* TODO: Filtering & Search order upgrades
* TODO: Show result counts?
*/ */
class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {

View file

@ -61,44 +61,80 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback {
return return
} }
// Search MusicStore for all the items [Artists, Albums, Songs] that contain
// the query, and update the LiveData with those items. This is done on a separate
// thread as it can be a very long operation for large music libraries.
viewModelScope.launch { viewModelScope.launch {
val combined = mutableListOf<BaseModel>() val combined = mutableListOf<BaseModel>()
val children = mDisplayMode.getChildren()
// If the Library DisplayMode supports it, include artists / genres in the search. // Searching is done in a different order depending on which items are being shown
if (children.contains(DisplayMode.SHOW_GENRES)) { // E.G If albums are being shown, then they will be the first items on the list.
val genres = musicStore.genres.filter { it.name.contains(query, true) } when (mDisplayMode) {
DisplayMode.SHOW_GENRES -> {
if (genres.isNotEmpty()) { searchForGenres(combined, query, context)
combined.add(Header(name = context.getString(R.string.label_genres))) searchForArtists(combined, query, context)
combined.addAll(genres) searchForAlbums(combined, query, context)
} }
}
if (children.contains(DisplayMode.SHOW_ARTISTS)) { DisplayMode.SHOW_ARTISTS -> {
val artists = musicStore.artists.filter { it.name.contains(query, true) } searchForArtists(combined, query, context) -
searchForAlbums(combined, query, context)
if (artists.isNotEmpty()) { searchForGenres(combined, query, context)
combined.add(Header(name = context.getString(R.string.label_artists)))
combined.addAll(artists)
} }
}
// Albums & Songs are always included. DisplayMode.SHOW_ALBUMS -> {
val albums = musicStore.albums.filter { it.name.contains(query, true) } searchForAlbums(combined, query, context)
searchForArtists(combined, query, context)
if (albums.isNotEmpty()) { searchForGenres(combined, query, context)
combined.add(Header(name = context.getString(R.string.label_albums))) }
combined.addAll(albums)
} }
mSearchResults.value = combined mSearchResults.value = combined
} }
} }
private fun searchForGenres(
data: MutableList<BaseModel>,
query: String,
context: Context
): MutableList<BaseModel> {
val genres = musicStore.genres.filter { it.name.contains(query, true) }
if (genres.isNotEmpty()) {
data.add(Header(id = 0, name = context.getString(R.string.label_genres)))
data.addAll(genres)
}
return data
}
private fun searchForArtists(
data: MutableList<BaseModel>,
query: String,
context: Context
): MutableList<BaseModel> {
val artists = musicStore.artists.filter { it.name.contains(query, true) }
if (artists.isNotEmpty()) {
data.add(Header(id = 1, name = context.getString(R.string.label_artists)))
data.addAll(artists)
}
return data
}
private fun searchForAlbums(
data: MutableList<BaseModel>,
query: String,
context: Context
): MutableList<BaseModel> {
val albums = musicStore.albums.filter { it.name.contains(query, true) }
if (albums.isNotEmpty()) {
data.add(Header(id = 2, name = context.getString(R.string.label_albums)))
data.addAll(albums)
}
return data
}
fun resetQuery() { fun resetQuery() {
mSearchResults.value = listOf() mSearchResults.value = listOf()
} }

View file

@ -10,6 +10,7 @@ import android.widget.TextView
import androidx.databinding.BindingAdapter import androidx.databinding.BindingAdapter
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.recycler.SortMode import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.ui.getPlural
/** /**
* List of ID3 genres + Winamp extensions, each index corresponds to their int value. * List of ID3 genres + Winamp extensions, each index corresponds to their int value.
@ -120,12 +121,8 @@ fun TextView.bindArtistGenre(artist: Artist) {
*/ */
@BindingAdapter("artistCounts") @BindingAdapter("artistCounts")
fun TextView.bindArtistCounts(artist: Artist) { fun TextView.bindArtistCounts(artist: Artist) {
val albums = context.resources.getQuantityString( val albums = context.getPlural(R.plurals.format_album_count, artist.albums.size)
R.plurals.format_album_count, artist.albums.size, artist.albums.size val songs = context.getPlural(R.plurals.format_song_count, artist.songs.size)
)
val songs = context.resources.getQuantityString(
R.plurals.format_song_count, artist.songs.size, artist.songs.size
)
text = context.getString(R.string.format_double_counts, albums, songs) text = context.getString(R.string.format_double_counts, albums, songs)
} }
@ -138,10 +135,7 @@ fun TextView.bindAllAlbumDetails(album: Album) {
text = context.getString( text = context.getString(
R.string.format_double_info, R.string.format_double_info,
album.year.toYear(context), album.year.toYear(context),
context.resources.getQuantityString( context.getPlural(R.plurals.format_song_count, album.songs.size),
R.plurals.format_song_count,
album.songs.size, album.songs.size
),
album.totalDuration album.totalDuration
) )
} }
@ -154,10 +148,7 @@ fun TextView.bindAlbumInfo(album: Album) {
text = context.getString( text = context.getString(
R.string.format_info, R.string.format_info,
album.artist.name, album.artist.name,
context.resources.getQuantityString( context.getPlural(R.plurals.format_song_count, album.songs.size),
R.plurals.format_song_count,
album.songs.size, album.songs.size
)
) )
} }

View file

@ -20,14 +20,17 @@ import org.oxycblt.auxio.settings.SettingsManager
import kotlin.random.Random import kotlin.random.Random
/** /**
* Master class for the playback state. This should ***not*** be used outside of the playback module. * Master class (and possible god object) for the playback state.
*
* This should ***NOT*** be used outside of the playback module.
* - If you want to use the playback state in the UI, use [org.oxycblt.auxio.playback.PlaybackViewModel]. * - If you want to use the playback state in the UI, use [org.oxycblt.auxio.playback.PlaybackViewModel].
* - If you want to use the playback state with the ExoPlayer instance or system-side things, * - If you want to use the playback state with the ExoPlayer instance or system-side things,
* use [org.oxycblt.auxio.playback.PlaybackService]. * use [org.oxycblt.auxio.playback.PlaybackService].
* *
* All access should be done with [PlaybackStateManager.getInstance]. * All access should be done with [PlaybackStateManager.getInstance].
* @author OxygenCobalt * @author OxygenCobalt
* // TODO: Sort queues *
* TODO: Sort queues
*/ */
class PlaybackStateManager private constructor() { class PlaybackStateManager private constructor() {
// Playback // Playback

View file

@ -3,6 +3,11 @@ package org.oxycblt.auxio.recycler
import android.content.Context import android.content.Context
import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.LinearSmoothScroller
/**
* [LinearSmoothScroller] subclass that centers the item on the screen instead of snapping to the
* top or bottom.
* @author OxygenCobalt
*/
class CenterSmoothScroller(context: Context, target: Int) : LinearSmoothScroller(context) { class CenterSmoothScroller(context: Context, target: Int) : LinearSmoothScroller(context) {
init { init {
targetPosition = target targetPosition = target

View file

@ -126,9 +126,8 @@ class SongsFragment : Fragment(), SearchView.OnQueryTextListener {
if (isLandscape(resources)) { if (isLandscape(resources)) {
val spans = getLandscapeSpans(resources) val spans = getLandscapeSpans(resources)
layoutManager = GridLayoutManager(requireContext(), GridLayoutManager.VERTICAL).also { layoutManager = GridLayoutManager(requireContext(), spans).apply {
it.spanCount = spans spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
return if (binding.songRecycler.adapter == searchAdapter && position == 0) return if (binding.songRecycler.adapter == searchAdapter && position == 0)
2 else 1 2 else 1

View file

@ -34,17 +34,16 @@ class SongsViewModel : ViewModel() {
} }
viewModelScope.launch { viewModelScope.launch {
val songs = mutableListOf<BaseModel>().also { val songs = mutableListOf<BaseModel>().also { list ->
it.addAll(musicStore.songs.filter { it.name.contains(query, true) }.toMutableList()) list.addAll(
musicStore.songs.filter {
it.name.contains(query, true)
}.toMutableList()
)
} }
if (songs.isNotEmpty()) { if (songs.isNotEmpty()) {
songs.add( songs.add(0, Header(id = 0, name = context.getString(R.string.label_songs)))
0,
Header(
name = context.getString(R.string.label_songs)
)
)
} }
mSearchResults.value = songs mSearchResults.value = songs

View file

@ -13,6 +13,7 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.PluralsRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -111,3 +112,13 @@ fun Fragment.requireCompatActivity(): AppCompatActivity {
error("Required AppCompatActivity, got ${activity::class.simpleName} instead.") error("Required AppCompatActivity, got ${activity::class.simpleName} instead.")
} }
} }
/**
* 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)
}

View file

@ -174,5 +174,4 @@
<item quantity="one">%s Album</item> <item quantity="one">%s Album</item>
<item quantity="other">%s Albums</item> <item quantity="other">%s Albums</item>
</plurals> </plurals>
</resources> </resources>