diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt index d3169e1e..64732a45 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistListFragment.kt @@ -59,15 +59,10 @@ class ArtistListFragment : EntryListFragment() { 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)) - bundle.putString( - Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, - Constants.ALPHABETICAL_BY_NAME - ) + 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(itemClickTarget, bundle) } - - // Constants.ALPHABETICAL_BY_NAME } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadsFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadsFragment.kt index 70c70986..c3abfe03 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadsFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadsFragment.kt @@ -11,6 +11,7 @@ import android.app.Application import android.os.Bundle import android.view.MenuItem import android.view.View +import androidx.core.view.isVisible import androidx.fragment.app.viewModels import androidx.lifecycle.LiveData import org.koin.core.component.inject @@ -76,6 +77,9 @@ class DownloadsFragment : MultiListFragment() { val liveDataList = listModel.getList() + emptyTextView.setText(R.string.download_empty) + emptyView.isVisible = liveDataList.value?.isEmpty() ?: true + viewAdapter.submitList(liveDataList.value) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EntryListFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EntryListFragment.kt index af4f1947..6d26d137 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EntryListFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EntryListFragment.kt @@ -7,8 +7,11 @@ import androidx.navigation.fragment.findNavController import org.moire.ultrasonic.R import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.GenericEntry +import org.moire.ultrasonic.domain.Identifiable import org.moire.ultrasonic.service.RxBus +import org.moire.ultrasonic.subsonic.DownloadHandler import org.moire.ultrasonic.util.Constants +import androidx.fragment.app.Fragment import org.moire.ultrasonic.util.Settings /** @@ -27,91 +30,11 @@ abstract class EntryListFragment : MultiListFragment() { !listModel.isOffline() && !Settings.shouldUseId3Tags } - @Suppress("LongMethod") + override fun onContextMenuItemSelected(menuItem: MenuItem, item: T): Boolean { val isArtist = (item is Artist) - when (menuItem.itemId) { - R.id.menu_play_now -> - downloadHandler.downloadRecursively( - this, - item.id, - save = false, - append = false, - autoPlay = true, - shuffle = false, - background = false, - playNext = false, - unpin = false, - isArtist = isArtist - ) - R.id.menu_play_next -> - downloadHandler.downloadRecursively( - this, - item.id, - save = false, - append = false, - autoPlay = true, - shuffle = true, - background = false, - playNext = true, - unpin = false, - isArtist = isArtist - ) - R.id.menu_play_last -> - downloadHandler.downloadRecursively( - this, - item.id, - save = false, - append = true, - autoPlay = false, - shuffle = false, - background = false, - playNext = false, - unpin = false, - isArtist = isArtist - ) - R.id.menu_pin -> - downloadHandler.downloadRecursively( - this, - item.id, - save = true, - append = true, - autoPlay = false, - shuffle = false, - background = false, - playNext = false, - unpin = false, - isArtist = isArtist - ) - R.id.menu_unpin -> - downloadHandler.downloadRecursively( - this, - item.id, - save = false, - append = false, - autoPlay = false, - shuffle = false, - background = false, - playNext = false, - unpin = true, - isArtist = isArtist - ) - R.id.menu_download -> - downloadHandler.downloadRecursively( - this, - item.id, - save = false, - append = false, - autoPlay = false, - shuffle = false, - background = true, - playNext = false, - unpin = false, - isArtist = isArtist - ) - } - return true + return handleContextMenu(menuItem, item, isArtist, downloadHandler, this) } override fun onItemClick(item: T) { @@ -137,4 +60,97 @@ abstract class EntryListFragment : MultiListFragment() { listModel.refresh(refreshListView!!, arguments) } } + + companion object { + @Suppress("LongMethod") + internal fun handleContextMenu( + menuItem: MenuItem, + item: Identifiable, + isArtist: Boolean, + downloadHandler: DownloadHandler, + fragment: Fragment + ): Boolean { + when (menuItem.itemId) { + R.id.menu_play_now -> + downloadHandler.downloadRecursively( + fragment, + item.id, + save = false, + append = false, + autoPlay = true, + shuffle = false, + background = false, + playNext = false, + unpin = false, + isArtist = isArtist + ) + R.id.menu_play_next -> + downloadHandler.downloadRecursively( + fragment, + item.id, + save = false, + append = false, + autoPlay = true, + shuffle = true, + background = false, + playNext = true, + unpin = false, + isArtist = isArtist + ) + R.id.menu_play_last -> + downloadHandler.downloadRecursively( + fragment, + item.id, + save = false, + append = true, + autoPlay = false, + shuffle = false, + background = false, + playNext = false, + unpin = false, + isArtist = isArtist + ) + R.id.menu_pin -> + downloadHandler.downloadRecursively( + fragment, + item.id, + save = true, + append = true, + autoPlay = false, + shuffle = false, + background = false, + playNext = false, + unpin = false, + isArtist = isArtist + ) + R.id.menu_unpin -> + downloadHandler.downloadRecursively( + fragment, + item.id, + save = false, + append = false, + autoPlay = false, + shuffle = false, + background = false, + playNext = false, + unpin = true, + isArtist = isArtist + ) + R.id.menu_download -> + downloadHandler.downloadRecursively( + fragment, + item.id, + save = false, + append = false, + autoPlay = false, + shuffle = false, + background = true, + playNext = false, + unpin = false, + isArtist = isArtist + ) + } + return true + } + } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MultiListFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MultiListFragment.kt index 635f7632..c932ecda 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MultiListFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MultiListFragment.kt @@ -13,6 +13,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -46,6 +47,7 @@ abstract class MultiListFragment : Fragment() { protected var refreshListView: SwipeRefreshLayout? = null internal var listView: RecyclerView? = null internal lateinit var viewManager: LinearLayoutManager + internal lateinit var emptyView: ConstraintLayout internal lateinit var emptyTextView: TextView /** @@ -71,7 +73,7 @@ abstract class MultiListFragment : Fragment() { * The central function to pass a query to the model and return a LiveData object */ open fun getLiveData(args: Bundle? = null): LiveData> { - return MutableLiveData(listOf()) + return MutableLiveData() } /** @@ -90,7 +92,9 @@ abstract class MultiListFragment : Fragment() { */ open val refreshListId = R.id.swipe_refresh_view open val recyclerViewId = R.id.recycler_view - open val emptyTextViewId = R.id.empty_list_text + open val emptyViewId = R.id.empty_list_view + open val emptyTextId = R.id.empty_list_text + open fun setTitle(title: String?) { if (title == null) { @@ -121,14 +125,14 @@ abstract class MultiListFragment : Fragment() { liveDataItems = getLiveData(arguments) // Link view to display text if the list is empty - // FIXME: Hook this up globally. - emptyTextView = view.findViewById(emptyTextViewId) + emptyView = view.findViewById(emptyViewId) + emptyTextView = view.findViewById(emptyTextId) // Register an observer to update our UI when the data changes liveDataItems.observe( viewLifecycleOwner, { newItems -> - emptyTextView.isVisible = newItems.isEmpty() + emptyView.isVisible = newItems.isEmpty() viewAdapter.submitList(newItems) } ) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt index 19553929..611f66af 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt @@ -28,6 +28,7 @@ import org.moire.ultrasonic.adapters.AlbumRowBinder import org.moire.ultrasonic.adapters.ArtistRowBinder import org.moire.ultrasonic.adapters.DividerBinder import org.moire.ultrasonic.adapters.TrackViewBinder +import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Identifiable import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.SearchResult @@ -176,11 +177,11 @@ class SearchFragment : MultiListFragment(), KoinComponent { return search(query, autoPlay) } } - - // Fragment was started from the Menu, create empty list - // populateList(SearchResult()) } + /** + * This method create the search bar above the recycler view + */ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { val activity = activity ?: return val searchManager = activity.getSystemService(Context.SEARCH_SERVICE) as SearchManager @@ -191,8 +192,8 @@ class SearchFragment : MultiListFragment(), KoinComponent { searchView.setSearchableInfo(searchableInfo) val arguments = arguments - val autoPlay = - arguments != null && arguments.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false) + val autoPlay = arguments != null && + arguments.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false) val query = arguments?.getString(Constants.INTENT_EXTRA_NAME_QUERY) // If started with a query, enter it to the searchView @@ -211,13 +212,13 @@ class SearchFragment : MultiListFragment(), KoinComponent { val cursor = searchView.suggestionsAdapter.cursor cursor.moveToPosition(position) - // TODO: Try to do something with this magic const: - // 2 is the index of col containing suggestion name. + // 2 is the index of col containing suggestion name. val suggestion = cursor.getString(2) searchView.setQuery(suggestion, true) return true } }) + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { Timber.d("onQueryTextSubmit: %s", query) @@ -230,6 +231,7 @@ class SearchFragment : MultiListFragment(), KoinComponent { return true } }) + searchView.setIconifiedByDefault(false) searchItem.expandActionView() } @@ -479,7 +481,7 @@ class SearchFragment : MultiListFragment(), KoinComponent { } // Show/hide the empty text view - emptyTextView.isVisible = list.isEmpty() + emptyView.isVisible = list.isEmpty() viewAdapter.submitList(list) } @@ -557,20 +559,15 @@ class SearchFragment : MultiListFragment(), KoinComponent { var DEFAULT_SONGS = Settings.defaultSongs } - // FIXME!! - override fun getLiveData(args: Bundle?): LiveData> { - return MutableLiveData(listOf()) - } - // FIXME override val itemClickTarget: Int = 0 - // FIXME - override fun onContextMenuItemSelected(menuItem: MenuItem, item: Identifiable): Boolean { - return true - } - // FIXME override fun onItemClick(item: Identifiable) { } + + override fun onContextMenuItemSelected(menuItem: MenuItem, item: Identifiable): Boolean { + val isArtist = (item is Artist) + return EntryListFragment.handleContextMenu(menuItem, item, isArtist, downloadHandler, this) + } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index 6d15cc8d..5f1386f7 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -57,7 +57,6 @@ import timber.log.Timber open class TrackCollectionFragment : MultiListFragment() { private var albumButtons: View? = null - private var emptyView: TextView? = null internal var selectButton: ImageView? = null internal var playNowButton: ImageView? = null internal var playNextButton: ImageView? = null @@ -107,8 +106,6 @@ open class TrackCollectionFragment : MultiListFragment() { setupButtons(view) - emptyView = view.findViewById(R.id.empty_list_text) - registerForContextMenu(listView!!) setHasOptionsMenu(true) @@ -579,7 +576,7 @@ open class TrackCollectionFragment : MultiListFragment() { } // Show a text if we have no entries - emptyView?.isVisible = entryList.isEmpty() + emptyView.isVisible = entryList.isEmpty() enableButtons() @@ -599,10 +596,8 @@ open class TrackCollectionFragment : MultiListFragment() { val albumHeader = AlbumHeader(it, name ?: intentAlbumName) val mixedList: MutableList = mutableListOf(albumHeader) mixedList.addAll(entryList) - Timber.e("SUBMITTING MIXED LIST") viewAdapter.submitList(mixedList) } else { - Timber.e("SUBMITTING ENTRY LIST") viewAdapter.submitList(entryList) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/AlbumListModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/AlbumListModel.kt index 24dfd80e..7ac20f1a 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/AlbumListModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/AlbumListModel.kt @@ -13,7 +13,7 @@ import org.moire.ultrasonic.util.Settings class AlbumListModel(application: Application) : GenericListModel(application) { - val list: MutableLiveData> = MutableLiveData(listOf()) + val list: MutableLiveData> = MutableLiveData() var lastType: String? = null private var loadedUntil: Int = 0 @@ -26,7 +26,7 @@ class AlbumListModel(application: Application) : GenericListModel(application) { // This way, we keep the scroll position val albumListType = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE)!! - if (refresh || list.value!!.isEmpty() || albumListType != lastType) { + if (refresh || list.value?.isEmpty() != false || albumListType != lastType) { lastType = albumListType backgroundLoadFromServer(refresh, swipe, args) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ArtistListModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ArtistListModel.kt index ca2bed1f..8861a9ef 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ArtistListModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/ArtistListModel.kt @@ -31,7 +31,7 @@ import org.moire.ultrasonic.service.MusicService * Provides ViewModel which contains the list of available Artists */ class ArtistListModel(application: Application) : GenericListModel(application) { - private val artists: MutableLiveData> = MutableLiveData(listOf()) + private val artists: MutableLiveData> = MutableLiveData() /** * Retrieves all available Artists in a LiveData @@ -39,7 +39,7 @@ class ArtistListModel(application: Application) : GenericListModel(application) fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData> { // Don't reload the data if navigating back to the view that was active before. // This way, we keep the scroll position - if (artists.value!!.isEmpty() || refresh) { + if (artists.value?.isEmpty() != false || refresh) { backgroundLoadFromServer(refresh, swipe) } return artists diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/SearchListModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/SearchListModel.kt index 7bd1c3a4..a5907352 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/SearchListModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/SearchListModel.kt @@ -14,7 +14,7 @@ import org.moire.ultrasonic.util.Settings class SearchListModel(application: Application) : GenericListModel(application) { - var searchResult: MutableLiveData = MutableLiveData(null) + var searchResult: MutableLiveData = MutableLiveData() override fun load( isOffline: Boolean, diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/TrackCollectionModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/TrackCollectionModel.kt index ac9ef7c1..af8621d3 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/TrackCollectionModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/model/TrackCollectionModel.kt @@ -19,6 +19,9 @@ import org.moire.ultrasonic.util.Util /* * Model for retrieving different collections of tracks from the API +* +* TODO: Remove double data keeping in currentList/currentDirectory and use the base model liveData +* For this refactor MusicService to replace MusicDirectories with List or List */ class TrackCollectionModel(application: Application) : GenericListModel(application) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt index 26e2e6c6..aa22751b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/Downloader.kt @@ -157,7 +157,8 @@ class Downloader( // Add file to queue if not in one of the queues already. if (!download.isWorkDone && !activelyDownloading.contains(download) && - !downloadQueue.contains(download) + !downloadQueue.contains(download) && + download.shouldRetry() ) { listChanged = true downloadQueue.add(download) @@ -281,14 +282,18 @@ class Downloader( fun clearPlaylist() { playlist.clear() + val toRemove = mutableListOf() + // Cancel all active downloads with a high priority for (download in activelyDownloading) { if (download.priority < 100) { download.cancelDownload() - activelyDownloading.remove(download) + toRemove.add(download) } } + activelyDownloading.removeAll(toRemove) + playlistUpdateRevision++ updateLiveData() } diff --git a/ultrasonic/src/main/res/drawable/ic_empty.xml b/ultrasonic/src/main/res/drawable/ic_empty.xml new file mode 100644 index 00000000..74776517 --- /dev/null +++ b/ultrasonic/src/main/res/drawable/ic_empty.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ultrasonic/src/main/res/layout/empty_view.xml b/ultrasonic/src/main/res/layout/empty_view.xml new file mode 100644 index 00000000..b6e8bcce --- /dev/null +++ b/ultrasonic/src/main/res/layout/empty_view.xml @@ -0,0 +1,38 @@ + + + + + + + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/generic_list.xml b/ultrasonic/src/main/res/layout/generic_list.xml index 45bce488..84164d10 100644 --- a/ultrasonic/src/main/res/layout/generic_list.xml +++ b/ultrasonic/src/main/res/layout/generic_list.xml @@ -4,6 +4,7 @@ a:layout_height="fill_parent" a:orientation="vertical"> + diff --git a/ultrasonic/src/main/res/layout/recycler_view.xml b/ultrasonic/src/main/res/layout/recycler_view.xml index 09ca5ccc..7808a71c 100644 --- a/ultrasonic/src/main/res/layout/recycler_view.xml +++ b/ultrasonic/src/main/res/layout/recycler_view.xml @@ -1,12 +1,5 @@ - - + diff --git a/ultrasonic/src/main/res/layout/track_list.xml b/ultrasonic/src/main/res/layout/track_list.xml index a53f6024..1190aefc 100644 --- a/ultrasonic/src/main/res/layout/track_list.xml +++ b/ultrasonic/src/main/res/layout/track_list.xml @@ -4,11 +4,7 @@ a:layout_height="fill_parent" a:orientation="vertical" > - - + diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index fe6010d8..9d217525 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -57,6 +57,7 @@ Do you want to delete %1$s Bookmark removed. Bookmark set at %s. + Nothing is downloading Playlist is empty Remote control is not allowed. Please enable jukebox mode in Users > Settings on your Subsonic server. Turned off remote control. Music is played on phone.