diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java index 13d79649..ca2a8829 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java @@ -284,9 +284,9 @@ public class CachedMusicService implements MusicService } @Override - public MusicDirectory getAlbumList(String type, int size, int offset, Context context) throws Exception + public MusicDirectory getAlbumList(String type, int size, int offset, String musicFolderId, Context context) throws Exception { - return musicService.getAlbumList(type, size, offset, context); + return musicService.getAlbumList(type, size, offset, musicFolderId, context); } @Override diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java index 37ded458..80bf4056 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java @@ -90,7 +90,7 @@ public interface MusicService void scrobble(String id, boolean submission, Context context) throws Exception; - MusicDirectory getAlbumList(String type, int size, int offset, Context context) throws Exception; + MusicDirectory getAlbumList(String type, int size, int offset, String musicFolderId, Context context) throws Exception; MusicDirectory getAlbumList2(String type, int size, int offset, Context context) throws Exception; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java index 500d6b6f..65fe5654 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java @@ -702,7 +702,7 @@ public class OfflineMusicService implements MusicService } @Override - public MusicDirectory getAlbumList(String type, int size, int offset, Context context) throws Exception + public MusicDirectory getAlbumList(String type, int size, int offset, String musicFolderId, Context context) throws Exception { throw new OfflineException("Album lists not available in offline mode"); } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt index 671ff8d5..40d439ff 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt @@ -24,7 +24,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.ImageView -import android.widget.LinearLayout import android.widget.PopupMenu import android.widget.RelativeLayout import android.widget.TextView @@ -37,6 +36,7 @@ import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.util.ImageLoader import org.moire.ultrasonic.util.Util +import org.moire.ultrasonic.view.SelectMusicFolderView /** * Creates a Row in a RecyclerView which contains the details of an Artist @@ -44,10 +44,9 @@ import org.moire.ultrasonic.util.Util class ArtistRowAdapter( private var artistList: List, private var folderName: String, - private var shouldShowHeader: Boolean, + private var selectFolderHeader: SelectMusicFolderView?, val onArtistClick: (Artist) -> Unit, val onContextMenuClick: (MenuItem, Artist) -> Boolean, - val onFolderClick: (view: View) -> Unit, private val imageLoader: ImageLoader ) : RecyclerView.Adapter(), SectionedAdapter { @@ -80,16 +79,6 @@ class ArtistRowAdapter( var coverArtId: String? = null } - /** - * Holds the view properties of the Header row - */ - class HeaderViewHolder( - itemView: View - ) : RecyclerView.ViewHolder(itemView) { - var folderName: TextView = itemView.findViewById(R.id.select_artist_folder_2) - var layout: LinearLayout = itemView.findViewById(R.id.select_artist_folder) - } - override fun onCreateViewHolder( parent: ViewGroup, viewType: Int @@ -99,9 +88,7 @@ class ArtistRowAdapter( .inflate(R.layout.artist_list_item, parent, false) return ArtistViewHolder(row) } - val header = LayoutInflater.from(parent.context) - .inflate(R.layout.select_artist_header, parent, false) - return HeaderViewHolder(header) + return selectFolderHeader!! } override fun onViewRecycled(holder: RecyclerView.ViewHolder) { @@ -113,7 +100,7 @@ class ArtistRowAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder is ArtistViewHolder) { - val listPosition = if (shouldShowHeader) position - 1 else position + val listPosition = if (selectFolderHeader != null) position - 1 else position holder.textView.text = artistList[listPosition].name holder.section.text = getSectionForArtist(listPosition) holder.layout.setOnClickListener { onArtistClick(artistList[listPosition]) } @@ -130,20 +117,20 @@ class ArtistRowAdapter( } else { holder.coverArt.visibility = View.GONE } - } else if (holder is HeaderViewHolder) { - holder.folderName.text = folderName - holder.layout.setOnClickListener { onFolderClick(holder.layout) } } } - override fun getItemCount() = if (shouldShowHeader) artistList.size + 1 else artistList.size + override fun getItemCount() = if (selectFolderHeader != null) + artistList.size + 1 + else + artistList.size override fun getItemViewType(position: Int): Int { - return if (position == 0 && shouldShowHeader) TYPE_HEADER else TYPE_ITEM + return if (position == 0 && selectFolderHeader != null) TYPE_HEADER else TYPE_ITEM } override fun getSectionName(position: Int): String { - var listPosition = if (shouldShowHeader) position - 1 else position + var listPosition = if (selectFolderHeader != null) position - 1 else position // Show the first artist's initial in the popup when the list is // scrolled up to the "Select Folder" row diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumFragment.kt index 515c3389..1b0eb7d0 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumFragment.kt @@ -24,13 +24,16 @@ import java.util.Collections import java.util.LinkedList import java.util.Random import org.koin.android.ext.android.inject +import org.koin.android.viewmodel.ext.android.viewModel import org.moire.ultrasonic.R +import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.fragment.FragmentTitle.Companion.getTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MusicService +import org.moire.ultrasonic.service.MusicServiceFactory import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService import org.moire.ultrasonic.subsonic.DownloadHandler import org.moire.ultrasonic.subsonic.ImageLoaderProvider @@ -45,6 +48,7 @@ import org.moire.ultrasonic.util.FragmentBackgroundTask import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.view.AlbumView import org.moire.ultrasonic.view.EntryAdapter +import org.moire.ultrasonic.view.SelectMusicFolderView import org.moire.ultrasonic.view.SongView import timber.log.Timber @@ -57,6 +61,7 @@ class SelectAlbumFragment : Fragment() { private var refreshAlbumListView: SwipeRefreshLayout? = null private var albumListView: ListView? = null private var header: View? = null + private var selectFolderHeader: SelectMusicFolderView? = null private var albumButtons: View? = null private var emptyView: View? = null private var selectButton: ImageView? = null @@ -73,6 +78,7 @@ class SelectAlbumFragment : Fragment() { private var playAllButton: MenuItem? = null private var shareButton: MenuItem? = null private var showHeader = true + private var showSelectFolderHeader = false private val random: Random = SecureRandom() private val mediaPlayerController: MediaPlayerController by inject() @@ -82,6 +88,9 @@ class SelectAlbumFragment : Fragment() { private val imageLoaderProvider: ImageLoaderProvider by inject() private val shareHandler: ShareHandler by inject() private var cancellationToken: CancellationToken? = null + private val activeServerProvider: ActiveServerProvider by inject() + private val serverSettingsModel: ServerSettingsModel by viewModel() + private val artistListModel: ArtistListModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { Util.applyTheme(this.context) @@ -117,6 +126,24 @@ class SelectAlbumFragment : Fragment() { false ) + selectFolderHeader = SelectMusicFolderView( + requireContext(), albumListView!!, + MusicServiceFactory.getMusicService(requireContext()).getMusicFolders( + false, requireContext() + ), + activeServerProvider.getActiveServer().musicFolderId, + { _, selectedFolderId -> + if (!ActiveServerProvider.isOffline(context)) { + val currentSetting = activeServerProvider.getActiveServer() + currentSetting.musicFolderId = selectedFolderId + serverSettingsModel.updateItem(currentSetting) + } + artistListModel.refresh(refreshAlbumListView!!) + + this.updateDisplay(true) + } + ) + albumListView!!.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE) albumListView!!.setOnItemClickListener( OnItemClickListener @@ -735,6 +762,7 @@ class SelectAlbumFragment : Fragment() { private fun getAlbumList(albumListType: String, albumListTitle: Int, size: Int, offset: Int) { showHeader = false + showSelectFolderHeader = !isOffline(context) setTitle(this, albumListTitle) // setActionBarSubtitle(albumListTitle); @@ -747,10 +775,12 @@ class SelectAlbumFragment : Fragment() { } override fun load(service: MusicService): MusicDirectory { + val musicFolderId = + this@SelectAlbumFragment.activeServerProvider.getActiveServer().musicFolderId return if (Util.getShouldUseId3Tags(context)) service.getAlbumList2(albumListType, size, offset, context) else - service.getAlbumList(albumListType, size, offset, context) + service.getAlbumList(albumListType, size, offset, musicFolderId, context) } override fun done(result: Pair) { @@ -1012,6 +1042,12 @@ class SelectAlbumFragment : Fragment() { } } } else { + if (showSelectFolderHeader) { + if (albumListView!!.headerViewsCount == 0) { + albumListView!!.addHeaderView(selectFolderHeader!!.itemView, null, false) + } + } + pinButton!!.visibility = View.GONE unpinButton!!.visibility = View.GONE downloadButton!!.visibility = View.GONE diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectArtistFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectArtistFragment.kt index 6c851f5c..2aba479b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectArtistFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectArtistFragment.kt @@ -5,7 +5,6 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.widget.PopupMenu import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.navigation.fragment.findNavController @@ -19,10 +18,12 @@ import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.MusicFolder import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle +import org.moire.ultrasonic.service.MusicServiceFactory import org.moire.ultrasonic.subsonic.DownloadHandler import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Util +import org.moire.ultrasonic.view.SelectMusicFolderView /** * Displays the list of Artists from the media library @@ -39,6 +40,7 @@ class SelectArtistFragment : Fragment() { private var musicFolders: List? = null private lateinit var viewManager: RecyclerView.LayoutManager private lateinit var viewAdapter: ArtistRowAdapter + private var selectFolderHeader: SelectMusicFolderView? = null @Override override fun onCreate(savedInstanceState: Bundle?) { @@ -60,10 +62,26 @@ class SelectArtistFragment : Fragment() { artistListModel.refresh(refreshArtistListView!!) } - val shouldShowHeader = ( - !ActiveServerProvider.isOffline(this.context) && - !Util.getShouldUseId3Tags(this.context) + if (!ActiveServerProvider.isOffline(this.context) && + !Util.getShouldUseId3Tags(this.context) + ) { + selectFolderHeader = SelectMusicFolderView( + requireContext(), view as ViewGroup, + MusicServiceFactory.getMusicService(requireContext()).getMusicFolders( + false, requireContext() + ), + activeServerProvider.getActiveServer().musicFolderId, + { musicFolderName, selectedFolderId -> + if (!ActiveServerProvider.isOffline(context)) { + val currentSetting = activeServerProvider.getActiveServer() + currentSetting.musicFolderId = selectedFolderId + serverSettingsModel.updateItem(currentSetting) + } + viewAdapter.setFolderName(musicFolderName) + artistListModel.refresh(refreshArtistListView!!) + } ) + } val title = arguments?.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE) @@ -102,10 +120,9 @@ class SelectArtistFragment : Fragment() { viewAdapter = ArtistRowAdapter( artists.value ?: listOf(), getText(R.string.select_artist_all_folders).toString(), - shouldShowHeader, + selectFolderHeader, { artist -> onItemClick(artist) }, { menuItem, artist -> onArtistMenuItemSelected(menuItem, artist) }, - { onFolderClick(it) }, imageLoaderProvider.getImageLoader() ) @@ -138,31 +155,6 @@ class SelectArtistFragment : Fragment() { findNavController().navigate(R.id.selectArtistToSelectAlbum, bundle) } - private fun onFolderClick(view: View) { - val popup = PopupMenu(this.context, view) - - val musicFolderId = activeServerProvider.getActiveServer().musicFolderId - var menuItem = popup.menu.add( - MENU_GROUP_MUSIC_FOLDER, -1, 0, R.string.select_artist_all_folders - ) - if (musicFolderId == null || musicFolderId.isEmpty()) { - menuItem.isChecked = true - } - if (musicFolders != null) { - for (i in musicFolders!!.indices) { - val (id, name) = musicFolders!![i] - menuItem = popup.menu.add(MENU_GROUP_MUSIC_FOLDER, i, i + 1, name) - if (id == musicFolderId) { - menuItem.isChecked = true - } - } - } - popup.menu.setGroupCheckable(MENU_GROUP_MUSIC_FOLDER, true, true) - - popup.setOnMenuItemClickListener { item -> onFolderMenuItemSelected(item) } - popup.show() - } - private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean { when (menuItem.itemId) { R.id.artist_menu_play_now -> @@ -246,23 +238,4 @@ class SelectArtistFragment : Fragment() { } return true } - - private fun onFolderMenuItemSelected(menuItem: MenuItem): Boolean { - val selectedFolder = if (menuItem.itemId == -1) null else musicFolders!![menuItem.itemId] - val musicFolderId = selectedFolder?.id - val musicFolderName = selectedFolder?.name - ?: getString(R.string.select_artist_all_folders) - if (!ActiveServerProvider.isOffline(this.context)) { - val currentSetting = activeServerProvider.getActiveServer() - currentSetting.musicFolderId = musicFolderId - serverSettingsModel.updateItem(currentSetting) - } - viewAdapter.setFolderName(musicFolderName) - artistListModel.refresh(refreshArtistListView!!) - return true - } - - companion object { - private const val MENU_GROUP_MUSIC_FOLDER = 10 - } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt index 6e5e1daf..5f02d287 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -445,10 +445,11 @@ open class RESTMusicService( type: String, size: Int, offset: Int, + musicFolderId: String?, context: Context ): MusicDirectory { val response = responseChecker.callWithResponseCheck { api -> - api.getAlbumList(fromName(type), size, offset, null, null, null, null) + api.getAlbumList(fromName(type), size, offset, null, null, null, musicFolderId) .execute() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SelectMusicFolderView.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SelectMusicFolderView.kt new file mode 100644 index 00000000..f02bbeb1 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SelectMusicFolderView.kt @@ -0,0 +1,81 @@ +package org.moire.ultrasonic.view + +import android.content.Context +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.PopupMenu +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import org.moire.ultrasonic.R +import org.moire.ultrasonic.domain.MusicFolder + +/** + * This little view shows the currently selected Folder (or catalog) on the music server. + * When clicked it will drop down a list of all available Folders and allow you to + * select one. The intended usage us to supply a filter to lists of artists, albums, etc + */ +class SelectMusicFolderView( + private val context: Context, + root: ViewGroup, + private val musicFolders: List, + private var selectedFolderId: String?, + private val onUpdate: (String, String?) -> Unit +) : RecyclerView.ViewHolder( + LayoutInflater.from(context).inflate( + R.layout.select_folder_header, root, false + ) +) { + private val folderName: TextView = itemView.findViewById(R.id.select_folder_2) + private val layout: LinearLayout = itemView.findViewById(R.id.select_folder_header) + + init { + if (selectedFolderId != null) { + for ((id, name) in musicFolders) { + if (id == selectedFolderId) { + folderName.text = name + break + } + } + } + layout.setOnClickListener { onFolderClick() } + } + + private fun onFolderClick() { + val popup = PopupMenu(context, layout) + val MENU_GROUP_MUSIC_FOLDER = 10 + + var menuItem = popup.menu.add( + MENU_GROUP_MUSIC_FOLDER, -1, 0, R.string.select_artist_all_folders + ) + if (selectedFolderId == null || selectedFolderId!!.isEmpty()) { + menuItem.isChecked = true + } + for (i in musicFolders.indices) { + val (id, name) = musicFolders[i] + menuItem = popup.menu.add(MENU_GROUP_MUSIC_FOLDER, i, i + 1, name) + if (id == selectedFolderId) { + menuItem.isChecked = true + } + } + + popup.menu.setGroupCheckable(MENU_GROUP_MUSIC_FOLDER, true, true) + + popup.setOnMenuItemClickListener { item -> onFolderMenuItemSelected(item) } + popup.show() + } + + private fun onFolderMenuItemSelected(menuItem: MenuItem): Boolean { + val selectedFolder = if (menuItem.itemId == -1) null else musicFolders[menuItem.itemId] + val musicFolderName = selectedFolder?.name + ?: context.getString(R.string.select_artist_all_folders) + selectedFolderId = selectedFolder?.id + + menuItem.isChecked = true + folderName.text = musicFolderName + onUpdate(musicFolderName, selectedFolderId) + + return true + } +} diff --git a/ultrasonic/src/main/res/layout/select_artist_header.xml b/ultrasonic/src/main/res/layout/select_folder_header.xml similarity index 89% rename from ultrasonic/src/main/res/layout/select_artist_header.xml rename to ultrasonic/src/main/res/layout/select_folder_header.xml index 7a56076e..e1c30ac2 100644 --- a/ultrasonic/src/main/res/layout/select_artist_header.xml +++ b/ultrasonic/src/main/res/layout/select_folder_header.xml @@ -1,6 +1,6 @@