Fixed search, put compareTo method into Interface

This commit is contained in:
tzugen 2021-11-30 20:53:10 +01:00
parent bdac092eff
commit f1e789ea9b
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
23 changed files with 117 additions and 233 deletions

View File

@ -11,21 +11,4 @@ data class Artist(
override var coverArt: String? = null,
override var albumCount: Long? = null,
override var closeness: Int = 0
) : ArtistOrIndex(id) {
fun compareTo(other: Artist): Int {
when {
this.closeness == other.closeness -> {
return 0
}
this.closeness > other.closeness -> {
return -1
}
else -> {
return 1
}
}
}
override fun compareTo(other: Identifiable) = compareTo(other as Artist)
}
) : ArtistOrIndex(id)

View File

@ -15,4 +15,21 @@ abstract class ArtistOrIndex(
open var albumCount: Long? = null,
@Ignore
open var closeness: Int = 0
) : GenericEntry()
) : GenericEntry() {
fun compareTo(other: ArtistOrIndex): Int {
when {
this.closeness == other.closeness -> {
return 0
}
this.closeness > other.closeness -> {
return -1
}
else -> {
return 1
}
}
}
override fun compareTo(other: Identifiable) = compareTo(other as ArtistOrIndex)
}

View File

@ -3,19 +3,17 @@ package org.moire.ultrasonic.domain
import androidx.room.Ignore
abstract class GenericEntry : Identifiable {
abstract override val id: String
@Ignore
open val name: String? = null
override fun compareTo(other: Identifiable): Int {
return this.id.toInt().compareTo(other.id.toInt())
}
@delegate:Ignore
override val longId: Long by lazy {
id.hashCode().toLong()
}
}
interface Identifiable : Comparable<Identifiable> {
val id: String
val longId: Long
get() = id.hashCode().toLong()
override fun compareTo(other: Identifiable): Int {
return longId.compareTo(other.longId)
}
}

View File

@ -32,7 +32,7 @@ class MusicDirectory : ArrayList<MusicDirectory.Child>() {
}
}
abstract class Child : Identifiable, GenericEntry() {
abstract class Child : GenericEntry() {
abstract override var id: String
abstract var parent: String?
abstract var isDirectory: Boolean

View File

@ -7,7 +7,7 @@ import org.moire.ultrasonic.domain.MusicDirectory.Entry
* The result of a search. Contains matching artists, albums and songs.
*/
data class SearchResult(
val artists: List<Artist> = listOf(),
val artists: List<ArtistOrIndex> = listOf(),
val albums: List<Album> = listOf(),
val songs: List<Entry> = listOf()
)

View File

@ -7,8 +7,8 @@ data class Album(
val id: String = "",
val parent: String = "",
val album: String = "",
val title: String = "",
val name: String = "",
val title: String? = null,
val name: String? = null,
val discNumber: Int = 0,
val coverArt: String = "",
val songCount: Int = 0,

View File

@ -2,7 +2,8 @@ org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.configureondemand=true
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2g
org.gradle.jvmargs=-Xmx2g -XX:+UseParallelGC
kotlin.incremental=true
kotlin.caching.enabled=true

View File

@ -1,117 +0,0 @@
/*
This file is part of Subsonic.
Subsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Subsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 (C) Sindre Mehus
*/
package org.moire.ultrasonic.view;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.SectionIndexer;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.domain.Artist;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
/**
* @author Sindre Mehus
*/
public class ArtistAdapter extends ArrayAdapter<Artist> implements SectionIndexer
{
private final LayoutInflater layoutInflater;
// Both arrays are indexed by section ID.
private final Object[] sections;
private final Integer[] positions;
public ArtistAdapter(Context context, List<Artist> artists)
{
super(context, R.layout.list_item_generic, artists);
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Collection<String> sectionSet = new LinkedHashSet<String>(30);
List<Integer> positionList = new ArrayList<Integer>(30);
for (int i = 0; i < artists.size(); i++)
{
Artist artist = artists.get(i);
String index = artist.getIndex();
if (!sectionSet.contains(index))
{
sectionSet.add(index);
positionList.add(i);
}
}
sections = sectionSet.toArray(new Object[0]);
positions = positionList.toArray(new Integer[0]);
}
@NonNull
@Override
public View getView(
int position,
@Nullable View convertView,
@NonNull ViewGroup parent
) {
View rowView = convertView;
if (rowView == null) {
rowView = layoutInflater.inflate(R.layout.list_item_generic, parent, false);
}
((TextView) rowView).setText(getItem(position).getName());
return rowView;
}
@Override
public Object[] getSections()
{
return sections;
}
@Override
public int getPositionForSection(int section)
{
return positions.length > section ? positions[section] : 0;
}
@Override
public int getSectionForPosition(int pos)
{
for (int i = 0; i < sections.length - 1; i++)
{
if (pos < positions[i + 1])
{
return i;
}
}
return sections.length - 1;
}
}

View File

@ -88,8 +88,4 @@ class AlbumHeader(
override val longId: Long
get() = -1L
override fun compareTo(other: Identifiable): Int {
return this.longId.compareTo(other.longId)
}
}

View File

@ -52,8 +52,6 @@ class BaseAdapter<T : Identifiable> : MultiTypeAdapter() {
return mDiffer.currentList[position]
}
// override getIt
override var items: List<Any>
get() = getCurrentList()
set(value) {

View File

@ -41,9 +41,5 @@ class DividerBinder : ItemViewBinder<DividerBinder.Divider, DividerBinder.ViewHo
data class Divider(val stringId: Int) : Identifiable {
override val id: String
get() = stringId.toString()
override val longId: Long
get() = stringId.toLong()
override fun compareTo(other: Identifiable): Int = longId.compareTo(other.longId)
}
}

View File

@ -128,9 +128,5 @@ class FolderSelectorBinder(context: Context) :
override val longId: Long
get() = -1L
override fun compareTo(other: Identifiable): Int {
return longId.compareTo(other.longId)
}
}
}

View File

@ -3,7 +3,6 @@ package org.moire.ultrasonic.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.drakeet.multitype.ItemViewBinder
import org.moire.ultrasonic.R
@ -41,10 +40,5 @@ class MoreButtonBinder : ItemViewBinder<MoreButtonBinder.MoreButton, RecyclerVie
override val id: String
get() = stringId.toString()
override val longId: Long
get() = stringId.toLong()
override fun compareTo(other: Identifiable): Int = longId.compareTo(other.longId)
}
}

