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..d8033ceb 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java @@ -76,6 +76,7 @@ public class CachedMusicService implements MusicService private final TimeLimitedCache> cachedGenres = new TimeLimitedCache<>(10 * 3600, TimeUnit.SECONDS); private String restUrl; + private String cachedMusicFolderId; public CachedMusicService(MusicService musicService) { @@ -284,15 +285,15 @@ 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 - public MusicDirectory getAlbumList2(String type, int size, int offset, Context context) throws Exception + public MusicDirectory getAlbumList2(String type, int size, int offset, String musicFolderId, Context context) throws Exception { - return musicService.getAlbumList2(type, size, offset, context); + return musicService.getAlbumList2(type, size, offset, musicFolderId, context); } @Override @@ -370,7 +371,8 @@ public class CachedMusicService implements MusicService private void checkSettingsChanged() { String newUrl = activeServerProvider.getValue().getRestUrl(null); - if (!Util.equals(newUrl, restUrl)) + String newFolderId = activeServerProvider.getValue().getActiveServer().getMusicFolderId(); + if (!Util.equals(newUrl, restUrl) || !Util.equals(cachedMusicFolderId,newFolderId)) { cachedMusicFolders.clear(); cachedMusicDirectories.clear(); @@ -382,6 +384,7 @@ public class CachedMusicService implements MusicService cachedArtist.clear(); cachedUserInfo.clear(); restUrl = newUrl; + cachedMusicFolderId = newFolderId; } } 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..8ef64e81 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java @@ -90,9 +90,9 @@ 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; + MusicDirectory getAlbumList2(String type, int size, int offset, String musicFolderId, Context context) throws Exception; MusicDirectory getRandomSongs(int size, 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..80688551 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"); } @@ -809,7 +809,7 @@ public class OfflineMusicService implements MusicService } @Override - public MusicDirectory getAlbumList2(String type, int size, int offset, Context context) { + public MusicDirectory getAlbumList2(String type, int size, int offset, String musicFolderId, Context context) { Timber.w("OfflineMusicService.getAlbumList2 was called but it isn't available"); return null; } 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..6a56e1d2 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,17 +36,16 @@ 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 */ 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 { @@ -59,14 +57,6 @@ class ArtistRowAdapter( notifyDataSetChanged() } - /** - * Sets the name of the folder to be displayed n the Header (first) row - */ - fun setFolderName(name: String) { - folderName = name - notifyDataSetChanged() - } - /** * Holds the view properties of an Artist row */ @@ -80,16 +70,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 +79,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 +91,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 +108,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..6a9043aa 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumFragment.kt @@ -1,6 +1,8 @@ package org.moire.ultrasonic.fragment import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.view.ContextMenu import android.view.ContextMenu.ContextMenuInfo import android.view.LayoutInflater @@ -16,6 +18,9 @@ import android.widget.ImageView import android.widget.ListView import android.widget.TextView import androidx.fragment.app.Fragment +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.viewModelScope import androidx.navigation.Navigation import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener @@ -23,14 +28,23 @@ import java.security.SecureRandom import java.util.Collections import java.util.LinkedList import java.util.Random +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.koin.android.ext.android.inject +import org.koin.android.viewmodel.ext.android.viewModel import org.moire.ultrasonic.R +import org.moire.ultrasonic.api.subsonic.models.AlbumListType +import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.domain.MusicDirectory +import org.moire.ultrasonic.domain.MusicFolder import org.moire.ultrasonic.fragment.FragmentTitle.Companion.getTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle +import org.moire.ultrasonic.service.CommunicationErrorHandler 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 +59,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,8 +72,9 @@ 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 emptyView: TextView? = null private var selectButton: ImageView? = null private var playNowButton: ImageView? = null private var playNextButton: ImageView? = null @@ -73,7 +89,9 @@ 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 musicFolders: MutableLiveData> = MutableLiveData() private val mediaPlayerController: MediaPlayerController by inject() private val videoPlayer: VideoPlayer by inject() @@ -82,6 +100,8 @@ 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() override fun onCreate(savedInstanceState: Bundle?) { Util.applyTheme(this.context) @@ -117,6 +137,29 @@ class SelectAlbumFragment : Fragment() { false ) + selectFolderHeader = SelectMusicFolderView( + requireContext(), view as ViewGroup, + { selectedFolderId -> + if (!ActiveServerProvider.isOffline(context)) { + val currentSetting = activeServerProvider.getActiveServer() + currentSetting.musicFolderId = selectedFolderId + serverSettingsModel.updateItem(currentSetting) + } + this.updateDisplay(true) + } + ) + musicFolders.observe( + viewLifecycleOwner, + Observer { changedFolders -> + if (changedFolders != null) { + selectFolderHeader!!.setData( + activeServerProvider.getActiveServer().musicFolderId, + changedFolders + ) + } + } + ) + albumListView!!.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE) albumListView!!.setOnItemClickListener( OnItemClickListener @@ -174,7 +217,7 @@ class SelectAlbumFragment : Fragment() { downloadButton = view.findViewById(R.id.select_album_download) deleteButton = view.findViewById(R.id.select_album_delete) moreButton = view.findViewById(R.id.select_album_more) - emptyView = view.findViewById(R.id.select_album_empty) + emptyView = TextView(requireContext()) selectButton!!.setOnClickListener( View.OnClickListener @@ -269,6 +312,8 @@ class SelectAlbumFragment : Fragment() { Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 ) + backgroundLoadMusicFolders(refresh) + if (playlistId != null) { getPlaylist(playlistId, playlistName) } else if (podcastChannelId != null) { @@ -298,6 +343,29 @@ class SelectAlbumFragment : Fragment() { } } + private fun backgroundLoadMusicFolders(refresh: Boolean) { + serverSettingsModel.viewModelScope.launch { + refreshAlbumListView!!.isRefreshing = true + loadMusicFolders(refresh) + refreshAlbumListView!!.isRefreshing = false + } + } + + private suspend fun loadMusicFolders(refresh: Boolean) { + withContext(Dispatchers.IO) { + if (!isOffline(context)) { + val musicService = MusicServiceFactory.getMusicService(requireContext()) + try { + musicFolders.postValue(musicService.getMusicFolders(refresh, context)) + } catch (exception: Exception) { + Handler(Looper.getMainLooper()).post { + CommunicationErrorHandler.handleError(exception, requireContext()) + } + } + } + } + } + override fun onCreateContextMenu(menu: ContextMenu, view: View, menuInfo: ContextMenuInfo?) { super.onCreateContextMenu(menu, view, menuInfo) val info = menuInfo as AdapterContextMenuInfo? @@ -735,6 +803,11 @@ class SelectAlbumFragment : Fragment() { private fun getAlbumList(albumListType: String, albumListTitle: Int, size: Int, offset: Int) { showHeader = false + showSelectFolderHeader = !isOffline(context) && !Util.getShouldUseId3Tags(context) && + ( + (albumListType == AlbumListType.SORTED_BY_NAME.toString()) || + (albumListType == AlbumListType.SORTED_BY_ARTIST.toString()) + ) setTitle(this, albumListTitle) // setActionBarSubtitle(albumListTitle); @@ -747,10 +820,15 @@ class SelectAlbumFragment : Fragment() { } override fun load(service: MusicService): MusicDirectory { + val musicFolderId = if (showSelectFolderHeader) { + this@SelectAlbumFragment.activeServerProvider.getActiveServer().musicFolderId + } else { + null + } return if (Util.getShouldUseId3Tags(context)) - service.getAlbumList2(albumListType, size, offset, context) + service.getAlbumList2(albumListType, size, offset, musicFolderId, context) else - service.getAlbumList(albumListType, size, offset, context) + service.getAlbumList(albumListType, size, offset, musicFolderId, context) } override fun done(result: Pair) { @@ -1012,6 +1090,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 @@ -1034,7 +1118,12 @@ class SelectAlbumFragment : Fragment() { playAllButtonVisible = !(isAlbumList || entries.isEmpty()) && !allVideos shareButtonVisible = !isOffline(context) && songCount > 0 - emptyView!!.visibility = if (entries.isEmpty()) View.VISIBLE else View.GONE + albumListView!!.removeHeaderView(emptyView!!) + if (entries.isEmpty()) { + emptyView!!.text = "No Media Found" + emptyView!!.setPadding(10, 10, 10, 10) + albumListView!!.addHeaderView(emptyView, null, false) + } if (playAllButton != null) { playAllButton!!.isVisible = playAllButtonVisible 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..39b2ebac 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 @@ -17,12 +16,12 @@ import org.koin.android.viewmodel.ext.android.viewModel import org.moire.ultrasonic.R 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.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 @@ -36,9 +35,9 @@ class SelectArtistFragment : Fragment() { private var refreshArtistListView: SwipeRefreshLayout? = null private var artistListView: RecyclerView? = null - 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 +59,22 @@ 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, + { selectedFolderId -> + if (!ActiveServerProvider.isOffline(context)) { + val currentSetting = activeServerProvider.getActiveServer() + currentSetting.musicFolderId = selectedFolderId + serverSettingsModel.updateItem(currentSetting) + } + viewAdapter.notifyDataSetChanged() + artistListModel.refresh(refreshArtistListView!!) + } ) + } val title = arguments?.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE) @@ -78,8 +89,6 @@ class SelectArtistFragment : Fragment() { setTitle(this, title) } - musicFolders = null - val refresh = arguments?.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH) ?: false artistListModel.getMusicFolders() @@ -87,8 +96,11 @@ class SelectArtistFragment : Fragment() { viewLifecycleOwner, Observer { changedFolders -> if (changedFolders != null) { - musicFolders = changedFolders - viewAdapter.setFolderName(getMusicFolderName(changedFolders)) + viewAdapter.notifyDataSetChanged() + selectFolderHeader!!.setData( + activeServerProvider.getActiveServer().musicFolderId, + changedFolders + ) } } ) @@ -101,11 +113,9 @@ class SelectArtistFragment : Fragment() { viewManager = LinearLayoutManager(this.context) 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() ) @@ -117,18 +127,6 @@ class SelectArtistFragment : Fragment() { super.onViewCreated(view, savedInstanceState) } - private fun getMusicFolderName(musicFolders: List): String { - val musicFolderId = activeServerProvider.getActiveServer().musicFolderId - if (musicFolderId != null && musicFolderId != "") { - for ((id, name) in musicFolders) { - if (id == musicFolderId) { - return name - } - } - } - return getText(R.string.select_artist_all_folders).toString() - } - private fun onItemClick(artist: Artist) { val bundle = Bundle() bundle.putString(Constants.INTENT_EXTRA_NAME_ID, artist.id) @@ -138,31 +136,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 +219,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..8f380d9e 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -114,7 +114,9 @@ open class RESTMusicService( refresh: Boolean, context: Context ): Indexes { - val cachedIndexes = fileStorage.load(INDEXES_STORAGE_NAME, getIndexesSerializer()) + val indexName = INDEXES_STORAGE_NAME + (musicFolderId ?: "") + + val cachedIndexes = fileStorage.load(indexName, getIndexesSerializer()) if (cachedIndexes != null && !refresh) return cachedIndexes val response = responseChecker.callWithResponseCheck { api -> @@ -122,7 +124,7 @@ open class RESTMusicService( } val indexes = response.body()!!.indexes.toDomainEntity() - fileStorage.store(INDEXES_STORAGE_NAME, indexes, getIndexesSerializer()) + fileStorage.store(indexName, indexes, getIndexesSerializer()) return indexes } @@ -445,10 +447,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() } @@ -464,6 +467,7 @@ open class RESTMusicService( type: String, size: Int, offset: Int, + musicFolderId: String?, context: Context ): MusicDirectory { val response = responseChecker.callWithResponseCheck { api -> @@ -474,7 +478,7 @@ open class RESTMusicService( null, null, null, - null + musicFolderId ).execute() } @@ -936,6 +940,7 @@ open class RESTMusicService( companion object { private const val MUSIC_FOLDER_STORAGE_NAME = "music_folder" private const val INDEXES_STORAGE_NAME = "indexes" + private const val INDEXES_FOLDER_STORAGE_NAME = "indexes_folder" private const val ARTISTS_STORAGE_NAME = "artists" } } 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..28d34b2d --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SelectMusicFolderView.kt @@ -0,0 +1,89 @@ +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 is to supply a filter to lists of artists, albums, etc + */ +class SelectMusicFolderView( + private val context: Context, + root: ViewGroup, + private val onUpdate: (String?) -> Unit +) : RecyclerView.ViewHolder( + LayoutInflater.from(context).inflate( + R.layout.select_folder_header, root, false + ) +) { + private var musicFolders: List = mutableListOf() + private var selectedFolderId: String? = null + private val folderName: TextView = itemView.findViewById(R.id.select_folder_name) + private val layout: LinearLayout = itemView.findViewById(R.id.select_folder_header) + private val MENU_GROUP_MUSIC_FOLDER = 10 + + init { + folderName.text = context.getString(R.string.select_artist_all_folders) + layout.setOnClickListener { onFolderClick() } + } + + fun setData(selectedId: String?, folders: List) { + selectedFolderId = selectedId + musicFolders = folders + if (selectedFolderId != null) { + for ((id, name) in musicFolders) { + if (id == selectedFolderId) { + folderName.text = name + break + } + } + } else { + folderName.text = context.getString(R.string.select_artist_all_folders) + } + } + + private fun onFolderClick() { + val popup = PopupMenu(context, layout) + + 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 + } + musicFolders.forEachIndexed { i, musicFolder -> + val (id, name) = musicFolder + 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(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 90% rename from ultrasonic/src/main/res/layout/select_artist_header.xml rename to ultrasonic/src/main/res/layout/select_folder_header.xml index 7a56076e..68538369 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 @@