Merge branch 'develop' into mediastore

This commit is contained in:
Nite 2021-04-15 09:48:07 +02:00 committed by GitHub
commit fbcbc65be8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 240 additions and 122 deletions

View File

@ -76,6 +76,7 @@ public class CachedMusicService implements MusicService
private final TimeLimitedCache<List<Genre>> cachedGenres = new TimeLimitedCache<>(10 * 3600, TimeUnit.SECONDS); private final TimeLimitedCache<List<Genre>> cachedGenres = new TimeLimitedCache<>(10 * 3600, TimeUnit.SECONDS);
private String restUrl; private String restUrl;
private String cachedMusicFolderId;
public CachedMusicService(MusicService musicService) public CachedMusicService(MusicService musicService)
{ {
@ -284,15 +285,15 @@ public class CachedMusicService implements MusicService
} }
@Override @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 @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 @Override
@ -370,7 +371,8 @@ public class CachedMusicService implements MusicService
private void checkSettingsChanged() private void checkSettingsChanged()
{ {
String newUrl = activeServerProvider.getValue().getRestUrl(null); 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(); cachedMusicFolders.clear();
cachedMusicDirectories.clear(); cachedMusicDirectories.clear();
@ -382,6 +384,7 @@ public class CachedMusicService implements MusicService
cachedArtist.clear(); cachedArtist.clear();
cachedUserInfo.clear(); cachedUserInfo.clear();
restUrl = newUrl; restUrl = newUrl;
cachedMusicFolderId = newFolderId;
} }
} }

View File

@ -90,9 +90,9 @@ public interface MusicService
void scrobble(String id, boolean submission, Context context) throws Exception; 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; MusicDirectory getRandomSongs(int size, Context context) throws Exception;

View File

@ -702,7 +702,7 @@ public class OfflineMusicService implements MusicService
} }
@Override @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"); throw new OfflineException("Album lists not available in offline mode");
} }
@ -809,7 +809,7 @@ public class OfflineMusicService implements MusicService
} }
@Override @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"); Timber.w("OfflineMusicService.getAlbumList2 was called but it isn't available");
return null; return null;
} }

View File

