2021-04-21 22:31:56 +02:00
|
|
|
/*
|
2021-05-12 13:28:33 +02:00
|
|
|
* TrackCollectionFragment.kt
|
2021-04-21 22:31:56 +02:00
|
|
|
* Copyright (C) 2009-2021 Ultrasonic developers
|
|
|
|
*
|
|
|
|
* Distributed under terms of the GNU GPLv3 license.
|
|
|
|
*/
|
|
|
|
|
2021-03-19 04:23:00 +01:00
|
|
|
package org.moire.ultrasonic.fragment
|
|
|
|
|
|
|
|
import android.os.Bundle
|
2021-05-12 13:28:33 +02:00
|
|
|
import android.os.Handler
|
|
|
|
import android.os.Looper
|
2021-03-19 04:23:00 +01:00
|
|
|
import android.view.Menu
|
|
|
|
import android.view.MenuInflater
|
|
|
|
import android.view.MenuItem
|
|
|
|
import android.view.View
|
|
|
|
import android.widget.ImageView
|
2021-10-16 11:30:51 +02:00
|
|
|
import androidx.core.view.isVisible
|
2021-04-21 22:31:56 +02:00
|
|
|
import androidx.fragment.app.viewModels
|
2021-10-16 11:30:51 +02:00
|
|
|
import androidx.lifecycle.LiveData
|
2021-04-10 04:41:38 +02:00
|
|
|
import androidx.lifecycle.viewModelScope
|
2021-03-19 04:23:00 +01:00
|
|
|
import androidx.navigation.Navigation
|
2021-10-16 11:30:51 +02:00
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
|
|
|
import androidx.recyclerview.widget.RecyclerView
|
2021-11-23 21:58:58 +01:00
|
|
|
import java.util.Collections
|
2021-05-12 13:28:33 +02:00
|
|
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
2021-04-10 04:41:38 +02:00
|
|
|
import kotlinx.coroutines.launch
|
2021-03-19 04:23:00 +01:00
|
|
|
import org.koin.android.ext.android.inject
|
|
|
|
import org.moire.ultrasonic.R
|
2021-11-30 00:46:48 +01:00
|
|
|
import org.moire.ultrasonic.adapters.AlbumHeader
|
|
|
|
import org.moire.ultrasonic.adapters.AlbumRowBinder
|
2021-10-18 12:57:21 +02:00
|
|
|
import org.moire.ultrasonic.adapters.HeaderViewBinder
|
2021-10-16 11:30:51 +02:00
|
|
|
import org.moire.ultrasonic.adapters.TrackViewBinder
|
2021-03-19 04:23:00 +01:00
|
|
|
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
2021-10-16 11:30:51 +02:00
|
|
|
import org.moire.ultrasonic.domain.Identifiable
|
2021-03-19 04:23:00 +01:00
|
|
|
import org.moire.ultrasonic.domain.MusicDirectory
|
2022-03-27 14:57:07 +02:00
|
|
|
import org.moire.ultrasonic.domain.Track
|
2021-03-19 04:23:00 +01:00
|
|
|
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
|
2021-11-23 20:38:26 +01:00
|
|
|
import org.moire.ultrasonic.model.TrackCollectionModel
|
2021-03-19 04:23:00 +01:00
|
|
|
import org.moire.ultrasonic.service.MediaPlayerController
|
|
|
|
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
|
|
|
|
import org.moire.ultrasonic.subsonic.ShareHandler
|
2021-11-23 20:38:26 +01:00
|
|
|
import org.moire.ultrasonic.subsonic.VideoPlayer
|
2021-03-19 04:23:00 +01:00
|
|
|
import org.moire.ultrasonic.util.CancellationToken
|
2021-11-13 13:43:41 +01:00
|
|
|
import org.moire.ultrasonic.util.CommunicationError
|
2021-03-19 04:23:00 +01:00
|
|
|
import org.moire.ultrasonic.util.Constants
|
|
|
|
import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator
|
2021-09-24 18:20:53 +02:00
|
|
|
import org.moire.ultrasonic.util.Settings
|
2021-03-19 04:23:00 +01:00
|
|
|
import org.moire.ultrasonic.util.Util
|
2022-06-19 12:59:26 +02:00
|
|
|
import timber.log.Timber
|
2021-03-19 04:23:00 +01:00
|
|
|
|
|
|
|
/**
|
2021-05-12 13:28:33 +02:00
|
|
|
* Displays a group of tracks, eg. the songs of an album, of a playlist etc.
|
2021-11-30 00:46:48 +01:00
|
|
|
*
|
|
|
|
* In most cases the data should be just a list of Entries, but there are some cases
|
|
|
|
* where the list can contain Albums as well. This happens especially when having ID3 tags disabled,
|
|
|
|
* or using Offline mode, both in which Indexes instead of Artists are being used.
|
2021-12-05 21:07:08 +01:00
|
|
|
*
|
|
|
|
* TODO: Remove more button and introduce endless scrolling
|
2021-03-19 04:23:00 +01:00
|
|
|
*/
|
2021-11-28 18:26:44 +01:00
|
|
|
@Suppress("TooManyFunctions")
|
2021-11-30 00:46:48 +01:00
|
|
|
open class TrackCollectionFragment : MultiListFragment<MusicDirectory.Child>() {
|
2021-03-19 04:23:00 +01:00
|
|
|
|
|
|
|
private var albumButtons: View? = null
|
2022-06-19 12:59:26 +02:00
|
|
|
private var selectButton: ImageView? = null
|
2021-11-23 20:38:26 +01:00
|
|
|
internal var playNowButton: ImageView? = null
|
2021-11-28 18:26:44 +01:00
|
|
|
private var playNextButton: ImageView? = null
|
|
|
|
private var playLastButton: ImageView? = null
|
2022-06-19 12:59:26 +02:00
|
|
|
private var pinButton: ImageView? = null
|
2021-11-28 18:26:44 +01:00
|
|
|
private var unpinButton: ImageView? = null
|
|
|
|
private var downloadButton: ImageView? = null
|
|
|
|
private var deleteButton: ImageView? = null
|
|
|
|
private var moreButton: ImageView? = null
|
2021-03-19 04:23:00 +01:00
|
|
|
private var playAllButtonVisible = false
|
|
|
|
private var shareButtonVisible = false
|
|
|
|
private var playAllButton: MenuItem? = null
|
|
|
|
private var shareButton: MenuItem? = null
|
|
|
|
|
2021-11-23 20:38:26 +01:00
|
|
|
internal val mediaPlayerController: MediaPlayerController by inject()
|
2021-03-19 04:23:00 +01:00
|
|
|
private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
|
|
|
|
private val shareHandler: ShareHandler by inject()
|
2021-11-23 20:38:26 +01:00
|
|
|
internal var cancellationToken: CancellationToken? = null
|
2021-03-19 04:23:00 +01:00
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
override val listModel: TrackCollectionModel by viewModels()
|
2021-04-21 22:31:56 +02:00
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
/**
|
|
|
|
* The id of the main layout
|
|
|
|
*/
|
2021-11-27 00:51:41 +01:00
|
|
|
override val mainLayout: Int = R.layout.list_layout_track
|
2021-10-16 11:30:51 +02:00
|
|
|
|
2021-03-19 04:23:00 +01:00
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
super.onViewCreated(view, savedInstanceState)
|
|
|
|
cancellationToken = CancellationToken()
|
|
|
|
|
|
|
|
albumButtons = view.findViewById(R.id.menu_album)
|
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
// Setup refresh handler
|
|
|
|
refreshListView = view.findViewById(refreshListId)
|
|
|
|
refreshListView?.setOnRefreshListener {
|
2021-11-30 20:53:10 +01:00
|
|
|
getLiveData(arguments, true)
|
2021-04-21 22:36:19 +02:00
|
|
|
}
|
2021-03-19 04:23:00 +01:00
|
|
|
|
2021-11-23 20:38:26 +01:00
|
|
|
setupButtons(view)
|
2021-03-19 04:23:00 +01:00
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
registerForContextMenu(listView!!)
|
2021-03-19 04:23:00 +01:00
|
|
|
setHasOptionsMenu(true)
|
2021-05-12 13:28:33 +02:00
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
// Create a View Manager
|
|
|
|
viewManager = LinearLayoutManager(this.context)
|
2021-04-21 21:55:55 +02:00
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
// Hook up the view with the manager and the adapter
|
|
|
|
listView = view.findViewById<RecyclerView>(recyclerViewId).apply {
|
|
|
|
setHasFixedSize(true)
|
|
|
|
layoutManager = viewManager
|
|
|
|
adapter = viewAdapter
|
2021-04-21 22:55:58 +02:00
|
|
|
}
|
2021-03-19 04:23:00 +01:00
|
|
|
|
2021-10-18 12:57:21 +02:00
|
|
|
viewAdapter.register(
|
|
|
|
HeaderViewBinder(
|
|
|
|
context = requireContext()
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
viewAdapter.register(
|
|
|
|
TrackViewBinder(
|
2022-04-03 23:57:50 +02:00
|
|
|
onItemClick = { file, _ -> onItemClick(file.track) },
|
2022-03-27 11:35:14 +02:00
|
|
|
onContextMenuClick = { menu, id -> onContextMenuItemSelected(menu, id.track) },
|
2021-10-16 11:30:51 +02:00
|
|
|
checkable = true,
|
|
|
|
draggable = false,
|
2021-11-14 21:20:23 +01:00
|
|
|
context = requireContext(),
|
|
|
|
lifecycleOwner = viewLifecycleOwner
|
2021-10-16 11:30:51 +02:00
|
|
|
)
|
|
|
|
)
|
2021-04-21 21:55:55 +02:00
|
|
|
|
2021-11-30 00:46:48 +01:00
|
|
|
viewAdapter.register(
|
|
|
|
AlbumRowBinder(
|
|
|
|
{ entry -> onItemClick(entry) },
|
|
|
|
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) },
|
|
|
|
imageLoaderProvider.getImageLoader(),
|
|
|
|
context = requireContext()
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
enableButtons()
|
2021-04-21 21:55:55 +02:00
|
|
|
|
2021-11-14 21:20:23 +01:00
|
|
|
// Update the buttons when the selection has changed
|
2021-11-15 20:01:04 +01:00
|
|
|
viewAdapter.selectionRevision.observe(
|
2022-06-19 12:59:26 +02:00
|
|
|
viewLifecycleOwner
|
|
|
|
) {
|
|
|
|
enableButtons()
|
|
|
|
}
|
2021-11-23 20:38:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
internal open fun setupButtons(view: View) {
|
|
|
|
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)
|
|
|
|
|
|
|
|
selectButton?.setOnClickListener {
|
|
|
|
selectAllOrNone()
|
|
|
|
}
|
|
|
|
|
|
|
|
playNowButton?.setOnClickListener {
|
|
|
|
playNow(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
playNextButton?.setOnClickListener {
|
|
|
|
downloadHandler.download(
|
|
|
|
this@TrackCollectionFragment, append = true,
|
|
|
|
save = false, autoPlay = false, playNext = true, shuffle = false,
|
|
|
|
songs = getSelectedSongs()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
playLastButton!!.setOnClickListener {
|
|
|
|
playNow(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
pinButton?.setOnClickListener {
|
|
|
|
downloadBackground(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
unpinButton?.setOnClickListener {
|
|
|
|
unpin()
|
|
|
|
}
|
|
|
|
|
|
|
|
downloadButton?.setOnClickListener {
|
|
|
|
downloadBackground(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
deleteButton?.setOnClickListener {
|
|
|
|
delete()
|
|
|
|
}
|
2021-04-10 04:41:38 +02:00
|
|
|
}
|
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
val handler = CoroutineExceptionHandler { _, exception ->
|
|
|
|
Handler(Looper.getMainLooper()).post {
|
|
|
|
CommunicationError.handleError(exception, context)
|
2021-03-19 04:23:00 +01:00
|
|
|
}
|
2021-11-23 20:38:26 +01:00
|
|
|
refreshListView?.isRefreshing = false
|
2021-10-16 11:30:51 +02:00
|
|
|
}
|
2021-03-19 04:23:00 +01:00
|
|
|
|
|
|
|
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(
|
2021-10-16 11:30:51 +02:00
|
|
|
this, getSelectedSongs(),
|
|
|
|
refreshListView, cancellationToken!!
|
2021-03-19 04:23:00 +01:00
|
|
|
)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDestroyView() {
|
|
|
|
cancellationToken!!.cancel()
|
|
|
|
super.onDestroyView()
|
|
|
|
}
|
|
|
|
|
2022-03-11 19:35:18 +01:00
|
|
|
private fun playNow(
|
|
|
|
append: Boolean,
|
2022-03-27 14:57:07 +02:00
|
|
|
selectedSongs: List<Track> = getSelectedSongs()
|
2022-03-11 19:35:18 +01:00
|
|
|
) {
|
2021-04-21 22:36:19 +02:00
|
|
|
if (selectedSongs.isNotEmpty()) {
|
2021-03-19 04:23:00 +01:00
|
|
|
downloadHandler.download(
|
2021-04-21 22:42:52 +02:00
|
|
|
this, append, false, !append, playNext = false,
|
2021-04-21 22:55:58 +02:00
|
|
|
shuffle = false, songs = selectedSongs
|
2021-03-19 04:23:00 +01:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
playAll(false, append)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-14 21:20:23 +01:00
|
|
|
/**
|
|
|
|
* Get the size of the underlying list
|
|
|
|
*/
|
2021-10-18 12:57:21 +02:00
|
|
|
private val childCount: Int
|
|
|
|
get() {
|
2021-11-14 21:20:23 +01:00
|
|
|
val count = viewAdapter.getCurrentList().count()
|
2022-06-19 12:59:26 +02:00
|
|
|
return if (listModel.showHeader) {
|
|
|
|
count - 1
|
2021-10-18 12:57:21 +02:00
|
|
|
} else {
|
2022-06-19 12:59:26 +02:00
|
|
|
count
|
2021-10-18 12:57:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-19 04:23:00 +01:00
|
|
|
private fun playAll(shuffle: Boolean = false, append: Boolean = false) {
|
|
|
|
var hasSubFolders = false
|
|
|
|
|
2021-11-14 21:20:23 +01:00
|
|
|
for (item in viewAdapter.getCurrentList()) {
|
2022-02-19 13:19:21 +01:00
|
|
|
if (item is MusicDirectory.Child && item.isDirectory) {
|
2021-03-19 04:23:00 +01:00
|
|
|
hasSubFolders = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-30 21:21:50 +01:00
|
|
|
val isArtist = arguments?.getBoolean(Constants.INTENT_ARTIST, false) ?: false
|
|
|
|
val id = arguments?.getString(Constants.INTENT_ID)
|
2021-03-19 04:23:00 +01:00
|
|
|
|
|
|
|
if (hasSubFolders && id != null) {
|
|
|
|
downloadHandler.downloadRecursively(
|
2021-10-16 11:30:51 +02:00
|
|
|
fragment = this,
|
|
|
|
id = id,
|
|
|
|
save = false,
|
|
|
|
append = append,
|
|
|
|
autoPlay = !append,
|
|
|
|
shuffle = shuffle,
|
|
|
|
background = false,
|
|
|
|
playNext = false,
|
|
|
|
unpin = false,
|
|
|
|
isArtist = isArtist
|
2021-03-19 04:23:00 +01:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
downloadHandler.download(
|
2021-10-16 11:30:51 +02:00
|
|
|
fragment = this,
|
|
|
|
append = append,
|
|
|
|
save = false,
|
|
|
|
autoPlay = !append,
|
|
|
|
playNext = false,
|
|
|
|
shuffle = shuffle,
|
2021-11-14 21:20:23 +01:00
|
|
|
songs = getAllSongs()
|
2021-03-19 04:23:00 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-14 21:20:23 +01:00
|
|
|
@Suppress("UNCHECKED_CAST")
|
2022-03-27 14:57:07 +02:00
|
|
|
private fun getAllSongs(): List<Track> {
|
2021-11-14 21:20:23 +01:00
|
|
|
return viewAdapter.getCurrentList().filter {
|
2022-03-27 14:57:07 +02:00
|
|
|
it is Track && !it.isDirectory
|
|
|
|
} as List<Track>
|
2021-11-14 21:20:23 +01:00
|
|
|
}
|
|
|
|
|
2022-06-19 12:59:26 +02:00
|
|
|
private fun selectAllOrNone() {
|
2021-11-14 21:20:23 +01:00
|
|
|
val someUnselected = viewAdapter.selectedSet.size < childCount
|
2021-03-19 04:23:00 +01:00
|
|
|
|
|
|
|
selectAll(someUnselected, true)
|
|
|
|
}
|
|
|
|
|
2022-06-19 12:59:26 +02:00
|
|
|
private fun selectAll(selected: Boolean, toast: Boolean) {
|
2021-11-14 21:20:23 +01:00
|
|
|
var selectedCount = viewAdapter.selectedSet.size * -1
|
2021-10-18 12:57:21 +02:00
|
|
|
|
2021-11-14 21:20:23 +01:00
|
|
|
selectedCount += viewAdapter.setSelectionStatusOfAll(selected)
|
2021-10-16 11:30:51 +02:00
|
|
|
|
2021-11-14 21:20:23 +01:00
|
|
|
// Display toast: N tracks selected
|
2021-03-19 04:23:00 +01:00
|
|
|
if (toast) {
|
2021-11-14 21:20:23 +01:00
|
|
|
val toastResId = R.string.select_album_n_selected
|
|
|
|
Util.toast(activity, getString(toastResId, selectedCount.coerceAtLeast(0)))
|
2021-03-19 04:23:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-27 14:57:07 +02:00
|
|
|
internal open fun enableButtons(selection: List<Track> = getSelectedSongs()) {
|
2021-04-21 22:36:19 +02:00
|
|
|
val enabled = selection.isNotEmpty()
|
2021-03-19 04:23:00 +01:00
|
|
|
var unpinEnabled = false
|
|
|
|
var deleteEnabled = false
|
2021-11-25 19:44:16 +01:00
|
|
|
val multipleSelection = viewAdapter.hasMultipleSelection()
|
2021-03-19 04:23:00 +01:00
|
|
|
|
|
|
|
var pinnedCount = 0
|
|
|
|
|
|
|
|
for (song in selection) {
|
|
|
|
val downloadFile = mediaPlayerController.getDownloadFileForSong(song)
|
|
|
|
if (downloadFile.isWorkDone) {
|
|
|
|
deleteEnabled = true
|
|
|
|
}
|
|
|
|
if (downloadFile.isSaved) {
|
|
|
|
pinnedCount++
|
|
|
|
unpinEnabled = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
playNowButton?.isVisible = enabled
|
2021-11-25 19:44:16 +01:00
|
|
|
playNextButton?.isVisible = enabled && multipleSelection
|
|
|
|
playLastButton?.isVisible = enabled && multipleSelection
|
2021-10-16 11:30:51 +02:00
|
|
|
pinButton?.isVisible = (enabled && !isOffline() && selection.size > pinnedCount)
|
|
|
|
unpinButton?.isVisible = (enabled && unpinEnabled)
|
|
|
|
downloadButton?.isVisible = (enabled && !deleteEnabled && !isOffline())
|
|
|
|
deleteButton?.isVisible = (enabled && deleteEnabled)
|
2021-03-19 04:23:00 +01:00
|
|
|
}
|
|
|
|
|
2022-06-19 12:59:26 +02:00
|
|
|
private fun downloadBackground(save: Boolean) {
|
2021-10-16 11:30:51 +02:00
|
|
|
var songs = getSelectedSongs()
|
2021-03-19 04:23:00 +01:00
|
|
|
|
|
|
|
if (songs.isEmpty()) {
|
2021-11-14 21:20:23 +01:00
|
|
|
songs = getAllSongs()
|
2021-03-19 04:23:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
downloadBackground(save, songs)
|
|
|
|
}
|
|
|
|
|
2022-03-11 19:35:18 +01:00
|
|
|
private fun downloadBackground(
|
|
|
|
save: Boolean,
|
2022-03-27 14:57:07 +02:00
|
|
|
songs: List<Track?>
|
2022-03-11 19:35:18 +01:00
|
|
|
) {
|
2021-03-19 04:23:00 +01:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2022-03-27 14:57:07 +02:00
|
|
|
internal fun delete(songs: List<Track> = getSelectedSongs()) {
|
2021-11-15 20:01:04 +01:00
|
|
|
Util.toast(
|
|
|
|
context,
|
|
|
|
resources.getQuantityString(
|
|
|
|
R.plurals.select_album_n_songs_deleted, songs.size, songs.size
|
|
|
|
)
|
|
|
|
)
|
2021-03-19 04:23:00 +01:00
|
|
|
|
|
|
|
mediaPlayerController.delete(songs)
|
|
|
|
}
|
|
|
|
|
2022-03-27 14:57:07 +02:00
|
|
|
internal fun unpin(songs: List<Track> = getSelectedSongs()) {
|
2021-03-19 04:23:00 +01:00
|
|
|
Util.toast(
|
|
|
|
context,
|
|
|
|
resources.getQuantityString(
|
|
|
|
R.plurals.select_album_n_songs_unpinned, songs.size, songs.size
|
|
|
|
)
|
|
|
|
)
|
|
|
|
mediaPlayerController.unpin(songs)
|
|
|
|
}
|
|
|
|
|
2021-11-30 00:46:48 +01:00
|
|
|
override val defaultObserver: (List<MusicDirectory.Child>) -> Unit = {
|
2021-10-18 12:57:21 +02:00
|
|
|
|
2022-06-19 12:59:26 +02:00
|
|
|
Timber.i("Received list")
|
2021-11-30 00:46:48 +01:00
|
|
|
val entryList: MutableList<MusicDirectory.Child> = it.toMutableList()
|
2021-04-21 21:55:55 +02:00
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
if (listModel.currentListIsSortable && Settings.shouldSortByDisc) {
|
2021-10-18 12:57:21 +02:00
|
|
|
Collections.sort(entryList, EntryByDiscAndTrackComparator())
|
2021-04-21 21:55:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var allVideos = true
|
|
|
|
var songCount = 0
|
|
|
|
|
2021-10-18 12:57:21 +02:00
|
|
|
for (entry in entryList) {
|
2021-04-21 21:55:55 +02:00
|
|
|
if (!entry.isVideo) {
|
|
|
|
allVideos = false
|
|
|
|
}
|
|
|
|
if (!entry.isDirectory) {
|
|
|
|
songCount++
|
2021-03-19 04:23:00 +01:00
|
|
|
}
|
2021-04-21 21:55:55 +02:00
|
|
|
}
|
2021-03-19 04:23:00 +01:00
|
|
|
|
2021-11-30 21:21:50 +01:00
|
|
|
val listSize = arguments?.getInt(Constants.INTENT_ALBUM_LIST_SIZE, 0) ?: 0
|
2021-03-19 04:23:00 +01:00
|
|
|
|
2021-11-25 19:44:16 +01:00
|
|
|
// Hide select button for video lists and singular selection lists
|
2021-11-30 00:46:48 +01:00
|
|
|
selectButton!!.isVisible = !allVideos && viewAdapter.hasMultipleSelection() && songCount > 0
|
2021-04-21 21:55:55 +02:00
|
|
|
|
2021-11-23 20:38:26 +01:00
|
|
|
if (songCount > 0) {
|
2021-04-21 21:55:55 +02:00
|
|
|
if (listSize == 0 || songCount < listSize) {
|
|
|
|
moreButton!!.visibility = View.GONE
|
|
|
|
} else {
|
|
|
|
moreButton!!.visibility = View.VISIBLE
|
2022-06-19 12:59:26 +02:00
|
|
|
if ((arguments?.getInt(Constants.INTENT_RANDOM, 0) ?: 0) > 0) {
|
2021-12-05 21:07:08 +01:00
|
|
|
moreRandomTracks()
|
2022-06-19 12:59:26 +02:00
|
|
|
} else if ((arguments?.getString(Constants.INTENT_GENRE_NAME, "") ?: "") != "") {
|
2021-12-05 21:07:08 +01:00
|
|
|
moreSongsForGenre()
|
2021-03-19 04:23:00 +01:00
|
|
|
}
|
2021-04-21 21:55:55 +02:00
|
|
|
}
|
|
|
|
}
|
2021-03-19 04:23:00 +01:00
|
|
|
|
2021-11-23 20:38:26 +01:00
|
|
|
// Show a text if we have no entries
|
2021-11-26 19:01:14 +01:00
|
|
|
emptyView.isVisible = entryList.isEmpty()
|
2021-11-23 20:38:26 +01:00
|
|
|
|
2021-04-21 21:55:55 +02:00
|
|
|
enableButtons()
|
2021-03-19 04:23:00 +01:00
|
|
|
|
2021-11-23 20:38:26 +01:00
|
|
|
val isAlbumList = arguments?.containsKey(
|
2021-11-30 21:21:50 +01:00
|
|
|
Constants.INTENT_ALBUM_LIST_TYPE
|
2021-11-23 21:58:58 +01:00
|
|
|
) ?: false
|
2021-04-21 22:55:58 +02:00
|
|
|
|
2021-10-18 12:57:21 +02:00
|
|
|
playAllButtonVisible = !(isAlbumList || entryList.isEmpty()) && !allVideos
|
2021-05-09 10:25:04 +02:00
|
|
|
shareButtonVisible = !isOffline() && songCount > 0
|
2021-03-19 04:23:00 +01:00
|
|
|
|
2021-11-23 20:38:26 +01:00
|
|
|
playAllButton?.isVisible = playAllButtonVisible
|
|
|
|
shareButton?.isVisible = shareButtonVisible
|
2021-03-19 04:23:00 +01:00
|
|
|
|
2021-10-18 12:57:21 +02:00
|
|
|
if (songCount > 0 && listModel.showHeader) {
|
2021-11-30 21:21:50 +01:00
|
|
|
val intentAlbumName = arguments?.getString(Constants.INTENT_NAME, "")
|
2021-11-29 19:00:28 +01:00
|
|
|
val albumHeader = AlbumHeader(it, intentAlbumName)
|
2021-10-18 12:57:21 +02:00
|
|
|
val mixedList: MutableList<Identifiable> = mutableListOf(albumHeader)
|
|
|
|
mixedList.addAll(entryList)
|
|
|
|
viewAdapter.submitList(mixedList)
|
|
|
|
} else {
|
|
|
|
viewAdapter.submitList(entryList)
|
|
|
|
}
|
|
|
|
|
2021-11-30 21:21:50 +01:00
|
|
|
val playAll = arguments?.getBoolean(Constants.INTENT_AUTOPLAY, false) ?: false
|
2021-11-23 20:38:26 +01:00
|
|
|
|
2021-04-21 21:55:55 +02:00
|
|
|
if (playAll && songCount > 0) {
|
|
|
|
playAll(
|
2021-11-30 21:21:50 +01:00
|
|
|
arguments?.getBoolean(Constants.INTENT_SHUFFLE, false) ?: false,
|
2021-04-21 22:55:58 +02:00
|
|
|
false
|
2021-04-21 21:55:55 +02:00
|
|
|
)
|
2021-03-19 04:23:00 +01:00
|
|
|
}
|
|
|
|
|
2021-10-16 11:30:51 +02:00
|
|
|
listModel.currentListIsSortable = true
|
2022-06-19 12:59:26 +02:00
|
|
|
|
|
|
|
Timber.i("Processed list")
|
2021-04-21 21:55:55 +02:00
|
|
|
}
|
|
|
|
|
2021-12-05 21:07:08 +01:00
|
|
|
private fun moreSongsForGenre(args: Bundle = requireArguments()) {
|
|
|
|
moreButton!!.setOnClickListener {
|
|
|
|
val theGenre = args.getString(Constants.INTENT_GENRE_NAME)
|
|
|
|
val size = args.getInt(Constants.INTENT_ALBUM_LIST_SIZE, 0)
|
|
|
|
val theOffset = args.getInt(
|
|
|
|
Constants.INTENT_ALBUM_LIST_OFFSET, 0
|
|
|
|
) + size
|
|
|
|
val bundle = Bundle()
|
|
|
|
bundle.putString(Constants.INTENT_GENRE_NAME, theGenre)
|
|
|
|
bundle.putInt(Constants.INTENT_ALBUM_LIST_SIZE, size)
|
|
|
|
bundle.putInt(Constants.INTENT_ALBUM_LIST_OFFSET, theOffset)
|
|
|
|
|
|
|
|
Navigation.findNavController(requireView())
|
|
|
|
.navigate(R.id.trackCollectionFragment, bundle)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun moreRandomTracks() {
|
|
|
|
val listSize = arguments?.getInt(Constants.INTENT_ALBUM_LIST_SIZE, 0) ?: 0
|
|
|
|
|
2021-12-08 17:51:31 +01:00
|
|
|
moreButton!!.setOnClickListener {
|
2021-12-05 21:07:08 +01:00
|
|
|
val offset = requireArguments().getInt(
|
|
|
|
Constants.INTENT_ALBUM_LIST_OFFSET, 0
|
|
|
|
) + listSize
|
|
|
|
val bundle = Bundle()
|
|
|
|
bundle.putInt(Constants.INTENT_RANDOM, 1)
|
|
|
|
bundle.putInt(Constants.INTENT_ALBUM_LIST_SIZE, listSize)
|
|
|
|
bundle.putInt(Constants.INTENT_ALBUM_LIST_OFFSET, offset)
|
|
|
|
Navigation.findNavController(requireView()).navigate(
|
|
|
|
R.id.trackCollectionFragment, bundle
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-27 14:57:07 +02:00
|
|
|
internal fun getSelectedSongs(): List<Track> {
|
2021-11-14 21:20:23 +01:00
|
|
|
// Walk through selected set and get the Entries based on the saved ids.
|
|
|
|
return viewAdapter.getCurrentList().mapNotNull {
|
2022-03-27 14:57:07 +02:00
|
|
|
if (it is Track && viewAdapter.isSelected(it.longId))
|
2021-11-14 21:20:23 +01:00
|
|
|
it
|
|
|
|
else
|
|
|
|
null
|
2021-10-16 11:30:51 +02:00
|
|
|
}
|
2021-03-19 04:23:00 +01:00
|
|
|
}
|
2021-10-16 11:30:51 +02:00
|
|
|
|
|
|
|
override fun setTitle(title: String?) {
|
|
|
|
setTitle(this@TrackCollectionFragment, title)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun setTitle(id: Int) {
|
|
|
|
setTitle(this@TrackCollectionFragment, id)
|
|
|
|
}
|
|
|
|
|
|
|
|
@Suppress("LongMethod")
|
2021-11-30 20:53:10 +01:00
|
|
|
override fun getLiveData(
|
|
|
|
args: Bundle?,
|
|
|
|
refresh: Boolean
|
|
|
|
): LiveData<List<MusicDirectory.Child>> {
|
2022-06-19 12:59:26 +02:00
|
|
|
Timber.i("Starting gathering track collection data...")
|
2021-10-16 11:30:51 +02:00
|
|
|
if (args == null) return listModel.currentList
|
2021-11-30 21:21:50 +01:00
|
|
|
val id = args.getString(Constants.INTENT_ID)
|
|
|
|
val isAlbum = args.getBoolean(Constants.INTENT_IS_ALBUM, false)
|
|
|
|
val name = args.getString(Constants.INTENT_NAME)
|
|
|
|
val playlistId = args.getString(Constants.INTENT_PLAYLIST_ID)
|
|
|
|
val podcastChannelId = args.getString(Constants.INTENT_PODCAST_CHANNEL_ID)
|
|
|
|
val playlistName = args.getString(Constants.INTENT_PLAYLIST_NAME)
|
|
|
|
val shareId = args.getString(Constants.INTENT_SHARE_ID)
|
|
|
|
val shareName = args.getString(Constants.INTENT_SHARE_NAME)
|
|
|
|
val genreName = args.getString(Constants.INTENT_GENRE_NAME)
|
|
|
|
|
|
|
|
val getStarredTracks = args.getInt(Constants.INTENT_STARRED, 0)
|
|
|
|
val getVideos = args.getInt(Constants.INTENT_VIDEOS, 0)
|
|
|
|
val getRandomTracks = args.getInt(Constants.INTENT_RANDOM, 0)
|
|
|
|
val albumListSize = args.getInt(Constants.INTENT_ALBUM_LIST_SIZE, 0)
|
|
|
|
val albumListOffset = args.getInt(Constants.INTENT_ALBUM_LIST_OFFSET, 0)
|
|
|
|
val refresh2 = args.getBoolean(Constants.INTENT_REFRESH, true) || refresh
|
2021-10-16 11:30:51 +02:00
|
|
|
|
|
|
|
listModel.viewModelScope.launch(handler) {
|
2021-11-23 20:38:26 +01:00
|
|
|
refreshListView?.isRefreshing = true
|
2021-10-16 11:30:51 +02:00
|
|
|
|
|
|
|
if (playlistId != null) {
|
|
|
|
setTitle(playlistName!!)
|
|
|
|
listModel.getPlaylist(playlistId, playlistName)
|
|
|
|
} else if (podcastChannelId != null) {
|
|
|
|
setTitle(getString(R.string.podcasts_label))
|
|
|
|
listModel.getPodcastEpisodes(podcastChannelId)
|
|
|
|
} else if (shareId != null) {
|
|
|
|
setTitle(shareName)
|
|
|
|
listModel.getShare(shareId)
|
|
|
|
} else if (genreName != null) {
|
|
|
|
setTitle(genreName)
|
|
|
|
listModel.getSongsForGenre(genreName, albumListSize, albumListOffset)
|
|
|
|
} else if (getStarredTracks != 0) {
|
|
|
|
setTitle(getString(R.string.main_songs_starred))
|
|
|
|
listModel.getStarred()
|
|
|
|
} else if (getVideos != 0) {
|
|
|
|
setTitle(R.string.main_videos)
|
2021-11-30 20:53:10 +01:00
|
|
|
listModel.getVideos(refresh2)
|
2021-10-16 11:30:51 +02:00
|
|
|
} else if (getRandomTracks != 0) {
|
|
|
|
setTitle(R.string.main_songs_random)
|
|
|
|
listModel.getRandom(albumListSize)
|
|
|
|
} else {
|
|
|
|
setTitle(name)
|
2022-04-18 07:31:06 +02:00
|
|
|
if (!isOffline() && Settings.shouldUseId3Tags || Settings.useId3TagsOffline) {
|
2021-10-16 11:30:51 +02:00
|
|
|
if (isAlbum) {
|
2021-11-30 20:53:10 +01:00
|
|
|
listModel.getAlbum(refresh2, id!!, name)
|
2021-10-16 11:30:51 +02:00
|
|
|
} else {
|
2021-11-23 20:38:26 +01:00
|
|
|
throw IllegalAccessException("Use AlbumFragment instead!")
|
2021-10-16 11:30:51 +02:00
|
|
|
}
|
|
|
|
} else {
|
2021-11-30 20:53:10 +01:00
|
|
|
listModel.getMusicDirectory(refresh2, id!!, name)
|
2021-10-16 11:30:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-23 20:38:26 +01:00
|
|
|
refreshListView?.isRefreshing = false
|
2021-10-16 11:30:51 +02:00
|
|
|
}
|
|
|
|
return listModel.currentList
|
|
|
|
}
|
|
|
|
|
2021-11-27 00:51:41 +01:00
|
|
|
@Suppress("LongMethod")
|
2021-10-16 11:30:51 +02:00
|
|
|
override fun onContextMenuItemSelected(
|
|
|
|
menuItem: MenuItem,
|
2021-11-30 00:46:48 +01:00
|
|
|
item: MusicDirectory.Child
|
2021-10-16 11:30:51 +02:00
|
|
|
): Boolean {
|
2022-03-05 00:10:20 +01:00
|
|
|
val songs = getClickedSong(item)
|
2021-11-27 00:51:41 +01:00
|
|
|
|
|
|
|
when (menuItem.itemId) {
|
2022-03-05 00:10:20 +01:00
|
|
|
R.id.song_menu_play_now -> {
|
2022-03-11 19:35:18 +01:00
|
|
|
playNow(false, songs)
|
2021-11-27 00:51:41 +01:00
|
|
|
}
|
2022-03-05 00:10:20 +01:00
|
|
|
R.id.song_menu_play_next -> {
|
|
|
|
downloadHandler.download(
|
2022-03-11 19:35:18 +01:00
|
|
|
fragment = this@TrackCollectionFragment,
|
2022-03-05 00:10:20 +01:00
|
|
|
append = true,
|
|
|
|
save = false,
|
|
|
|
autoPlay = false,
|
|
|
|
playNext = true,
|
|
|
|
shuffle = false,
|
|
|
|
songs = songs
|
2021-11-27 00:51:41 +01:00
|
|
|
)
|
|
|
|
}
|
2022-03-05 00:10:20 +01:00
|
|
|
R.id.song_menu_play_last -> {
|
2022-03-11 19:35:18 +01:00
|
|
|
playNow(true, songs)
|
2021-11-27 00:51:41 +01:00
|
|
|
}
|
2022-03-05 00:10:20 +01:00
|
|
|
R.id.song_menu_pin -> {
|
|
|
|
downloadBackground(true, songs)
|
2021-11-27 00:51:41 +01:00
|
|
|
}
|
2022-03-05 00:10:20 +01:00
|
|
|
R.id.song_menu_unpin -> {
|
2022-03-11 19:35:18 +01:00
|
|
|
unpin(songs)
|
2021-11-27 00:51:41 +01:00
|
|
|
}
|
2022-03-05 00:10:20 +01:00
|
|
|
R.id.song_menu_download -> {
|
|
|
|
downloadBackground(false, songs)
|
2021-11-27 00:51:41 +01:00
|
|
|
}
|
|
|
|
R.id.select_album_play_all -> {
|
|
|
|
// TODO: Why is this being handled here?!
|
|
|
|
playAll()
|
|
|
|
}
|
2022-03-05 00:10:20 +01:00
|
|
|
R.id.song_menu_share -> {
|
2022-03-27 14:57:07 +02:00
|
|
|
if (item is Track) {
|
2021-11-30 00:46:48 +01:00
|
|
|
shareHandler.createShare(
|
|
|
|
this, listOf(item), refreshListView,
|
|
|
|
cancellationToken!!
|
|
|
|
)
|
|
|
|
}
|
2021-11-27 00:51:41 +01:00
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
return super.onContextItemSelected(menuItem)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
2021-10-16 11:30:51 +02:00
|
|
|
}
|
|
|
|
|
2022-06-19 12:59:26 +02:00
|
|
|
private fun getClickedSong(item: MusicDirectory.Child): List<Track> {
|
2022-03-06 00:19:25 +01:00
|
|
|
// This can probably be done better
|
2022-03-05 00:10:20 +01:00
|
|
|
return viewAdapter.getCurrentList().mapNotNull {
|
2022-03-27 14:57:07 +02:00
|
|
|
if (it is Track && (it.id == item.id))
|
2022-03-05 00:10:20 +01:00
|
|
|
it
|
|
|
|
else
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-30 00:46:48 +01:00
|
|
|
override fun onItemClick(item: MusicDirectory.Child) {
|
2021-11-23 20:38:26 +01:00
|
|
|
when {
|
|
|
|
item.isDirectory -> {
|
|
|
|
val bundle = Bundle()
|
2021-11-30 21:21:50 +01:00
|
|
|
bundle.putString(Constants.INTENT_ID, item.id)
|
|
|
|
bundle.putBoolean(Constants.INTENT_IS_ALBUM, item.isDirectory)
|
|
|
|
bundle.putString(Constants.INTENT_NAME, item.title)
|
|
|
|
bundle.putString(Constants.INTENT_PARENT_ID, item.parent)
|
2021-11-23 20:38:26 +01:00
|
|
|
Navigation.findNavController(requireView()).navigate(
|
|
|
|
R.id.trackCollectionFragment,
|
|
|
|
bundle
|
|
|
|
)
|
|
|
|
}
|
2022-03-27 14:57:07 +02:00
|
|
|
item is Track && item.isVideo -> {
|
2021-11-23 20:38:26 +01:00
|
|
|
VideoPlayer.playVideo(requireContext(), item)
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
enableButtons()
|
|
|
|
}
|
|
|
|
}
|
2021-10-16 11:30:51 +02:00
|
|
|
}
|
2021-03-19 04:23:00 +01:00
|
|
|
}
|