ultrasonic-app-subsonic-and.../ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SelectAlbumFragment.kt

1297 lines
48 KiB
Kotlin
Raw Normal View History

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
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView.AdapterContextMenuInfo
import android.widget.AdapterView.OnItemClickListener
import android.widget.AdapterView.OnItemLongClickListener
import android.widget.ImageView
import android.widget.ListView
import android.widget.TextView
import androidx.fragment.app.Fragment
2021-04-21 21:55:55 +02:00
import androidx.lifecycle.LifecycleOwner
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
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.getMusicService
import org.moire.ultrasonic.subsonic.DownloadHandler
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
import org.moire.ultrasonic.subsonic.ShareHandler
import org.moire.ultrasonic.subsonic.VideoPlayer
import org.moire.ultrasonic.util.AlbumHeader
import org.moire.ultrasonic.util.CancellationToken
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator
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
2021-04-21 21:55:55 +02:00
import java.security.SecureRandom
import java.util.Collections
import java.util.LinkedList
import java.util.Random
/**
* Displays a group of playable media from the library, which can be an Album, a Playlist, etc.
*/
class SelectAlbumFragment : Fragment() {
private val allSongsId = "-1"
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: TextView? = null
private var selectButton: ImageView? = null
private var playNowButton: ImageView? = null
private var playNextButton: ImageView? = null
private var playLastButton: ImageView? = null
private var pinButton: ImageView? = null
private var unpinButton: ImageView? = null
private var downloadButton: ImageView? = null
private var deleteButton: ImageView? = null
private var moreButton: ImageView? = null
private var playAllButtonVisible = false
private var shareButtonVisible = false
private var playAllButton: MenuItem? = null
private var shareButton: MenuItem? = null
private var showHeader = true
private var showSelectFolderHeader = false
private val random: Random = SecureRandom()
2021-04-21 21:55:55 +02:00
private val musicFolders: MutableLiveData<List<MusicFolder>> = MutableLiveData()
2021-04-21 21:55:55 +02:00
private val artists: MutableLiveData<MusicDirectory> = MutableLiveData()
private val albumList: MutableLiveData<MusicDirectory> = MutableLiveData()
private val currentDirectory: MutableLiveData<MusicDirectory> = MutableLiveData()
private val songsForGenre: MutableLiveData<MusicDirectory> = MutableLiveData()
private var currentDirectoryIsSortable = true
private val mediaPlayerController: MediaPlayerController by inject()
private val videoPlayer: VideoPlayer by inject()
private val downloadHandler: DownloadHandler by inject()
private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
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)
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.select_album, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
cancellationToken = CancellationToken()
albumButtons = view.findViewById(R.id.menu_album)
refreshAlbumListView = view.findViewById(R.id.select_album_entries_refresh)
albumListView = view.findViewById(R.id.select_album_entries_list)
refreshAlbumListView!!.setOnRefreshListener(
OnRefreshListener
{
updateDisplay(true)
}
)
header = LayoutInflater.from(context).inflate(
R.layout.select_album_header, albumListView,
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)
}
)
2021-04-21 21:55:55 +02:00
musicFolders.observe(viewLifecycleOwner, musicFolderObserver)
currentDirectory.observe(viewLifecycleOwner, defaultObserver)
songsForGenre.observe(viewLifecycleOwner, songsForGenreObserver)
albumList.observe(viewLifecycleOwner, albumListObserver)
albumListView!!.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE)
albumListView!!.setOnItemClickListener(
OnItemClickListener
{ parent, theView, position, _ ->
if (position >= 0) {
val entry = parent.getItemAtPosition(position) as MusicDirectory.Entry?
if (entry != null && entry.isDirectory) {
val bundle = Bundle()
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, entry.id)
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, entry.isDirectory)
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.title)
bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.parent)
Navigation.findNavController(theView).navigate(
R.id.selectAlbumFragment,
bundle
)
} else if (entry != null && entry.isVideo) {
2021-03-27 20:14:56 +01:00
videoPlayer.playVideo(requireContext(), entry)
} else {
enableButtons()
}
}
}
)
// TODO Long click on an item will first try to maximize / collapse the item, even when it
// fits inside the TextView. The context menu is only displayed on the second long click...
// This may be improved somehow, e.g. checking first if the texts fit
albumListView!!.setOnItemLongClickListener(
OnItemLongClickListener
{ _, theView, _, _ ->
if (theView is AlbumView) {
val albumView = theView
if (!albumView.isMaximized) {
albumView.maximizeOrMinimize()
return@OnItemLongClickListener true
} else {
return@OnItemLongClickListener false
}
}
if (theView is SongView) {
theView.maximizeOrMinimize()
return@OnItemLongClickListener true
}
false
}
)
selectButton = view.findViewById(R.id.select_album_select)
playNowButton = view.findViewById(R.id.select_album_play_now)
playNextButton = view.findViewById(R.id.select_album_play_next)
playLastButton = view.findViewById(R.id.select_album_play_last)
pinButton = view.findViewById(R.id.select_album_pin)
unpinButton = view.findViewById(R.id.select_album_unpin)
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 = TextView(requireContext())
selectButton!!.setOnClickListener(
View.OnClickListener
{
selectAllOrNone()
}
)
playNowButton!!.setOnClickListener(
View.OnClickListener
{
playNow(false)
}
)
playNextButton!!.setOnClickListener(
View.OnClickListener
{
downloadHandler.download(
this@SelectAlbumFragment, true,
false, false, true, false,
getSelectedSongs(albumListView)
)
selectAll(false, false)
}
)
playLastButton!!.setOnClickListener(
View.OnClickListener
{
playNow(true)
}
)
pinButton!!.setOnClickListener(
View.OnClickListener
{
downloadBackground(true)
selectAll(false, false)
}
)
unpinButton!!.setOnClickListener(
View.OnClickListener
{
unpin()
selectAll(false, false)
}
)
downloadButton!!.setOnClickListener(
View.OnClickListener
{
downloadBackground(false)
selectAll(false, false)
}
)
deleteButton!!.setOnClickListener(
View.OnClickListener
{
delete()
selectAll(false, false)
}
)
registerForContextMenu(albumListView!!)
setHasOptionsMenu(true)
enableButtons()
updateDisplay(false)
}
private fun updateDisplay(refresh: Boolean) {
val id = requireArguments().getString(Constants.INTENT_EXTRA_NAME_ID)
val isAlbum = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, false)
val name = requireArguments().getString(Constants.INTENT_EXTRA_NAME_NAME)
val parentId = requireArguments().getString(Constants.INTENT_EXTRA_NAME_PARENT_ID)
val playlistId = requireArguments().getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID)
val podcastChannelId = requireArguments().getString(
Constants.INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID
)
val playlistName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME)
val shareId = requireArguments().getString(Constants.INTENT_EXTRA_NAME_SHARE_ID)
val shareName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_SHARE_NAME)
val albumListType = requireArguments().getString(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE
)
val genreName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_GENRE_NAME)
val albumListTitle = requireArguments().getInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, 0
)
val getStarredTracks = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_STARRED, 0)
val getVideos = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_VIDEOS, 0)
val getRandomTracks = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_RANDOM, 0)
val albumListSize = requireArguments().getInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0
)
val albumListOffset = requireArguments().getInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0
)
2021-04-21 21:55:55 +02:00
triggerLoad(refresh, id, name, playlistId, playlistName, podcastChannelId, shareId, shareName, albumListType, albumListTitle, albumListSize, albumListOffset, genreName, getStarredTracks, getVideos, getRandomTracks, isAlbum, parentId)
}
2021-04-21 21:55:55 +02:00
private fun triggerLoad(
refresh: Boolean,
id: String?,
name: String?,
playlistId: String?,
playlistName: String?,
podcastChannelId: String?,
shareId: String?,
shareName: String?,
albumListType: String?,
albumListTitle: Int,
albumListSize: Int,
albumListOffset: Int,
genreName: String?,
getStarredTracks: Int,
getVideos: Int,
getRandomTracks: Int,
isAlbum: Boolean,
parentId: String?
) {
serverSettingsModel.viewModelScope.launch {
refreshAlbumListView!!.isRefreshing = true
2021-04-21 21:55:55 +02:00
this@SelectAlbumFragment.getMusicFolders(refresh)
if (playlistId != null) {
this@SelectAlbumFragment.getPlaylist(playlistId, playlistName)
} else if (podcastChannelId != null) {
this@SelectAlbumFragment.getPodcastEpisodes(podcastChannelId)
} else if (shareId != null) {
this@SelectAlbumFragment.getShare(shareId, shareName)
} else if (albumListType != null) {
this@SelectAlbumFragment.getAlbumList(albumListType, albumListTitle, albumListSize, albumListOffset)
} else if (genreName != null) {
this@SelectAlbumFragment.getSongsForGenre(genreName, albumListSize, albumListOffset)
} else if (getStarredTracks != 0) {
this@SelectAlbumFragment.getStarred()
} else if (getVideos != 0) {
this@SelectAlbumFragment.getVideos(refresh)
} else if (getRandomTracks != 0) {
this@SelectAlbumFragment.getRandom(albumListSize)
} else {
if (!isOffline(activity) && Util.getShouldUseId3Tags(activity)) {
if (isAlbum) {
this@SelectAlbumFragment.getAlbum(refresh, id, name, parentId)
} else {
this@SelectAlbumFragment.getArtist(refresh, id, name)
}
2021-04-21 21:55:55 +02:00
} else {
this@SelectAlbumFragment.getMusicDirectory(refresh, id, name, parentId)
}
}
2021-04-21 21:55:55 +02:00
refreshAlbumListView!!.isRefreshing = false
}
}
2021-04-21 21:55:55 +02:00
override fun onCreateContextMenu(menu: ContextMenu, view: View, menuInfo: ContextMenuInfo?) {
super.onCreateContextMenu(menu, view, menuInfo)
val info = menuInfo as AdapterContextMenuInfo?
val entry = albumListView!!.getItemAtPosition(info!!.position) as MusicDirectory.Entry?
if (entry != null && entry.isDirectory) {
val inflater = requireActivity().menuInflater
inflater.inflate(R.menu.select_album_context, menu)
}
shareButton = menu.findItem(R.id.menu_item_share)
if (shareButton != null) {
shareButton!!.isVisible = !isOffline(context)
}
val downloadMenuItem = menu.findItem(R.id.album_menu_download)
if (downloadMenuItem != null) {
downloadMenuItem.isVisible = !isOffline(context)
}
}
override fun onContextItemSelected(menuItem: MenuItem): Boolean {
Timber.d("onContextItemSelected")
val info = menuItem.menuInfo as AdapterContextMenuInfo? ?: return true
val entry = albumListView!!.getItemAtPosition(info.position) as MusicDirectory.Entry?
?: return true
val entryId = entry.id
val itemId = menuItem.itemId
if (itemId == R.id.album_menu_play_now) {
downloadHandler.downloadRecursively(
this, entryId, false, false, true, false, false, false, false, false
)
} else if (itemId == R.id.album_menu_play_next) {
downloadHandler.downloadRecursively(
this, entryId, false, false, false, false, false, true, false, false
)
} else if (itemId == R.id.album_menu_play_last) {
downloadHandler.downloadRecursively(
this, entryId, false, true, false, false, false, false, false, false
)
} else if (itemId == R.id.album_menu_pin) {
downloadHandler.downloadRecursively(
this, entryId, true, true, false, false, false, false, false, false
)
} else if (itemId == R.id.album_menu_unpin) {
downloadHandler.downloadRecursively(
this, entryId, false, false, false, false, false, false, true, false
)
} else if (itemId == R.id.album_menu_download) {
downloadHandler.downloadRecursively(
this, entryId, false, false, false, false, true, false, false, false
)
} else if (itemId == R.id.select_album_play_all) {
playAll()
} else if (itemId == R.id.menu_item_share) {
val entries: MutableList<MusicDirectory.Entry?> = ArrayList(1)
entries.add(entry)
shareHandler.createShare(
this, entries, refreshAlbumListView,
cancellationToken!!
)
return true
} else {
return super.onContextItemSelected(menuItem)
}
return true
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
playAllButton = menu.findItem(R.id.select_album_play_all)
if (playAllButton != null) {
playAllButton!!.isVisible = playAllButtonVisible
}
shareButton = menu.findItem(R.id.menu_item_share)
if (shareButton != null) {
shareButton!!.isVisible = shareButtonVisible
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.select_album, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val itemId = item.itemId
if (itemId == R.id.select_album_play_all) {
playAll()
return true
} else if (itemId == R.id.menu_item_share) {
shareHandler.createShare(
this, getSelectedSongs(albumListView),
refreshAlbumListView, cancellationToken!!
)
return true
}
return false
}
override fun onDestroyView() {
cancellationToken!!.cancel()
super.onDestroyView()
}
private fun playNow(append: Boolean) {
val selectedSongs = getSelectedSongs(albumListView)
if (!selectedSongs.isEmpty()) {
downloadHandler.download(
this, append, false, !append, false,
false, selectedSongs
)
selectAll(false, false)
} else {
playAll(false, append)
}
}
private fun playAll(shuffle: Boolean = false, append: Boolean = false) {
var hasSubFolders = false
for (i in 0 until albumListView!!.count) {
val entry = albumListView!!.getItemAtPosition(i) as MusicDirectory.Entry?
if (entry != null && entry.isDirectory) {
hasSubFolders = true
break
}
}
val isArtist = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, false)
val id = requireArguments().getString(Constants.INTENT_EXTRA_NAME_ID)
if (hasSubFolders && id != null) {
downloadHandler.downloadRecursively(
this, id, false, append, !append,
shuffle, false, false, false, isArtist
)
} else {
selectAll(true, false)
downloadHandler.download(
this, append, false, !append, false,
shuffle, getSelectedSongs(albumListView)
)
selectAll(false, false)
}
}
2021-04-21 21:55:55 +02:00
private suspend fun getMusicFolders(refresh: Boolean) {
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
val musicService = getMusicService(requireContext())
try {
musicFolders.postValue(musicService.getMusicFolders(refresh, context))
} catch (exception: Exception) {
Handler(Looper.getMainLooper()).post {
CommunicationErrorHandler.handleError(exception, requireContext())
}
}
}
}
}
private suspend fun getMusicDirectory(refresh: Boolean, id: String?, name: String?, parentId: String?) {
setTitle(this, name)
2021-04-21 21:55:55 +02:00
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
val service = getMusicService(requireContext())
fun getSongsRecursively(
parent: MusicDirectory,
songs: MutableList<MusicDirectory.Entry>
) {
for (song in parent.getChildren(false, true)) {
if (!song.isVideo && !song.isDirectory) {
songs.add(song)
}
}
for ((id1, _, _, title) in parent.getChildren(true, false)) {
var root: MusicDirectory
if (allSongsId != id1) {
root = service.getMusicDirectory(id1, title, false, context)
getSongsRecursively(root, songs)
}
}
}
var root = MusicDirectory()
if (allSongsId == id) {
val musicDirectory = service.getMusicDirectory(parentId, name, refresh, context)
val songs: MutableList<MusicDirectory.Entry> = LinkedList()
getSongsRecursively(musicDirectory, songs)
for (song in songs) {
if (!song.isDirectory) {
root.addChild(song)
}
}
} else {
val musicDirectory = service.getMusicDirectory(id, name, refresh, context)
if (Util.getShouldShowAllSongsByArtist(context) &&
musicDirectory.findChild(allSongsId) == null &&
musicDirectory.getChildren(true, false).size ==
musicDirectory.getChildren(true, true).size
) {
val allSongs = MusicDirectory.Entry()
allSongs.isDirectory = true
allSongs.artist = name
allSongs.parent = id
allSongs.id = allSongsId
allSongs.title = String.format(
resources.getString(R.string.select_album_all_songs), name
)
root.addChild(allSongs)
root.addAll(musicDirectory.getChildren())
} else {
root = musicDirectory
}
}
2021-04-21 21:55:55 +02:00
currentDirectory.postValue(root)
}
2021-04-21 21:55:55 +02:00
}
}
2021-04-21 21:55:55 +02:00
private suspend fun getArtist(refresh: Boolean, id: String?, name: String?) {
setTitle(this, name)
2021-04-21 21:55:55 +02:00
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
val service = getMusicService(requireContext())
var root = MusicDirectory()
val musicDirectory = service.getArtist(id, name, refresh, context)
if (Util.getShouldShowAllSongsByArtist(context) &&
musicDirectory.findChild(allSongsId) == null &&
musicDirectory.getChildren(true, false).size ==
musicDirectory.getChildren(true, true).size
) {
val allSongs = MusicDirectory.Entry()
allSongs.isDirectory = true
allSongs.artist = name
allSongs.parent = id
allSongs.id = allSongsId
allSongs.title = String.format(
resources.getString(R.string.select_album_all_songs), name
)
root.addFirst(allSongs)
root.addAll(musicDirectory.getChildren())
} else {
root = musicDirectory
}
2021-04-21 21:55:55 +02:00
currentDirectory.postValue(root)
}
2021-04-21 21:55:55 +02:00
}
}
2021-04-21 21:55:55 +02:00
private suspend fun getAlbum(refresh: Boolean, id: String?, name: String?, parentId: String?) {
setTitle(this, name)
2021-04-21 21:55:55 +02:00
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
val service = getMusicService(requireContext())
val musicDirectory: MusicDirectory
musicDirectory = if (allSongsId == id) {
val root = MusicDirectory()
val songs: MutableCollection<MusicDirectory.Entry> = LinkedList()
2021-04-21 21:55:55 +02:00
val artist = service.getArtist(parentId, "", false, context)
for ((id1) in artist.getChildren()) {
if (allSongsId != id1) {
val albumDirectory = service.getAlbum(id1, "", false, context)
for (song in albumDirectory.getChildren()) {
if (!song.isVideo) {
songs.add(song)
}
}
}
}
for (song in songs) {
if (!song.isDirectory) {
root.addChild(song)
}
}
root
} else {
service.getAlbum(id, name, refresh, context)
}
2021-04-21 21:55:55 +02:00
currentDirectory.postValue(musicDirectory);
}
2021-04-21 21:55:55 +02:00
}
}
2021-04-21 21:55:55 +02:00
private suspend fun getSongsForGenre(genre: String, count: Int, offset: Int) {
setTitle(this, genre)
2021-04-21 21:55:55 +02:00
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
val service = getMusicService(requireContext())
val musicDirectory: MusicDirectory
musicDirectory = service.getSongsByGenre(genre, count, offset, context)
songsForGenre.postValue(musicDirectory)
}
2021-04-21 21:55:55 +02:00
}
2021-04-21 21:55:55 +02:00
}
2021-04-21 21:55:55 +02:00
private suspend fun getStarred() {
setTitle(this, R.string.main_songs_starred)
2021-04-21 21:55:55 +02:00
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
2021-04-21 21:55:55 +02:00
val service = getMusicService(requireContext())
val musicDirectory: MusicDirectory
val context = requireContext()
if (Util.getShouldUseId3Tags(context)) {
musicDirectory = Util.getSongsFromSearchResult(service.getStarred2(context))
2021-04-21 21:55:55 +02:00
} else {
musicDirectory = Util.getSongsFromSearchResult(service.getStarred(context))
}
2021-04-21 21:55:55 +02:00
currentDirectory.postValue(musicDirectory)
}
}
2021-04-21 21:55:55 +02:00
}
2021-04-21 21:55:55 +02:00
private suspend fun getVideos(refresh: Boolean) {
showHeader = false
setTitle(this, R.string.main_videos)
2021-04-21 21:55:55 +02:00
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
val service = getMusicService(requireContext())
currentDirectory.postValue(service.getVideos(refresh, context))
}
2021-04-21 21:55:55 +02:00
}
}
2021-04-21 21:55:55 +02:00
private suspend fun getRandom(size: Int) {
setTitle(this, R.string.main_songs_random)
2021-04-21 21:55:55 +02:00
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
val service = getMusicService(requireContext())
val musicDirectory = service.getRandomSongs(size, context)
2021-04-21 21:55:55 +02:00
currentDirectoryIsSortable = false
currentDirectory.postValue(musicDirectory)
}
2021-04-21 21:55:55 +02:00
}
}
2021-04-21 21:55:55 +02:00
private suspend fun getPlaylist(playlistId: String, playlistName: String?) {
setTitle(this, playlistName)
2021-04-21 21:55:55 +02:00
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
val service = getMusicService(requireContext())
val musicDirectory: MusicDirectory
musicDirectory = service.getPlaylist(playlistId, playlistName, context)
currentDirectory.postValue(musicDirectory)
}
2021-04-21 21:55:55 +02:00
}
}
2021-04-21 21:55:55 +02:00
private suspend fun getPodcastEpisodes(podcastChannelId: String) {
setTitle(this, R.string.podcasts_label)
2021-04-21 21:55:55 +02:00
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
val service = getMusicService(requireContext())
val musicDirectory: MusicDirectory
musicDirectory = service.getPodcastEpisodes(podcastChannelId, context)
currentDirectory.postValue(musicDirectory)
}
2021-04-21 21:55:55 +02:00
}
}
2021-04-21 21:55:55 +02:00
private suspend fun getShare(shareId: String, shareName: CharSequence?) {
setTitle(this, shareName)
// setActionBarSubtitle(shareName);
2021-04-21 21:55:55 +02:00
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
val service = getMusicService(requireContext())
val musicDirectory = MusicDirectory()
val shares = service.getShares(true, context)
for (share in shares) {
if (share.id == shareId) {
for (entry in share.getEntries()) {
2021-04-21 21:55:55 +02:00
musicDirectory.addChild(entry)
}
break
}
}
2021-04-21 21:55:55 +02:00
currentDirectory.postValue(musicDirectory)
}
2021-04-21 21:55:55 +02:00
}
}
2021-04-21 21:55:55 +02:00
private suspend 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);
2021-04-21 21:55:55 +02:00
fun sortableCollection(): Boolean {
return albumListType != "newest" && albumListType != "random" &&
albumListType != "highest" && albumListType != "recent" &&
albumListType != "frequent"
2021-04-21 21:55:55 +02:00
}
2021-04-21 21:55:55 +02:00
withContext(Dispatchers.IO) {
if (!isOffline(context)) {
val service = getMusicService(requireContext())
val musicDirectory: MusicDirectory
val musicFolderId = if (showSelectFolderHeader) {
this@SelectAlbumFragment.activeServerProvider.getActiveServer().musicFolderId
} else {
null
}
2021-04-21 21:55:55 +02:00
if (Util.getShouldUseId3Tags(context)) {
musicDirectory = service.getAlbumList2(albumListType, size, offset, musicFolderId, context)
} else {
2021-04-21 21:55:55 +02:00
musicDirectory = service.getAlbumList(albumListType, size, offset, musicFolderId, context)
}
2021-04-21 21:55:55 +02:00
currentDirectoryIsSortable = sortableCollection()
albumList.postValue(musicDirectory)
}
2021-04-21 21:55:55 +02:00
}
}
private fun selectAllOrNone() {
var someUnselected = false
val count = albumListView!!.count
for (i in 0 until count) {
if (!albumListView!!.isItemChecked(i) &&
albumListView!!.getItemAtPosition(i) is MusicDirectory.Entry
) {
someUnselected = true
break
}
}
selectAll(someUnselected, true)
}
private fun selectAll(selected: Boolean, toast: Boolean) {
val count = albumListView!!.count
var selectedCount = 0
for (i in 0 until count) {
val entry = albumListView!!.getItemAtPosition(i) as MusicDirectory.Entry?
if (entry != null && !entry.isDirectory && !entry.isVideo) {
albumListView!!.setItemChecked(i, selected)
selectedCount++
}
}
// Display toast: N tracks selected / N tracks unselected
if (toast) {
val toastResId = if (selected)
R.string.select_album_n_selected
else
R.string.select_album_n_unselected
Util.toast(activity, getString(toastResId, selectedCount))
}
enableButtons()
}
private fun enableButtons() {
val selection = getSelectedSongs(albumListView)
val enabled = !selection.isEmpty()
var unpinEnabled = false
var deleteEnabled = false
var pinnedCount = 0
for (song in selection) {
val downloadFile = mediaPlayerController.getDownloadFileForSong(song)
if (downloadFile.isWorkDone) {
deleteEnabled = true
}
if (downloadFile.isSaved) {
pinnedCount++
unpinEnabled = true
}
}
playNowButton!!.visibility = if (enabled) View.VISIBLE else View.GONE
playNextButton!!.visibility = if (enabled) View.VISIBLE else View.GONE
playLastButton!!.visibility = if (enabled) View.VISIBLE else View.GONE
pinButton!!.visibility = if (enabled && !isOffline(context) && selection.size > pinnedCount)
View.VISIBLE
else
View.GONE
unpinButton!!.visibility = if (enabled && unpinEnabled) View.VISIBLE else View.GONE
downloadButton!!.visibility = if (enabled && !deleteEnabled && !isOffline(context))
View.VISIBLE
else
View.GONE
deleteButton!!.visibility = if (enabled && deleteEnabled) View.VISIBLE else View.GONE
}
private fun downloadBackground(save: Boolean) {
var songs = getSelectedSongs(albumListView)
if (songs.isEmpty()) {
selectAll(true, false)
songs = getSelectedSongs(albumListView)
}
downloadBackground(save, songs)
}
private fun downloadBackground(save: Boolean, songs: List<MusicDirectory.Entry?>) {
val onValid = Runnable {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
mediaPlayerController.downloadBackground(songs, save)
if (save) {
Util.toast(
context,
resources.getQuantityString(
R.plurals.select_album_n_songs_pinned, songs.size, songs.size
)
)
} else {
Util.toast(
context,
resources.getQuantityString(
R.plurals.select_album_n_songs_downloaded, songs.size, songs.size
)
)
}
}
onValid.run()
}
private fun delete() {
var songs = getSelectedSongs(albumListView)
if (songs.isEmpty()) {
selectAll(true, false)
songs = getSelectedSongs(albumListView)
}
mediaPlayerController.delete(songs)
}
private fun unpin() {
val songs = getSelectedSongs(albumListView)
Util.toast(
context,
resources.getQuantityString(
R.plurals.select_album_n_songs_unpinned, songs.size, songs.size
)
)
mediaPlayerController.unpin(songs)
}
2021-04-21 21:55:55 +02:00
val albumListObserver = Observer<MusicDirectory> { musicDirectory ->
if (!musicDirectory.getChildren().isEmpty()) {
pinButton!!.visibility = View.GONE
unpinButton!!.visibility = View.GONE
downloadButton!!.visibility = View.GONE
deleteButton!!.visibility = View.GONE
2021-04-21 21:55:55 +02:00
// Hide more button when results are less than album list size
if (musicDirectory.getChildren().size < requireArguments().getInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0
)
) {
moreButton!!.visibility = View.GONE
} else {
moreButton!!.visibility = View.VISIBLE
moreButton!!.setOnClickListener {
val theAlbumListTitle = requireArguments().getInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, 0
)
val type = requireArguments().getString(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE
)
val theSize = requireArguments().getInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0
)
val theOffset = requireArguments().getInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0
) + theSize
val bundle = Bundle()
bundle.putInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, theAlbumListTitle
)
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type)
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, theSize)
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, theOffset)
Navigation.findNavController(requireView()).navigate(
R.id.selectAlbumFragment, bundle
)
}
}
} else {
moreButton!!.visibility = View.GONE
}
2021-04-21 21:55:55 +02:00
updateInterfaceWithEntries(musicDirectory)
}
val musicFolderObserver = Observer<List<MusicFolder>> { changedFolders ->
if (changedFolders != null) {
selectFolderHeader!!.setData(
activeServerProvider.getActiveServer().musicFolderId,
changedFolders
)
}
2021-04-21 21:55:55 +02:00
}
2021-04-21 21:55:55 +02:00
val songsForGenreObserver = Observer<MusicDirectory> { musicDirectory ->
2021-04-21 21:55:55 +02:00
// Hide more button when results are less than album list size
if (musicDirectory.getChildren().size < requireArguments().getInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0
)
) {
moreButton!!.visibility = View.GONE
} else {
moreButton!!.visibility = View.VISIBLE
}
2021-04-21 21:55:55 +02:00
moreButton!!.setOnClickListener {
val theGenre = requireArguments().getString(Constants.INTENT_EXTRA_NAME_GENRE_NAME)
val size = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)
val theOffset = requireArguments().getInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0
) + size
val bundle = Bundle()
bundle.putString(Constants.INTENT_EXTRA_NAME_GENRE_NAME, theGenre)
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, size)
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, theOffset)
Navigation.findNavController(requireView()).navigate(R.id.selectAlbumFragment, bundle)
}
2021-04-21 21:55:55 +02:00
updateInterfaceWithEntries(musicDirectory)
}
// Our old "done" function
val defaultObserver = Observer(this::updateInterfaceWithEntries)
private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) {
val entries = musicDirectory.getChildren()
// FIXME
if (sortableCollection() && Util.getShouldSortByDisc(context)) {
Collections.sort(entries, EntryByDiscAndTrackComparator())
}
var allVideos = true
var songCount = 0
for (entry in entries) {
if (!entry.isVideo) {
allVideos = false
}
if (!entry.isDirectory) {
songCount++
}
2021-04-21 21:55:55 +02:00
}
2021-04-21 21:55:55 +02:00
val listSize = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)
2021-04-21 21:55:55 +02:00
if (songCount > 0) {
if (showHeader) {
val intentAlbumName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_NAME)
val directoryName = musicDirectory.name
val header = createHeader(
entries, intentAlbumName ?: directoryName,
songCount
2021-04-21 21:55:55 +02:00
)
if (header != null && albumListView!!.headerViewsCount == 0) {
albumListView!!.addHeaderView(header, null, false)
}
2021-04-21 21:55:55 +02:00
}
2021-04-21 21:55:55 +02:00
pinButton!!.visibility = View.VISIBLE
unpinButton!!.visibility = View.VISIBLE
downloadButton!!.visibility = View.VISIBLE
deleteButton!!.visibility = View.VISIBLE
selectButton!!.visibility = if (allVideos) View.GONE else View.VISIBLE
playNowButton!!.visibility = View.VISIBLE
playNextButton!!.visibility = View.VISIBLE
playLastButton!!.visibility = View.VISIBLE
if (listSize == 0 || songCount < listSize) {
moreButton!!.visibility = View.GONE
} else {
moreButton!!.visibility = View.VISIBLE
if (requireArguments().getInt(Constants.INTENT_EXTRA_NAME_RANDOM, 0) > 0) {
moreButton!!.setOnClickListener {
val offset = requireArguments().getInt(
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0
2021-04-21 21:55:55 +02:00
) + listSize
val bundle = Bundle()
bundle.putInt(Constants.INTENT_EXTRA_NAME_RANDOM, 1)
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, listSize)
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, offset)
Navigation.findNavController(requireView()).navigate(
R.id.selectAlbumFragment, bundle
2021-04-21 21:55:55 +02:00
)
}
}
2021-04-21 21:55:55 +02:00
}
} else {
if (showSelectFolderHeader) {
if (albumListView!!.headerViewsCount == 0) {
albumListView!!.addHeaderView(selectFolderHeader!!.itemView, null, false)
}
2021-04-21 21:55:55 +02:00
}
2021-04-21 21:55:55 +02:00
pinButton!!.visibility = View.GONE
unpinButton!!.visibility = View.GONE
downloadButton!!.visibility = View.GONE
deleteButton!!.visibility = View.GONE
selectButton!!.visibility = View.GONE
playNowButton!!.visibility = View.GONE
playNextButton!!.visibility = View.GONE
playLastButton!!.visibility = View.GONE
if (listSize == 0 || musicDirectory.getChildren().size < listSize) {
albumButtons!!.visibility = View.GONE
} else {
moreButton!!.visibility = View.VISIBLE
}
2021-04-21 21:55:55 +02:00
}
2021-04-21 21:55:55 +02:00
enableButtons()
2021-04-21 21:55:55 +02:00
val isAlbumList = requireArguments().containsKey(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE)
playAllButtonVisible = !(isAlbumList || entries.isEmpty()) && !allVideos
shareButtonVisible = !isOffline(context) && songCount > 0
2021-04-21 21:55:55 +02:00
albumListView!!.removeHeaderView(emptyView!!)
if (entries.isEmpty()) {
emptyView!!.text = "No Media Found"
emptyView!!.setPadding(10, 10, 10, 10)
albumListView!!.addHeaderView(emptyView, null, false)
}
2021-04-21 21:55:55 +02:00
if (playAllButton != null) {
playAllButton!!.isVisible = playAllButtonVisible
}
2021-04-21 21:55:55 +02:00
if (shareButton != null) {
shareButton!!.isVisible = shareButtonVisible
}
2021-04-21 21:55:55 +02:00
albumListView!!.adapter = EntryAdapter(
context,
imageLoaderProvider.getImageLoader(), entries, true
2021-04-21 21:55:55 +02:00
)
2021-04-21 21:55:55 +02:00
val playAll = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false)
if (playAll && songCount > 0) {
playAll(
requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false),
false
2021-04-21 21:55:55 +02:00
)
}
2021-04-21 21:55:55 +02:00
currentDirectoryIsSortable = true
}
protected fun createHeader(
entries: List<MusicDirectory.Entry>,
name: CharSequence?,
songCount: Int
2021-04-21 21:55:55 +02:00
): View? {
val coverArtView = header!!.findViewById<View>(R.id.select_album_art) as ImageView
val artworkSelection = random.nextInt(entries.size)
imageLoaderProvider.getImageLoader().loadImage(
coverArtView, entries[artworkSelection], false,
Util.getAlbumImageSize(context), false, true
2021-04-21 21:55:55 +02:00
)
2021-04-21 21:55:55 +02:00
val albumHeader = AlbumHeader.processEntries(context, entries)
2021-04-21 21:55:55 +02:00
val titleView = header!!.findViewById<View>(R.id.select_album_title) as TextView
titleView.text = name ?: getTitle(this@SelectAlbumFragment) // getActionBarSubtitle());
2021-04-21 21:55:55 +02:00
// Don't show a header if all entries are videos
if (albumHeader.isAllVideo) {
return null
}
2021-04-21 21:55:55 +02:00
val artistView = header!!.findViewById<TextView>(R.id.select_album_artist)
val artist: String
2021-04-21 21:55:55 +02:00
artist = if (albumHeader.artists.size == 1)
albumHeader.artists.iterator().next()
else if (albumHeader.grandParents.size == 1)
albumHeader.grandParents.iterator().next()
else
resources.getString(R.string.common_various_artists)
2021-04-21 21:55:55 +02:00
artistView.text = artist
2021-04-21 21:55:55 +02:00
val genreView = header!!.findViewById<TextView>(R.id.select_album_genre)
val genre: String
2021-04-21 21:55:55 +02:00
genre = if (albumHeader.genres.size == 1)
albumHeader.genres.iterator().next()
else
resources.getString(R.string.common_multiple_genres)
2021-04-21 21:55:55 +02:00
genreView.text = genre
2021-04-21 21:55:55 +02:00
val yearView = header!!.findViewById<TextView>(R.id.select_album_year)
val year: String
2021-04-21 21:55:55 +02:00
year = if (albumHeader.years.size == 1)
albumHeader.years.iterator().next().toString()
else
resources.getString(R.string.common_multiple_years)
2021-04-21 21:55:55 +02:00
yearView.text = year
2021-04-21 21:55:55 +02:00
val songCountView = header!!.findViewById<TextView>(R.id.select_album_song_count)
val songs = resources.getQuantityString(
R.plurals.select_album_n_songs, songCount,
songCount
2021-04-21 21:55:55 +02:00
)
songCountView.text = songs
2021-04-21 21:55:55 +02:00
val duration = Util.formatTotalDuration(albumHeader.totalDuration)
2021-04-21 21:55:55 +02:00
val durationView = header!!.findViewById<TextView>(R.id.select_album_duration)
durationView.text = duration
2021-04-21 21:55:55 +02:00
return header
}
private fun sortableCollection(): Boolean {
return currentDirectoryIsSortable
}
2021-04-21 21:55:55 +02:00
private fun getSelectedSongs(albumListView: ListView?): List<MusicDirectory.Entry?> {
val songs: MutableList<MusicDirectory.Entry?> = ArrayList(10)
if (albumListView != null) {
val count = albumListView.count
for (i in 0 until count) {
if (albumListView.isItemChecked(i)) {
songs.add(albumListView.getItemAtPosition(i) as MusicDirectory.Entry?)
}
}
}
return songs
}
}