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:
parent
22ab5ad255
commit
406ba212f8
13 changed files with 102 additions and 94 deletions
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
4
app/src/debug/res/values/strings.xml
Normal file
4
app/src/debug/res/values/strings.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Auxio Debug</string>
|
||||||
|
</resources>
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue