Introduce the SelectMusicFolderView

Signed-off-by: James Wells <james@jameswells.net>
This commit is contained in:
James Wells 2021-04-01 23:00:01 -04:00
parent 97d68eb08f
commit 191f9512bb
No known key found for this signature in database
GPG Key ID: 7A9AB99C0B899FB7
9 changed files with 160 additions and 82 deletions

View File

@ -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

View File

@ -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;

View File

@ -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");
}

View File

@ -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<Artist>,
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<RecyclerView.ViewHolder>(), 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

View File

@ -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<MusicDirectory, Boolean>) {
@ -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

View File

@ -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<MusicFolder>? = 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
}
}

View File

@ -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()
}

View File

@ -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<MusicFolder>,
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
}
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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_height="wrap_content"
a:minHeight="?android:attr/listPreferredItemHeight"
@ -24,7 +24,7 @@
a:orientation="vertical" >
<TextView
a:id="@+id/select_artist_folder_1"
a:id="@+id/select_folder_1"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:layout_marginLeft="10dip"
@ -33,7 +33,7 @@
a:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
a:id="@+id/select_artist_folder_2"
a:id="@+id/select_folder_2"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:layout_marginLeft="10dip"