View File

@ -7,7 +7,7 @@ import org.moire.ultrasonic.api.subsonic.models.Album
fun Album.toDomainEntity(): MusicDirectory.Album = MusicDirectory.Album(
id = this@toDomainEntity.id,
title = this@toDomainEntity.title,
title = this@toDomainEntity.name ?: this@toDomainEntity.title,
album = this@toDomainEntity.album,
coverArt = this@toDomainEntity.coverArt,
artist = this@toDomainEntity.artist,

View File

@ -9,15 +9,12 @@ package org.moire.ultrasonic.fragment
import android.os.Bundle
import android.view.View
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import org.moire.ultrasonic.R
import org.moire.ultrasonic.adapters.AlbumRowBinder
import org.moire.ultrasonic.adapters.FolderSelectorBinder
import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.model.AlbumListModel
import org.moire.ultrasonic.util.Constants
@ -40,7 +37,10 @@ class AlbumListFragment : EntryListFragment<MusicDirectory.Album>() {
/**
* The central function to pass a query to the model and return a LiveData object
*/
override fun getLiveData(args: Bundle?, refresh: Boolean): LiveData<List<MusicDirectory.Album>> {
override fun getLiveData(
args: Bundle?,
refresh: Boolean
): LiveData<List<MusicDirectory.Album>> {
if (args == null) throw IllegalArgumentException("Required arguments are missing")
val refresh = args.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH) || refresh

View File

@ -2,25 +2,20 @@ package org.moire.ultrasonic.fragment
import android.os.Bundle
import android.view.View
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import org.moire.ultrasonic.R
import org.moire.ultrasonic.adapters.ArtistRowBinder
import org.moire.ultrasonic.adapters.FolderSelectorBinder
import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.ArtistOrIndex
import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.domain.Index
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.model.ArtistListModel
import org.moire.ultrasonic.util.Constants
/**
* Displays the list of Artists from the media library
*
* FIXME: FOLDER HEADER NOT POPULATED ON FIST LOAD
* Displays the list of Artists or Indexes (folders) from the media library
*/
class ArtistListFragment : EntryListFragment<ArtistOrIndex>() {
@ -60,6 +55,11 @@ class ArtistListFragment : EntryListFragment<ArtistOrIndex>() {
* If we are showing artists, we need to go to AlbumList
*/
override fun onItemClick(item: ArtistOrIndex) {
Companion.onItemClick(item, findNavController())
}
companion object {
fun onItemClick(item: ArtistOrIndex, navController: NavController) {
val bundle = Bundle()
// Common arguments
@ -70,13 +70,17 @@ class ArtistListFragment : EntryListFragment<ArtistOrIndex>() {
// Check type
if (item is Index) {
findNavController().navigate(R.id.artistsListToTrackCollection, bundle)
navController.navigate(R.id.artistsListToTrackCollection, bundle)
} else {
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, Constants.ALBUMS_OF_ARTIST)
bundle.putString(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE,
Constants.ALBUMS_OF_ARTIST
)
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, item.name)
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 1000)
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0)
findNavController().navigate(R.id.artistsListToAlbumsList, bundle)
navController.navigate(R.id.artistsListToAlbumsList, bundle)
}
}
}
}

View File

@ -9,7 +9,6 @@ import androidx.navigation.fragment.findNavController
import org.moire.ultrasonic.R
import org.moire.ultrasonic.adapters.FolderSelectorBinder
import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.ArtistOrIndex
import org.moire.ultrasonic.domain.GenericEntry
import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.service.RxBus
@ -50,6 +49,10 @@ abstract class EntryListFragment<T : GenericEntry> : MultiListFragment<T>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Call a cheap function on ServerSettingsModel to make sure it is initialized by Koin,
// because it can't be initialized from inside the callback
serverSettingsModel.toString()
RxBus.musicFolderChangedEventObservable.subscribe {
if (!listModel.isOffline()) {
val currentSetting = listModel.activeServer

View File

@ -3,17 +3,16 @@ package org.moire.ultrasonic.fragment
import android.app.SearchManager
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.widget.ListAdapter
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import androidx.lifecycle.viewModelScope
import androidx.navigation.Navigation
import androidx.navigation.fragment.findNavController
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
@ -26,7 +25,9 @@ import org.moire.ultrasonic.adapters.MoreButtonBinder
import org.moire.ultrasonic.adapters.MoreButtonBinder.MoreButton
import org.moire.ultrasonic.adapters.TrackViewBinder
import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.ArtistOrIndex
import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.domain.Index
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.SearchResult
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
@ -41,12 +42,10 @@ import org.moire.ultrasonic.util.CommunicationError
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util.toast
import org.moire.ultrasonic.view.ArtistAdapter
import timber.log.Timber
/**
* Initiates a search on the media library and displays the results
* FIXME: Artist click, display
*/
class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
private var searchResult: SearchResult? = null
@ -265,11 +264,28 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
populateList(listModel.trimResultLength(searchResult!!, maxSongs = Int.MAX_VALUE))
}
private fun onArtistSelected(artist: Artist) {
private fun onArtistSelected(item: ArtistOrIndex) {
val bundle = Bundle()
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, artist.id)
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.id)
Navigation.findNavController(requireView()).navigate(R.id.searchToSelectAlbum, bundle)
// Common arguments
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, item.id)
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, item.name)
bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, item.id)
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, (item is Artist))
// Check type
if (item is Index) {
findNavController().navigate(R.id.searchToTrackCollection, bundle)
} else {
bundle.putString(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE,
Constants.ALBUMS_OF_ARTIST
)
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, item.name)
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 1000)
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0)
findNavController().navigate(R.id.searchToAlbumsList, bundle)
}
}
private fun onAlbumSelected(album: MusicDirectory.Album, autoplay: Boolean) {
@ -278,14 +294,21 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, album.title)
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, album.isDirectory)
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, autoplay)
Navigation.findNavController(requireView()).navigate(R.id.searchToSelectAlbum, bundle)
Navigation.findNavController(requireView()).navigate(R.id.searchToTrackCollection, bundle)
}
private fun onSongSelected(song: MusicDirectory.Entry, append: Boolean) {
if (!append) {
mediaPlayerController.clear()
}
mediaPlayerController.addToPlaylist(listOf(song), false, false, false, false, false)
mediaPlayerController.addToPlaylist(
listOf(song),
save = false,
autoPlay = false,
playNext = false,
shuffle = false,
newPlaylist = false
)
mediaPlayerController.play(mediaPlayerController.playlistSize - 1)
toast(context, resources.getQuantityString(R.plurals.select_album_n_songs_added, 1, 1))
}
@ -304,7 +327,7 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
override fun onItemClick(item: Identifiable) {
when (item) {
is Artist -> {
is ArtistOrIndex -> {
onArtistSelected(item)
}
is MusicDirectory.Entry -> {

View File

@ -49,13 +49,11 @@ import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util
/**
*
* Displays a group of tracks, eg. the songs of an album, of a playlist etc.
*
* In most cases the data should be just a list of Entries, but there are some cases
* where the list can contain Albums as well. This happens especially when having ID3 tags disabled,
* or using Offline mode, both in which Indexes instead of Artists are being used.
*
*/
@Suppress("TooManyFunctions")
open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
@ -96,7 +94,7 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
// Setup refresh handler
refreshListView = view.findViewById(refreshListId)
refreshListView?.setOnRefreshListener {
refreshData(true)
getLiveData(arguments, true)
}
// TODO: remove special casing for songsForGenre
@ -209,12 +207,6 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
refreshListView?.isRefreshing = false
}
private fun refreshData(refresh: Boolean = false) {
val args = getArgumentsClone()
args.putBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, refresh)
getLiveData(args)
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
playAllButton = menu.findItem(R.id.select_album_play_all)
@ -293,8 +285,6 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
}
val isArtist = arguments?.getBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, false) ?: false
// FIXME WHICH id if no arguments?
val id = arguments?.getString(Constants.INTENT_EXTRA_NAME_ID)
if (hasSubFolders && id != null) {
@ -565,7 +555,10 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
}
@Suppress("LongMethod")
override fun getLiveData(args: Bundle?, refresh: Boolean): LiveData<List<MusicDirectory.Child>> {
override fun getLiveData(
args: Bundle?,
refresh: Boolean
): LiveData<List<MusicDirectory.Child>> {
if (args == null) return listModel.currentList
val id = args.getString(Constants.INTENT_EXTRA_NAME_ID)
val isAlbum = args.getBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, false)
@ -588,7 +581,7 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
val albumListOffset = args.getInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0
)
val refresh = args.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true) || refresh
val refresh2 = args.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH, true) || refresh
listModel.viewModelScope.launch(handler) {
refreshListView?.isRefreshing = true
@ -610,7 +603,7 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
listModel.getStarred()
} else if (getVideos != 0) {
setTitle(R.string.main_videos)
listModel.getVideos(refresh)
listModel.getVideos(refresh2)
} else if (getRandomTracks != 0) {
setTitle(R.string.main_songs_random)
listModel.getRandom(albumListSize)
@ -618,12 +611,12 @@ open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
setTitle(name)
if (!isOffline() && Settings.shouldUseId3Tags) {
if (isAlbum) {
listModel.getAlbum(refresh, id!!, name)
listModel.getAlbum(refresh2, id!!, name)
} else {
throw IllegalAccessException("Use AlbumFragment instead!")
}
} else {
listModel.getMusicDirectory(refresh, id!!, name)
listModel.getMusicDirectory(refresh2, id!!, name)
}
}

