Add genre support to search
Add the ability for genres to show up in search, as long as the library is currently displaying them.
This commit is contained in:
parent
127f700700
commit
711d9a0b00
15 changed files with 50 additions and 27 deletions
|
@ -34,7 +34,7 @@ TODOs surrounded with !s are things I tried to do, but failed for reasons includ
|
||||||
- Fix issue where fast navigations will cause the app to not display anything
|
- Fix issue where fast navigations will cause the app to not display anything
|
||||||
|
|
||||||
/other/
|
/other/
|
||||||
- Collapse show constants into ShowMode enum
|
- Standardize fragment init
|
||||||
|
|
||||||
To be added:
|
To be added:
|
||||||
/prefs/
|
/prefs/
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
|
@ -58,8 +59,9 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
|
||||||
val item = findItem(R.id.action_search)
|
val item = findItem(R.id.action_search)
|
||||||
val searchView = item.actionView as SearchView
|
val searchView = item.actionView as SearchView
|
||||||
|
|
||||||
// Set up the SearchView itself
|
searchView.findViewById<ImageView>(androidx.appcompat.R.id.search_go_btn)
|
||||||
searchView.queryHint = getString(R.string.hint_search_library)
|
.setImageResource(R.drawable.ic_back)
|
||||||
|
|
||||||
searchView.setOnQueryTextListener(this@LibraryFragment)
|
searchView.setOnQueryTextListener(this@LibraryFragment)
|
||||||
searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
|
searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
|
||||||
libraryModel.updateSearchFocusStatus(hasFocus)
|
libraryModel.updateSearchFocusStatus(hasFocus)
|
||||||
|
@ -139,7 +141,9 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
|
||||||
|
|
||||||
libraryModel.searchResults.observe(viewLifecycleOwner) {
|
libraryModel.searchResults.observe(viewLifecycleOwner) {
|
||||||
if (libraryModel.searchHasFocus) {
|
if (libraryModel.searchHasFocus) {
|
||||||
searchAdapter.submitList(it)
|
searchAdapter.submitList(it) {
|
||||||
|
binding.libraryRecycler.scrollToPosition(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ class LibraryViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSearchQuery(query: String, musicModel: MusicViewModel) {
|
fun updateSearchQuery(query: String, musicModel: MusicViewModel) {
|
||||||
|
// Don't bother if the query is blank.
|
||||||
if (query == "") {
|
if (query == "") {
|
||||||
resetQuery()
|
resetQuery()
|
||||||
|
|
||||||
|
@ -52,18 +53,32 @@ class LibraryViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search MusicViewModel for all the items [Artists, Albums, Songs] that contain
|
// Search MusicViewModel for all the items [Artists, Albums, Songs] that contain
|
||||||
// the query, and update the LiveData with those items. This is done on a seperate
|
// the query, and update the LiveData with those items. This is done on a separate
|
||||||
// thread as it can be a very intensive operation for large music libraries.
|
// 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 = showMode.value!!.getChildren()
|
||||||
|
|
||||||
|
// If the Library ShowMode supports it, include artists / genres in the search.
|
||||||
|
if (children.contains(ShowMode.SHOW_GENRES)) {
|
||||||
|
val genres = musicModel.genres.value!!.filter { it.name.contains(query, true) }
|
||||||
|
|
||||||
|
if (genres.isNotEmpty()) {
|
||||||
|
combined.add(Header(id = ShowMode.SHOW_GENRES.constant))
|
||||||
|
combined.addAll(genres)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children.contains(ShowMode.SHOW_ARTISTS)) {
|
||||||
val artists = musicModel.artists.value!!.filter { it.name.contains(query, true) }
|
val artists = musicModel.artists.value!!.filter { it.name.contains(query, true) }
|
||||||
|
|
||||||
if (artists.isNotEmpty()) {
|
if (artists.isNotEmpty()) {
|
||||||
combined.add(Header(id = ShowMode.SHOW_ARTISTS.constant))
|
combined.add(Header(id = ShowMode.SHOW_ARTISTS.constant))
|
||||||
combined.addAll(artists)
|
combined.addAll(artists)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Albums & Songs are always included.
|
||||||
val albums = musicModel.albums.value!!.filter { it.name.contains(query, true) }
|
val albums = musicModel.albums.value!!.filter { it.name.contains(query, true) }
|
||||||
|
|
||||||
if (albums.isNotEmpty()) {
|
if (albums.isNotEmpty()) {
|
||||||
|
|
|
@ -97,7 +97,7 @@ fun TextView.bindGenreCounts(genre: Genre) {
|
||||||
R.plurals.format_artist_count, genre.numArtists, genre.numArtists
|
R.plurals.format_artist_count, genre.numArtists, genre.numArtists
|
||||||
)
|
)
|
||||||
val albums = context.resources.getQuantityString(
|
val albums = context.resources.getQuantityString(
|
||||||
R.plurals.format_song_count, genre.numAlbums, genre.numAlbums
|
R.plurals.format_album_count, genre.numAlbums, genre.numAlbums
|
||||||
)
|
)
|
||||||
|
|
||||||
text = context.getString(R.string.format_double_counts, artists, albums)
|
text = context.getString(R.string.format_double_counts, artists, albums)
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
package org.oxycblt.auxio.recycler
|
package org.oxycblt.auxio.recycler
|
||||||
|
|
||||||
enum class ShowMode(val constant: Long) {
|
enum class ShowMode(val constant: Long) {
|
||||||
SHOW_GENRES(10), SHOW_ARTISTS(11), SHOW_ALBUMS(12), SHOW_SONGS(13);
|
SHOW_GENRES(0), SHOW_ARTISTS(1), SHOW_ALBUMS(2), SHOW_SONGS(3);
|
||||||
|
|
||||||
|
// Make a slice of all the values that this ShowMode covers.
|
||||||
|
// ex. SHOW_ARTISTS would return SHOW_ARTISTS, SHOW_ALBUMS, and SHOW_SONGS
|
||||||
|
fun getChildren(): List<ShowMode> {
|
||||||
|
val vals = values()
|
||||||
|
|
||||||
|
return vals.slice(vals.indexOf(this) until vals.size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ import org.oxycblt.auxio.music.Header
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.recycler.ClickListener
|
import org.oxycblt.auxio.recycler.ClickListener
|
||||||
|
|
||||||
// Basic ViewHolders for each music model.
|
// Shared ViewHolders for each ViewModel, providing basic information
|
||||||
|
// All new instances should be created with from(context, listener) instead of direct instantiation.
|
||||||
class GenreViewHolder private constructor(
|
class GenreViewHolder private constructor(
|
||||||
listener: ClickListener<Genre>,
|
listener: ClickListener<Genre>,
|
||||||
private val binding: ItemGenreBinding
|
private val binding: ItemGenreBinding
|
||||||
|
|
|
@ -65,6 +65,7 @@ fun Int.toColor(context: Context): Int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve an attribute into a color
|
||||||
@ColorInt
|
@ColorInt
|
||||||
fun resolveAttr(context: Context, @AttrRes attr: Int): Int {
|
fun resolveAttr(context: Context, @AttrRes attr: Int): Int {
|
||||||
// Convert the attribute into its color
|
// Convert the attribute into its color
|
||||||
|
@ -82,6 +83,7 @@ fun resolveAttr(context: Context, @AttrRes attr: Int): Int {
|
||||||
return color.toColor(context)
|
return color.toColor(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply a color to a Menu Item
|
||||||
fun MenuItem.applyColor(color: Int) {
|
fun MenuItem.applyColor(color: Int) {
|
||||||
SpannableString(title).apply {
|
SpannableString(title).apply {
|
||||||
setSpan(ForegroundColorSpan(color), 0, length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE)
|
setSpan(ForegroundColorSpan(color), 0, length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24"
|
android:viewportHeight="24"
|
||||||
android:tint="?android:attr/colorControlNormal">
|
android:tint="@color/control_color">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
|
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24"
|
android:viewportHeight="24"
|
||||||
android:tint="?attr/colorControlNormal">
|
android:tint="@color/control_color">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
|
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24"
|
android:viewportHeight="24"
|
||||||
android:tint="?android:attr/colorControlNormal">
|
android:tint="@color/control_color">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z" />
|
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z" />
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<color name="divider_color">#323232</color>
|
<color name="divider_color">#323232</color>
|
||||||
<color name="selection_color">#484848</color>
|
<color name="selection_color">#484848</color>
|
||||||
<color name="inactive_color">#404040</color>
|
<color name="inactive_color">#404040</color>
|
||||||
|
<color name="control_color">#ffffff</color>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Base color set derived from Music Player GO.
|
Base color set derived from Music Player GO.
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<color name="divider_color">#cbcbcb</color>
|
<color name="divider_color">#cbcbcb</color>
|
||||||
<color name="selection_color">#cbcbcb</color>
|
<color name="selection_color">#cbcbcb</color>
|
||||||
<color name="inactive_color">#c4c4c4</color>
|
<color name="inactive_color">#c4c4c4</color>
|
||||||
|
<color name="control_color">#202020</color>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Base color set derived from Music Player GO.
|
Base color set derived from Music Player GO.
|
||||||
|
|
|
@ -43,12 +43,11 @@
|
||||||
<string name="placeholder_album">Unknown Album</string>
|
<string name="placeholder_album">Unknown Album</string>
|
||||||
<string name="placeholder_no_date">No Date</string>
|
<string name="placeholder_no_date">No Date</string>
|
||||||
|
|
||||||
<!-- Format Namespace | Value formatting -->
|
<!-- Format Namespace | Value formatting/plurals -->
|
||||||
<string name="format_info">%1$s / %2$s</string>
|
<string name="format_info">%1$s / %2$s</string>
|
||||||
<string name="format_double_info">%1$s / %2$s / %3$s</string>
|
<string name="format_double_info">%1$s / %2$s / %3$s</string>
|
||||||
<string name="format_double_counts">%1$s, %2$s</string>
|
<string name="format_double_counts">%1$s, %2$s</string>
|
||||||
|
|
||||||
<!-- Format Namespace | Value formatting with plurals -->
|
|
||||||
<plurals name="format_song_count">
|
<plurals name="format_song_count">
|
||||||
<item quantity="one">%s Song</item>
|
<item quantity="one">%s Song</item>
|
||||||
<item quantity="other">%s Songs</item>
|
<item quantity="other">%s Songs</item>
|
||||||
|
|
|
@ -18,10 +18,7 @@
|
||||||
<item name="android:textColor">?android:attr/colorPrimary</item>
|
<item name="android:textColor">?android:attr/colorPrimary</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Toolbar.SearchViewStyle" parent="Widget.AppCompat.SearchView">
|
<style name="Toolbar.SearchViewStyle" parent="Widget.AppCompat.SearchView"/>
|
||||||
<item name="android:searchIcon">@drawable/ic_search</item>
|
|
||||||
<item name="android:closeIcon">@drawable/ic_back</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="DetailHeader">
|
<style name="DetailHeader">
|
||||||
<item name="android:textAppearance">?android:attr/textAppearanceLarge</item>
|
<item name="android:textAppearance">?android:attr/textAppearanceLarge</item>
|
||||||
|
|
|
@ -38,25 +38,21 @@
|
||||||
<item name="colorSecondary">@color/light_blue</item>
|
<item name="colorSecondary">@color/light_blue</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="Theme.Cyan" parent="Theme.Base">
|
<style name="Theme.Cyan" parent="Theme.Base">
|
||||||
<item name="colorPrimary">@color/cyan</item>
|
<item name="colorPrimary">@color/cyan</item>
|
||||||
<item name="colorSecondary">@color/cyan</item>
|
<item name="colorSecondary">@color/cyan</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="Theme.Teal" parent="Theme.Base">
|
<style name="Theme.Teal" parent="Theme.Base">
|
||||||
<item name="colorPrimary">@color/teal</item>
|
<item name="colorPrimary">@color/teal</item>
|
||||||
<item name="colorSecondary">@color/teal</item>
|
<item name="colorSecondary">@color/teal</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="Theme.Green" parent="Theme.Base">
|
<style name="Theme.Green" parent="Theme.Base">
|
||||||
<item name="colorPrimary">@color/green</item>
|
<item name="colorPrimary">@color/green</item>
|
||||||
<item name="colorSecondary">@color/green</item>
|
<item name="colorSecondary">@color/green</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="Theme.LightGreen" parent="Theme.Base">
|
<style name="Theme.LightGreen" parent="Theme.Base">
|
||||||
<item name="colorPrimary">@color/light_green</item>
|
<item name="colorPrimary">@color/light_green</item>
|
||||||
<item name="colorSecondary">@color/light_green</item>
|
<item name="colorSecondary">@color/light_green</item>
|
||||||
|
|
Loading…
Reference in a new issue