@ -24,7 +24,6 @@ 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 android.widget.ImageView
import android.widget.LinearLayout
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
@ -37,17 +36,16 @@ import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.util.ImageLoader import org.moire.ultrasonic.util.ImageLoader
import org.moire.ultrasonic.util.Util 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 * Creates a Row in a RecyclerView which contains the details of an Artist
*/ */
class ArtistRowAdapter( class ArtistRowAdapter(
private var artistList: List<Artist>, private var artistList: List<Artist>,
private var folderName: String, private var selectFolderHeader: SelectMusicFolderView?,
private var shouldShowHeader: Boolean,
val onArtistClick: (Artist) -> Unit, val onArtistClick: (Artist) -> Unit,
val onContextMenuClick: (MenuItem, Artist) -> Boolean, val onContextMenuClick: (MenuItem, Artist) -> Boolean,
val onFolderClick: (view: View) -> Unit,
private val imageLoader: ImageLoader private val imageLoader: ImageLoader
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), SectionedAdapter { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), SectionedAdapter {
@ -59,14 +57,6 @@ class ArtistRowAdapter(
notifyDataSetChanged() 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 * Holds the view properties of an Artist row
*/ */
@ -80,16 +70,6 @@ class ArtistRowAdapter(
var coverArtId: String? = null 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( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
@ -99,9 +79,7 @@ class ArtistRowAdapter(
.inflate(R.layout.artist_list_item, parent, false) .inflate(R.layout.artist_list_item, parent, false)
return ArtistViewHolder(row) return ArtistViewHolder(row)
} }
val header = LayoutInflater.from(parent.context) return selectFolderHeader!!
.inflate(R.layout.select_artist_header, parent, false)
return HeaderViewHolder(header)
} }
override fun onViewRecycled(holder: RecyclerView.ViewHolder) { override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
@ -113,7 +91,7 @@ class ArtistRowAdapter(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ArtistViewHolder) { 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.textView.text = artistList[listPosition].name
holder.section.text = getSectionForArtist(listPosition) holder.section.text = getSectionForArtist(listPosition)
holder.layout.setOnClickListener { onArtistClick(artistList[listPosition]) } holder.layout.setOnClickListener { onArtistClick(artistList[listPosition]) }
@ -130,20 +108,20 @@ class ArtistRowAdapter(
} else { } else {
holder.coverArt.visibility = View.GONE 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 { 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 { 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 // Show the first artist's initial in the popup when the list is
// scrolled up to the "Select Folder" row // scrolled up to the "Select Folder" row

View File

@ -1,6 +1,8 @@
package org.moire.ultrasonic.fragment package org.moire.ultrasonic.fragment
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.ContextMenu import android.view.ContextMenu
import android.view.ContextMenu.ContextMenuInfo import android.view.ContextMenu.ContextMenuInfo
import android.view.LayoutInflater import android.view.LayoutInflater
@ -16,6 +18,9 @@ import android.widget.ImageView
import android.widget.ListView import android.widget.ListView
import android.widget.TextView import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope
import androidx.navigation.Navigation import androidx.navigation.Navigation
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
@ -23,14 +28,23 @@ import java.security.SecureRandom
import java.util.Collections import java.util.Collections
import java.util.LinkedList import java.util.LinkedList
import java.util.Random 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.ext.android.inject
import org.koin.android.viewmodel.ext.android.viewModel
import org.moire.ultrasonic.R 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.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.MusicDirectory 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.getTitle
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle 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.MediaPlayerController
import org.moire.ultrasonic.service.MusicService import org.moire.ultrasonic.service.MusicService
import org.moire.ultrasonic.service.MusicServiceFactory
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.subsonic.DownloadHandler import org.moire.ultrasonic.subsonic.DownloadHandler
import org.moire.ultrasonic.subsonic.ImageLoaderProvider 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.util.Util
import org.moire.ultrasonic.view.AlbumView import org.moire.ultrasonic.view.AlbumView
import org.moire.ultrasonic.view.EntryAdapter import org.moire.ultrasonic.view.EntryAdapter
import org.moire.ultrasonic.view.SelectMusicFolderView
import org.moire.ultrasonic.view.SongView import org.moire.ultrasonic.view.SongView
import timber.log.Timber import timber.log.Timber
@ -57,8 +72,9 @@ class SelectAlbumFragment : Fragment() {
private var refreshAlbumListView: SwipeRefreshLayout? = null private var refreshAlbumListView: SwipeRefreshLayout? = null
private var albumListView: ListView? = null private var albumListView: ListView? = null
private var header: View? = null private var header: View? = null
private var selectFolderHeader: SelectMusicFolderView? = null
private var albumButtons: View? = null private var albumButtons: View? = null
private var emptyView: View? = null private var emptyView: TextView? = null
private var selectButton: ImageView? = null private var selectButton: ImageView? = null
private var playNowButton: ImageView? = null private var playNowButton: ImageView? = null
private var playNextButton: ImageView? = null private var playNextButton: ImageView? = null
@ -73,7 +89,9 @@ class SelectAlbumFragment : Fragment() {
private var playAllButton: MenuItem? = null private var playAllButton: MenuItem? = null
private var shareButton: MenuItem? = null private var shareButton: MenuItem? = null
private var showHeader = true private var showHeader = true
private var showSelectFolderHeader = false
private val random: Random = SecureRandom() private val random: Random = SecureRandom()
private val musicFolders: MutableLiveData<List<MusicFolder>> = MutableLiveData()
private val mediaPlayerController: MediaPlayerController by inject() private val mediaPlayerController: MediaPlayerController by inject()
private val videoPlayer: VideoPlayer by inject() private val videoPlayer: VideoPlayer by inject()
@ -82,6 +100,8 @@ class SelectAlbumFragment : Fragment() {
private val imageLoaderProvider: ImageLoaderProvider by inject() private val imageLoaderProvider: ImageLoaderProvider by inject()
private val shareHandler: ShareHandler by inject() private val shareHandler: ShareHandler by inject()
private var cancellationToken: CancellationToken? = null private var cancellationToken: CancellationToken? = null
private val activeServerProvider: ActiveServerProvider by inject()
private val serverSettingsModel: ServerSettingsModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Util.applyTheme(this.context) Util.applyTheme(this.context)
@ -117,6 +137,29 @@ class SelectAlbumFragment : Fragment() {
false 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!!.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE)
albumListView!!.setOnItemClickListener( albumListView!!.setOnItemClickListener(
OnItemClickListener OnItemClickListener
@ -174,7 +217,7 @@ class SelectAlbumFragment : Fragment() {
downloadButton = view.findViewById(R.id.select_album_download) downloadButton = view.findViewById(R.id.select_album_download)
deleteButton = view.findViewById(R.id.select_album_delete) deleteButton = view.findViewById(R.id.select_album_delete)
moreButton = view.findViewById(R.id.select_album_more) moreButton = view.findViewById(R.id.select_album_more)
emptyView = view.findViewById(R.id.select_album_empty) emptyView = TextView(requireContext())
selectButton!!.setOnClickListener( selectButton!!.setOnClickListener(
View.OnClickListener View.OnClickListener
@ -269,6 +312,8 @@ class SelectAlbumFragment : Fragment() {
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0 Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0
) )
backgroundLoadMusicFolders(refresh)
if (playlistId != null) { if (playlistId != null) {
getPlaylist(playlistId, playlistName) getPlaylist(playlistId, playlistName)
} else if (podcastChannelId != null) { } 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?) { override fun onCreateContextMenu(menu: ContextMenu, view: View, menuInfo: ContextMenuInfo?) {
super.onCreateContextMenu(menu, view, menuInfo) super.onCreateContextMenu(menu, view, menuInfo)
val info = menuInfo as AdapterContextMenuInfo? val info = menuInfo as AdapterContextMenuInfo?
@ -735,6 +803,11 @@ class SelectAlbumFragment : Fragment() {
private fun getAlbumList(albumListType: String, albumListTitle: Int, size: Int, offset: Int) { private fun getAlbumList(albumListType: String, albumListTitle: Int, size: Int, offset: Int) {
showHeader = false showHeader = false
showSelectFolderHeader = !isOffline(context) && !Util.getShouldUseId3Tags(context) &&
(
(albumListType == AlbumListType.SORTED_BY_NAME.toString()) ||
(albumListType == AlbumListType.SORTED_BY_ARTIST.toString())
)
setTitle(this, albumListTitle) setTitle(this, albumListTitle)
// setActionBarSubtitle(albumListTitle); // setActionBarSubtitle(albumListTitle);
@ -747,10 +820,15 @@ class SelectAlbumFragment : Fragment() {
} }
override fun load(service: MusicService): MusicDirectory { override fun load(service: MusicService): MusicDirectory {
val musicFolderId = if (showSelectFolderHeader) {
this@SelectAlbumFragment.activeServerProvider.getActiveServer().musicFolderId
} else {
null
}
return if (Util.getShouldUseId3Tags(context)) return if (Util.getShouldUseId3Tags(context))
service.getAlbumList2(albumListType, size, offset, context) service.getAlbumList2(albumListType, size, offset, musicFolderId, context)
else else
service.getAlbumList(albumListType, size, offset, context) service.getAlbumList(albumListType, size, offset, musicFolderId, context)
} }
override fun done(result: Pair<MusicDirectory, Boolean>) { override fun done(result: Pair<MusicDirectory, Boolean>) {
@ -1012,6 +1090,12 @@ class SelectAlbumFragment : Fragment() {
} }
} }
} else { } else {
if (showSelectFolderHeader) {
if (albumListView!!.headerViewsCount == 0) {
albumListView!!.addHeaderView(selectFolderHeader!!.itemView, null, false)
}
}
pinButton!!.visibility = View.GONE pinButton!!.visibility = View.GONE
unpinButton!!.visibility = View.GONE unpinButton!!.visibility = View.GONE
downloadButton!!.visibility = View.GONE downloadButton!!.visibility = View.GONE
@ -1034,7 +1118,12 @@ class SelectAlbumFragment : Fragment() {
playAllButtonVisible = !(isAlbumList || entries.isEmpty()) && !allVideos playAllButtonVisible = !(isAlbumList || entries.isEmpty()) && !allVideos
shareButtonVisible = !isOffline(context) && songCount > 0 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) { if (playAllButton != null) {
playAllButton!!.isVisible = playAllButtonVisible playAllButton!!.isVisible = playAllButtonVisible

View File

@ -5,7 +5,6 @@ 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.PopupMenu
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController 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.R
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.MusicFolder
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.subsonic.DownloadHandler import org.moire.ultrasonic.subsonic.DownloadHandler
import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.view.SelectMusicFolderView
/** /**
* Displays the list of Artists from the media library * Displays the list of Artists from the media library
@ -36,9 +35,9 @@ class SelectArtistFragment : Fragment() {
private var refreshArtistListView: SwipeRefreshLayout? = null private var refreshArtistListView: SwipeRefreshLayout? = null
private var artistListView: RecyclerView? = null private var artistListView: RecyclerView? = null
private var musicFolders: List<MusicFolder>? = null
private lateinit var viewManager: RecyclerView.LayoutManager private lateinit var viewManager: RecyclerView.LayoutManager
private lateinit var viewAdapter: ArtistRowAdapter private lateinit var viewAdapter: ArtistRowAdapter
private var selectFolderHeader: SelectMusicFolderView? = null
@Override @Override
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -60,10 +59,22 @@ class SelectArtistFragment : Fragment() {
artistListModel.refresh(refreshArtistListView!!) artistListModel.refresh(refreshArtistListView!!)
} }
val shouldShowHeader = ( if (!ActiveServerProvider.isOffline(this.context) &&
!ActiveServerProvider.isOffline(this.context) && !Util.getShouldUseId3Tags(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) val title = arguments?.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE)
@ -78,8 +89,6 @@ class SelectArtistFragment : Fragment() {
setTitle(this, title) setTitle(this, title)
} }
musicFolders = null
val refresh = arguments?.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH) ?: false val refresh = arguments?.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH) ?: false
artistListModel.getMusicFolders() artistListModel.getMusicFolders()
@ -87,8 +96,11 @@ class SelectArtistFragment : Fragment() {
viewLifecycleOwner, viewLifecycleOwner,
Observer { changedFolders -> Observer { changedFolders ->
if (changedFolders != null) { if (changedFolders != null) {
musicFolders = changedFolders viewAdapter.notifyDataSetChanged()
viewAdapter.setFolderName(getMusicFolderName(changedFolders)) selectFolderHeader!!.setData(
activeServerProvider.getActiveServer().musicFolderId,
changedFolders
)
} }
} }
) )
@ -101,11 +113,9 @@ class SelectArtistFragment : Fragment() {
viewManager = LinearLayoutManager(this.context) viewManager = LinearLayoutManager(this.context)
viewAdapter = ArtistRowAdapter( viewAdapter = ArtistRowAdapter(
artists.value ?: listOf(), artists.value ?: listOf(),
getText(R.string.select_artist_all_folders).toString(), selectFolderHeader,
shouldShowHeader,
{ artist -> onItemClick(artist) }, { artist -> onItemClick(artist) },
{ menuItem, artist -> onArtistMenuItemSelected(menuItem, artist) }, { menuItem, artist -> onArtistMenuItemSelected(menuItem, artist) },
{ onFolderClick(it) },
imageLoaderProvider.getImageLoader() imageLoaderProvider.getImageLoader()
) )
@ -117,18 +127,6 @@ class SelectArtistFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
} }
private fun getMusicFolderName(musicFolders: List<MusicFolder>): 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) { private fun onItemClick(artist: Artist) {
val bundle = Bundle() val bundle = Bundle()
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, artist.id) bundle.putString(Constants.INTENT_EXTRA_NAME_ID, artist.id)
@ -138,31 +136,6 @@ class SelectArtistFragment : Fragment() {
findNavController().navigate(R.id.selectArtistToSelectAlbum, bundle) 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 { private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.artist_menu_play_now -> R.id.artist_menu_play_now ->
@ -246,23 +219,4 @@ class SelectArtistFragment : Fragment() {
} }
return true 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
}
} }

View File

@ -114,7 +114,9 @@ open class RESTMusicService(
refresh: Boolean, refresh: Boolean,
context: Context context: Context
): Indexes { ): 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 if (cachedIndexes != null && !refresh) return cachedIndexes
val response = responseChecker.callWithResponseCheck { api -> val response = responseChecker.callWithResponseCheck { api ->
@ -122,7 +124,7 @@ open class RESTMusicService(
} }
val indexes = response.body()!!.indexes.toDomainEntity() val indexes = response.body()!!.indexes.toDomainEntity()
fileStorage.store(INDEXES_STORAGE_NAME, indexes, getIndexesSerializer()) fileStorage.store(indexName, indexes, getIndexesSerializer())
return indexes return indexes
} }
@ -445,10 +447,11 @@ open class RESTMusicService(
type: String, type: String,
size: Int, size: Int,
offset: Int, offset: Int,
musicFolderId: String?,
context: Context context: Context
): MusicDirectory { ): MusicDirectory {
val response = responseChecker.callWithResponseCheck { api -> 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() .execute()
} }
@ -464,6 +467,7 @@ open class RESTMusicService(
type: String, type: String,
size: Int, size: Int,
offset: Int, offset: Int,
musicFolderId: String?,
context: Context context: Context
): MusicDirectory { ): MusicDirectory {
val response = responseChecker.callWithResponseCheck { api -> val response = responseChecker.callWithResponseCheck { api ->
@ -474,7 +478,7 @@ open class RESTMusicService(
null, null,
null, null,
null, null,
null musicFolderId
).execute() ).execute()
} }
@ -936,6 +940,7 @@ open class RESTMusicService(
companion object { companion object {
private const val MUSIC_FOLDER_STORAGE_NAME = "music_folder" private const val MUSIC_FOLDER_STORAGE_NAME = "music_folder"
private const val INDEXES_STORAGE_NAME = "indexes" private const val INDEXES_STORAGE_NAME = "indexes"
private const val INDEXES_FOLDER_STORAGE_NAME = "indexes_folder"
private const val ARTISTS_STORAGE_NAME = "artists" private const val ARTISTS_STORAGE_NAME = "artists"
} }
} }

View File

@ -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<MusicFolder> = mutableListOf<MusicFolder>()
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<MusicFolder>) {
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
}
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:id="@+id/select_artist_folder" a:id="@+id/select_folder_header"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:minHeight="?android:attr/listPreferredItemHeight" a:minHeight="?android:attr/listPreferredItemHeight"
@ -24,7 +24,7 @@
a:orientation="vertical" > a:orientation="vertical" >
<TextView <TextView
a:id="@+id/select_artist_folder_1" a:id="@+id/select_folder_title"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_marginLeft="10dip" a:layout_marginLeft="10dip"
@ -33,7 +33,7 @@
a:textAppearance="?android:attr/textAppearanceLarge" /> a:textAppearance="?android:attr/textAppearanceLarge" />
<TextView <TextView
a:id="@+id/select_artist_folder_2" a:id="@+id/select_folder_name"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_marginLeft="10dip" a:layout_marginLeft="10dip"