View File

@ -436,10 +436,6 @@ class DownloadFile(
override val id: String
get() = song.id
override val longId: Long by lazy {
id.hashCode().toLong()
}
companion object {
const val MAX_RETRIES = 5
}

View File

@ -24,6 +24,7 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.ArtistOrIndex
import org.moire.ultrasonic.domain.Bookmark
import org.moire.ultrasonic.domain.ChatMessage
import org.moire.ultrasonic.domain.Genre
@ -122,7 +123,7 @@ class OfflineMusicService : MusicService, KoinComponent {
}
override fun search(criteria: SearchCriteria): SearchResult {
val artists: MutableList<Artist> = ArrayList()
val artists: MutableList<ArtistOrIndex> = ArrayList()
val albums: MutableList<MusicDirectory.Album> = ArrayList()
val songs: MutableList<MusicDirectory.Entry> = ArrayList()
val root = FileUtil.musicDirectory
@ -131,7 +132,7 @@ class OfflineMusicService : MusicService, KoinComponent {
val artistName = artistFile.name
if (artistFile.isDirectory) {
if (matchCriteria(criteria, artistName).also { closeness = it } > 0) {
val artist = Artist(artistFile.path)
val artist = Index(artistFile.path)
artist.index = artistFile.name.substring(0, 1)
artist.name = artistName
artist.closeness = closeness

View File

@ -361,7 +361,6 @@ open class RESTMusicService(
musicFolderId
).execute().throwOnFailure()
return response.body()!!.albumList.toDomainEntityList()
}

View File

@ -60,8 +60,11 @@
android:id="@+id/searchFragment"
android:name="org.moire.ultrasonic.fragment.SearchFragment" >
<action
android:id="@+id/searchToSelectAlbum"
android:id="@+id/searchToTrackCollection"
app:destination="@id/trackCollectionFragment" />
<action
android:id="@+id/searchToAlbumsList"
app:destination="@id/albumListFragment" />
</fragment>
<fragment
android:id="@+id/playlistsFragment"