Use Coroutines for triggering the download or playback of music through the context menus

This commit is contained in:
birdbird 2023-05-09 09:34:15 +00:00
parent 842cb36ecb
commit cd982814cf
10 changed files with 329 additions and 475 deletions

View File

@ -1,6 +1,6 @@
[versions]
# You need to run ./gradlew wrapper after updating the version
gradle = "7.6"
gradle = "8.1.1"
navigation = "2.5.3"
gradlePlugin = "8.0.1"

View File

@ -21,6 +21,7 @@ import org.moire.ultrasonic.domain.GenericEntry
import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.service.RxBus
import org.moire.ultrasonic.service.plusAssign
import org.moire.ultrasonic.subsonic.DownloadAction
import org.moire.ultrasonic.subsonic.DownloadHandler
import org.moire.ultrasonic.util.Settings
@ -129,81 +130,54 @@ abstract class EntryListFragment<T : GenericEntry> : MultiListFragment<T>() {
): Boolean {
when (menuItem.itemId) {
R.id.menu_play_now ->
downloadHandler.downloadRecursively(
downloadHandler.fetchTracksAndAddToController(
fragment,
item.id,
save = false,
append = false,
autoPlay = true,
shuffle = false,
background = false,
playNext = false,
unpin = false,
isArtist = isArtist
)
R.id.menu_play_next ->
downloadHandler.downloadRecursively(
downloadHandler.fetchTracksAndAddToController(
fragment,
item.id,
save = false,
append = false,
autoPlay = true,
shuffle = true,
background = false,
playNext = true,
unpin = false,
isArtist = isArtist
)
R.id.menu_play_last ->
downloadHandler.downloadRecursively(
downloadHandler.fetchTracksAndAddToController(
fragment,
item.id,
save = false,
append = true,
autoPlay = false,
shuffle = false,
background = false,
playNext = false,
unpin = false,
isArtist = isArtist
)
R.id.menu_pin ->
downloadHandler.downloadRecursively(
downloadHandler.justDownload(
action = DownloadAction.PIN,
fragment,
item.id,
save = true,
append = true,
autoPlay = false,
shuffle = false,
background = false,
playNext = false,
unpin = false,
isArtist = isArtist
)
R.id.menu_unpin ->
downloadHandler.downloadRecursively(
downloadHandler.justDownload(
action = DownloadAction.UNPIN,
fragment,
item.id,
save = false,
append = false,
autoPlay = false,
shuffle = false,
background = false,
playNext = false,
unpin = true,
isArtist = isArtist
)
R.id.menu_download ->
downloadHandler.downloadRecursively(
downloadHandler.justDownload(
action = DownloadAction.DOWNLOAD,
fragment,
item.id,
save = false,
append = false,
autoPlay = false,
shuffle = false,
background = true,
playNext = false,
unpin = false,
isArtist = isArtist
)
else -> return false

View File

@ -309,7 +309,6 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
}
mediaPlayerController.addToPlaylist(
listOf(song),
cachePermanently = false,
autoPlay = false,
shuffle = false,
insertionMode = MediaPlayerController.InsertionMode.APPEND
@ -367,40 +366,37 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
when (menuItem.itemId) {
R.id.song_menu_play_now -> {
songs.add(item)
downloadHandler.download(
fragment = this,
append = false,
save = false,
autoPlay = true,
playNext = false,
shuffle = false,
downloadHandler.addTracksToMediaController(
songs = songs,
append = false,
playNext = false,
autoPlay = true,
shuffle = false,
fragment = this,
playlistName = null
)
}
R.id.song_menu_play_next -> {
songs.add(item)
downloadHandler.download(
fragment = this,
append = true,
save = false,
autoPlay = false,
playNext = true,
shuffle = false,
downloadHandler.addTracksToMediaController(
songs = songs,
append = true,
playNext = true,
autoPlay = false,
shuffle = false,
fragment = this,
playlistName = null
)
}
R.id.song_menu_play_last -> {
songs.add(item)
downloadHandler.download(
fragment = this,
append = true,
save = false,
autoPlay = false,
playNext = false,
shuffle = false,
downloadHandler.addTracksToMediaController(
songs = songs,
append = true,
playNext = false,
autoPlay = false,
shuffle = false,
fragment = this,
playlistName = null
)
}

View File

@ -40,11 +40,10 @@ import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.model.TrackCollectionModel
import org.moire.ultrasonic.service.DownloadService
import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.RxBus
import org.moire.ultrasonic.service.plusAssign
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
import org.moire.ultrasonic.subsonic.DownloadAction
import org.moire.ultrasonic.subsonic.ShareHandler
import org.moire.ultrasonic.subsonic.VideoPlayer
import org.moire.ultrasonic.util.CancellationToken
@ -84,7 +83,6 @@ open class TrackCollectionFragment(
private var shareButton: MenuItem? = null
internal val mediaPlayerController: MediaPlayerController by inject()
private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
private val shareHandler: ShareHandler by inject()
internal var cancellationToken: CancellationToken? = null
@ -211,11 +209,14 @@ open class TrackCollectionFragment(
}
playNextButton?.setOnClickListener {
downloadHandler.download(
this@TrackCollectionFragment, append = true,
save = false, autoPlay = false, playNext = true, shuffle = false,
downloadHandler.addTracksToMediaController(
songs = getSelectedSongs(),
playlistName = navArgs.playlistName
append = true,
playNext = true,
autoPlay = false,
shuffle = false,
playlistName = navArgs.playlistName,
this@TrackCollectionFragment
)
}
@ -304,9 +305,14 @@ open class TrackCollectionFragment(
selectedSongs: List<Track> = getSelectedSongs()
) {
if (selectedSongs.isNotEmpty()) {
downloadHandler.download(
this, append, false, !append, playNext = false,
shuffle = false, songs = selectedSongs, null
downloadHandler.addTracksToMediaController(
songs = selectedSongs,
append = append,
playNext = false,
autoPlay = !append,
shuffle = false,
playlistName = null,
fragment = this
)
} else {
playAll(false, append)
@ -337,31 +343,29 @@ open class TrackCollectionFragment(
}
val isArtist = navArgs.isArtist
val id = navArgs.id
// Need a valid id to download stuff
val id = navArgs.id ?: return
if (hasSubFolders) {
downloadHandler.downloadRecursively(
downloadHandler.fetchTracksAndAddToController(
fragment = this,
id = id,
save = false,
append = append,
autoPlay = !append,
shuffle = shuffle,
background = false,
playNext = false,
unpin = false,
isArtist = isArtist
)
} else {
downloadHandler.download(
fragment = this,
append = append,
save = false,
autoPlay = !append,
playNext = false,
shuffle = shuffle,
downloadHandler.addTracksToMediaController(
songs = getAllSongs(),
playlistName = navArgs.playlistName
append = append,
playNext = false,
autoPlay = !append,
shuffle = shuffle,
playlistName = navArgs.playlistName,
fragment = this
)
}
}
@ -416,62 +420,35 @@ open class TrackCollectionFragment(
}
}
private fun downloadBackground(save: Boolean) {
var songs = getSelectedSongs()
private fun downloadBackground(save: Boolean, tracks: List<Track> = getSelectedSongs()) {
var songs = tracks
if (songs.isEmpty()) {
songs = getAllSongs()
}
downloadBackground(save, songs)
}
private fun downloadBackground(
save: Boolean,
songs: List<Track?>
) {
val onValid = Runnable {
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
DownloadService.download(songs.filterNotNull(), 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()
val action = if (save) DownloadAction.PIN else DownloadAction.DOWNLOAD
downloadHandler.justDownload(
action = action,
fragment = this,
tracks = songs
)
}
internal fun delete(songs: List<Track> = getSelectedSongs()) {
Util.toast(
context,
resources.getQuantityString(
R.plurals.select_album_n_songs_deleted, songs.size, songs.size
)
downloadHandler.justDownload(
action = DownloadAction.DELETE,
fragment = this,
tracks = songs
)
DownloadService.delete(songs)
}
internal fun unpin(songs: List<Track> = getSelectedSongs()) {
Util.toast(
context,
resources.getQuantityString(
R.plurals.select_album_n_songs_unpinned, songs.size, songs.size
)
downloadHandler.justDownload(
action = DownloadAction.UNPIN,
fragment = this,
tracks = songs
)
DownloadService.unpin(songs)
}
override val defaultObserver: (List<MusicDirectory.Child>) -> Unit = {
@ -637,15 +614,14 @@ open class TrackCollectionFragment(
playNow(false, songs)
}
R.id.song_menu_play_next -> {
downloadHandler.download(
fragment = this@TrackCollectionFragment,
append = true,
save = false,
autoPlay = false,
playNext = true,
shuffle = false,
downloadHandler.addTracksToMediaController(
songs = songs,
playlistName = navArgs.playlistName
append = true,
playNext = true,
autoPlay = false,
shuffle = false,
playlistName = navArgs.playlistName,
fragment = this@TrackCollectionFragment
)
}
R.id.song_menu_play_last -> {

View File

@ -38,6 +38,7 @@ import org.moire.ultrasonic.domain.Playlist
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.service.OfflineException
import org.moire.ultrasonic.subsonic.DownloadAction
import org.moire.ultrasonic.subsonic.DownloadHandler
import org.moire.ultrasonic.util.BackgroundTask
import org.moire.ultrasonic.util.CacheCleaner
@ -147,45 +148,33 @@ class PlaylistsFragment : Fragment() {
val playlist = playlistsListView!!.getItemAtPosition(info.position) as Playlist
when (menuItem.itemId) {
R.id.playlist_menu_pin -> {
downloadHandler.value.downloadPlaylist(
this,
downloadHandler.value.justDownload(
DownloadAction.PIN,
fragment = this,
id = playlist.id,
name = playlist.name,
save = true,
append = true,
autoplay = false,
shuffle = false,
background = true,
playNext = false,
unpin = false
isShare = false,
isDirectory = false
)
}
R.id.playlist_menu_unpin -> {
downloadHandler.value.downloadPlaylist(
this,
downloadHandler.value.justDownload(
DownloadAction.UNPIN,
fragment = this,
id = playlist.id,
name = playlist.name,
save = false,
append = false,
autoplay = false,
shuffle = false,
background = true,
playNext = false,
unpin = true
isShare = false,
isDirectory = false
)
}
R.id.playlist_menu_download -> {
downloadHandler.value.downloadPlaylist(
this,
downloadHandler.value.justDownload(
DownloadAction.DOWNLOAD,
fragment = this,
id = playlist.id,
name = playlist.name,
save = false,
append = false,
autoplay = false,
shuffle = false,
background = true,
playNext = false,
unpin = false
isShare = false,
isDirectory = false
)
}
R.id.playlist_menu_play_now -> {

View File

@ -28,7 +28,8 @@ import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import java.util.Locale
import org.koin.java.KoinJavaComponent
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.NavigationGraphDirections
import org.moire.ultrasonic.R
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException
@ -36,6 +37,7 @@ import org.moire.ultrasonic.domain.Share
import org.moire.ultrasonic.fragment.FragmentTitle
import org.moire.ultrasonic.service.MusicServiceFactory
import org.moire.ultrasonic.service.OfflineException
import org.moire.ultrasonic.subsonic.DownloadAction
import org.moire.ultrasonic.subsonic.DownloadHandler
import org.moire.ultrasonic.util.BackgroundTask
import org.moire.ultrasonic.util.CancellationToken
@ -50,14 +52,12 @@ import org.moire.ultrasonic.view.ShareAdapter
*
* TODO: This file has been converted from Java, but not modernized yet.
*/
class SharesFragment : Fragment() {
class SharesFragment : Fragment(), KoinComponent {
private var refreshSharesListView: SwipeRefreshLayout? = null
private var sharesListView: ListView? = null
private var emptyTextView: View? = null
private var shareAdapter: ShareAdapter? = null
private val downloadHandler = KoinJavaComponent.inject<DownloadHandler>(
DownloadHandler::class.java
)
private val downloadHandler = inject<DownloadHandler>()
private var cancellationToken: CancellationToken? = null
override fun onCreate(savedInstanceState: Bundle?) {
Util.applyTheme(this.context)
@ -72,7 +72,6 @@ class SharesFragment : Fragment() {
return inflater.inflate(R.layout.select_share, container, false)
}
@Suppress("NAME_SHADOWING")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
cancellationToken = CancellationToken()
refreshSharesListView = view.findViewById(R.id.select_share_refresh)
@ -132,73 +131,55 @@ class SharesFragment : Fragment() {
val share = sharesListView!!.getItemAtPosition(info.position) as Share
when (menuItem.itemId) {
R.id.share_menu_pin -> {
downloadHandler.value.downloadShare(
this,
share.id,
share.name,
save = true,
append = true,
autoplay = false,
shuffle = false,
background = true,
playNext = false,
unpin = false
downloadHandler.value.justDownload(
DownloadAction.PIN,
fragment = this,
id = share.id,
name = share.name,
isShare = true,
isDirectory = false
)
}
R.id.share_menu_unpin -> {
downloadHandler.value.downloadShare(
this,
share.id,
share.name,
save = false,
append = false,
autoplay = false,
shuffle = false,
background = true,
playNext = false,
unpin = true
downloadHandler.value.justDownload(
DownloadAction.UNPIN,
fragment = this,
id = share.id,
name = share.name,
isShare = true,
isDirectory = false
)
}
R.id.share_menu_download -> {
downloadHandler.value.downloadShare(
this,
share.id,
share.name,
save = false,
append = false,
autoplay = false,
shuffle = false,
background = true,
playNext = false,
unpin = false
downloadHandler.value.justDownload(
DownloadAction.DOWNLOAD,
fragment = this,
id = share.id,
name = share.name,
isShare = true,
isDirectory = false
)
}
R.id.share_menu_play_now -> {
downloadHandler.value.downloadShare(
downloadHandler.value.fetchTracksAndAddToController(
this,
share.id,
share.name,
save = false,
append = false,
autoplay = true,
autoPlay = true,
shuffle = false,
background = false,
playNext = false,
unpin = false
)
}
R.id.share_menu_play_shuffled -> {
downloadHandler.value.downloadShare(
downloadHandler.value.fetchTracksAndAddToController(
this,
share.id,
share.name,
save = false,
append = false,
autoplay = true,
autoPlay = true,
shuffle = true,
background = false,
playNext = false,
unpin = false
)
}
R.id.share_menu_delete -> {

View File

@ -42,7 +42,6 @@ import org.moire.ultrasonic.playback.PlaybackService
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.util.setPin
import org.moire.ultrasonic.util.toMediaItem
import org.moire.ultrasonic.util.toTrack
import timber.log.Timber
@ -314,7 +313,6 @@ class MediaPlayerController(
addToPlaylist(
state.songs,
cachePermanently = false,
autoPlay = false,
shuffle = false,
insertionMode = insertionMode
@ -408,7 +406,6 @@ class MediaPlayerController(
@Synchronized
fun addToPlaylist(
songs: List<Track>,
cachePermanently: Boolean,
autoPlay: Boolean,
shuffle: Boolean,
insertionMode: InsertionMode
@ -423,7 +420,6 @@ class MediaPlayerController(
val mediaItems: List<MediaItem> = songs.map {
val result = it.toMediaItem()
if (cachePermanently) result.setPin(true)
result
}

View File

@ -7,17 +7,11 @@
package org.moire.ultrasonic.subsonic
import android.os.Handler
import android.os.Looper
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import java.util.Collections
import java.util.LinkedList
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
@ -26,12 +20,8 @@ import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.service.DownloadService
import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.util.CommunicationError
import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator
import org.moire.ultrasonic.util.InfoDialog
import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util
import timber.log.Timber
import org.moire.ultrasonic.util.executeTaskWithToast
/**
* Retrieves a list of songs and adds them to the now playing list
@ -39,279 +29,145 @@ import timber.log.Timber
@Suppress("LongParameterList")
class DownloadHandler(
val mediaPlayerController: MediaPlayerController,
val networkAndStorageChecker: NetworkAndStorageChecker
private val networkAndStorageChecker: NetworkAndStorageChecker
) : CoroutineScope by CoroutineScope(Dispatchers.IO) {
private val maxSongs = 500
/**
* Exception Handler for Coroutines
*/
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
Handler(Looper.getMainLooper()).post {
Timber.w(exception)
}
}
// TODO: Use coroutine here (with proper exception handler)
fun download(
fun justDownload(
action: DownloadAction,
fragment: Fragment,
append: Boolean,
save: Boolean,
autoPlay: Boolean,
playNext: Boolean,
shuffle: Boolean,
songs: List<Track>,
playlistName: String?,
id: String? = null,
name: String? = "",
isShare: Boolean = false,
isDirectory: Boolean = true,
isArtist: Boolean = false,
tracks: List<Track>? = null
) {
val onValid = Runnable {
// TODO: The logic here is different than in the controller...
val insertionMode = when {
playNext -> MediaPlayerController.InsertionMode.AFTER_CURRENT
append -> MediaPlayerController.InsertionMode.APPEND
else -> MediaPlayerController.InsertionMode.CLEAR
}
var successString: String? = null
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
mediaPlayerController.addToPlaylist(
songs,
save,
autoPlay,
shuffle,
insertionMode
)
if (playlistName != null) {
mediaPlayerController.suggestedPlaylistName = playlistName
}
if (autoPlay) {
if (Settings.shouldTransitionOnPlayback) {
fragment.findNavController().popBackStack(R.id.playerFragment, true)
fragment.findNavController().navigate(R.id.playerFragment)
}
} else if (save) {
Util.toast(
fragment.context,
fragment.resources.getQuantityString(
R.plurals.select_album_n_songs_pinned,
songs.size,
songs.size
)
)
} else if (playNext) {
Util.toast(
fragment.context,
fragment.resources.getQuantityString(
R.plurals.select_album_n_songs_play_next,
songs.size,
songs.size
)
)
} else if (append) {
Util.toast(
fragment.context,
fragment.resources.getQuantityString(
R.plurals.select_album_n_songs_added,
songs.size,
songs.size
)
)
}
}
onValid.run()
}
fun downloadPlaylist(
fragment: Fragment,
id: String,
name: String?,
save: Boolean,
append: Boolean,
autoplay: Boolean,
shuffle: Boolean,
background: Boolean,
playNext: Boolean,
unpin: Boolean
) {
downloadRecursively(
fragment,
id,
name,
isShare = false,
isDirectory = false,
save = save,
append = append,
autoPlay = autoplay,
shuffle = shuffle,
background = background,
playNext = playNext,
unpin = unpin,
isArtist = false
)
}
fun downloadShare(
fragment: Fragment,
id: String,
name: String?,
save: Boolean,
append: Boolean,
autoplay: Boolean,
shuffle: Boolean,
background: Boolean,
playNext: Boolean,
unpin: Boolean
) {
downloadRecursively(
fragment,
id,
name,
isShare = true,
isDirectory = false,
save = save,
append = append,
autoPlay = autoplay,
shuffle = shuffle,
background = background,
playNext = playNext,
unpin = unpin,
isArtist = false
)
}
fun downloadRecursively(
fragment: Fragment,
id: String?,
save: Boolean,
append: Boolean,
autoPlay: Boolean,
shuffle: Boolean,
background: Boolean,
playNext: Boolean,
unpin: Boolean,
isArtist: Boolean
) {
if (id.isNullOrEmpty()) return
downloadRecursively(
fragment,
id,
"",
isShare = false,
isDirectory = true,
save = save,
append = append,
autoPlay = autoPlay,
shuffle = shuffle,
background = background,
playNext = playNext,
unpin = unpin,
isArtist = isArtist
)
}
private fun downloadRecursively(
fragment: Fragment,
id: String,
name: String?,
isShare: Boolean,
isDirectory: Boolean,
save: Boolean,
append: Boolean,
autoPlay: Boolean,
shuffle: Boolean,
background: Boolean,
playNext: Boolean,
unpin: Boolean,
isArtist: Boolean
) {
// Launch the Job
val job = launch(exceptionHandler) {
executeTaskWithToast(fragment, {
val tracksToDownload: List<Track> = tracks
?: getTracksFromServer(isArtist, id!!, isDirectory, name, isShare)
withContext(Dispatchers.Main) {
// If we are just downloading tracks we don't need to add them to the controller
when (action) {
DownloadAction.DOWNLOAD -> DownloadService.download(tracksToDownload, false)
DownloadAction.PIN -> DownloadService.download(tracksToDownload, true)
DownloadAction.UNPIN -> DownloadService.unpin(tracksToDownload)
DownloadAction.DELETE -> DownloadService.delete(tracksToDownload)
}
successString = when (action) {
DownloadAction.DOWNLOAD -> fragment.resources.getQuantityString(
R.plurals.select_album_n_songs_downloaded,
tracksToDownload.size,
tracksToDownload.size
)
DownloadAction.UNPIN -> {
fragment.resources.getQuantityString(
R.plurals.select_album_n_songs_unpinned,
tracksToDownload.size,
tracksToDownload.size
)
}
DownloadAction.PIN -> {
fragment.resources.getQuantityString(
R.plurals.select_album_n_songs_pinned,
tracksToDownload.size,
tracksToDownload.size
)
}
DownloadAction.DELETE -> {
fragment.resources.getQuantityString(
R.plurals.select_album_n_songs_deleted,
tracksToDownload.size,
tracksToDownload.size
)
}
}
}
}) { successString }
}
fun fetchTracksAndAddToController(
fragment: Fragment,
id: String,
name: String? = "",
isShare: Boolean = false,
isDirectory: Boolean = true,
append: Boolean,
autoPlay: Boolean,
shuffle: Boolean,
playNext: Boolean,
isArtist: Boolean = false
) {
var successString: String? = null
// Launch the Job
executeTaskWithToast(fragment, {
val songs: MutableList<Track> =
getTracksFromServer(isArtist, id, isDirectory, name, isShare)
withContext(Dispatchers.Main) {
addTracksToMediaController(
songs,
background,
unpin,
append,
playNext,
save,
autoPlay,
shuffle,
fragment
songs = songs,
append = append,
playNext = playNext,
autoPlay = autoPlay,
shuffle = shuffle,
playlistName = null,
fragment = fragment
)
// Play Now doesn't get a Toast :)
if (playNext) {
successString = fragment.resources.getQuantityString(
R.plurals.select_album_n_songs_play_next,
songs.size,
songs.size
)
} else if (append) {
successString = fragment.resources.getQuantityString(
R.plurals.select_album_n_songs_added,
songs.size,
songs.size
)
}
}
}
// Create the dialog
val builder = InfoDialog.Builder(fragment.requireContext())
builder.setTitle(R.string.background_task_wait)
builder.setMessage(R.string.background_task_loading)
builder.setOnCancelListener { job.cancel() }
builder.setPositiveButton(R.string.common_cancel) { _, i -> job.cancel() }
val dialog = builder.create()
dialog.show()
job.invokeOnCompletion {
dialog.dismiss()
if (it != null && it !is CancellationException) {
Util.toast(
fragment.requireContext(),
CommunicationError.getErrorMessage(it, fragment.requireContext())
)
}
}
}) { successString }
}
private fun addTracksToMediaController(
songs: MutableList<Track>,
background: Boolean,
unpin: Boolean,
fun addTracksToMediaController(
songs: List<Track>,
append: Boolean,
playNext: Boolean,
save: Boolean,
autoPlay: Boolean,
shuffle: Boolean,
playlistName: String? = null,
fragment: Fragment
) {
if (songs.isEmpty()) return
if (Settings.shouldSortByDisc) {
Collections.sort(songs, EntryByDiscAndTrackComparator())
}
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
if (!background) {
if (unpin) {
DownloadService.unpin(songs)
} else {
val insertionMode = when {
append -> MediaPlayerController.InsertionMode.APPEND
playNext -> MediaPlayerController.InsertionMode.AFTER_CURRENT
else -> MediaPlayerController.InsertionMode.CLEAR
}
mediaPlayerController.addToPlaylist(
songs,
save,
autoPlay,
shuffle,
insertionMode
)
if (
!append &&
Settings.shouldTransitionOnPlayback
) {
fragment.findNavController().popBackStack(
R.id.playerFragment,
true
)
fragment.findNavController().navigate(R.id.playerFragment)
}
}
} else {
if (unpin) {
DownloadService.unpin(songs)
} else {
DownloadService.download(songs, save)
}
val insertionMode = when {
append -> MediaPlayerController.InsertionMode.APPEND
playNext -> MediaPlayerController.InsertionMode.AFTER_CURRENT
else -> MediaPlayerController.InsertionMode.CLEAR
}
if (playlistName != null) {
mediaPlayerController.suggestedPlaylistName = playlistName
}
mediaPlayerController.addToPlaylist(
songs,
autoPlay,
shuffle,
insertionMode
)
if (Settings.shouldTransitionOnPlayback && (!append || autoPlay)) {
fragment.findNavController().popBackStack(R.id.playerFragment, true)
fragment.findNavController().navigate(R.id.playerFragment)
}
}
@ -396,3 +252,7 @@ class DownloadHandler(
}
}
}
enum class DownloadAction {
DOWNLOAD, PIN, UNPIN, DELETE
}

View File

@ -0,0 +1,82 @@
/*
* CoroutinePatterns.kt
* Copyright (C) 2009-2023 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.util
import android.os.Handler
import android.os.Looper
import androidx.fragment.app.Fragment
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.moire.ultrasonic.R
import timber.log.Timber
object CoroutinePatterns {
val loggingExceptionHandler by lazy {
CoroutineExceptionHandler { _, exception ->
Handler(Looper.getMainLooper()).post {
Timber.w(exception)
}
}
}
}
fun CoroutineScope.executeTaskWithToast(
fragment: Fragment,
task: suspend CoroutineScope.() -> Unit,
successString: () -> String?
): Job {
// Launch the Job
val job = launch(CoroutinePatterns.loggingExceptionHandler, block = task)
// Setup a handler when the job is done
job.invokeOnCompletion {
val toastString = if (it != null && it !is CancellationException) {
CommunicationError.getErrorMessage(it, fragment.context)
} else {
successString()
}
// Return early if nothing to post
if (toastString == null) return@invokeOnCompletion
launch(Dispatchers.Main) {
Util.toast(fragment.context, toastString)
}
}
return job
}
fun CoroutineScope.executeTaskWithModalDialog(
fragment: Fragment,
task: suspend CoroutineScope.() -> Unit,
successString: () -> String
) {
// Create the job
val job = executeTaskWithToast(fragment, task, successString)
// Create the dialog
val builder = InfoDialog.Builder(fragment.requireContext())
builder.setTitle(R.string.background_task_wait)
builder.setMessage(R.string.background_task_loading)
builder.setOnCancelListener { job.cancel() }
builder.setPositiveButton(R.string.common_cancel) { _, _ -> job.cancel() }
val dialog = builder.create()
dialog.show()
// Add additional handler to close the dialog
job.invokeOnCompletion {
launch(Dispatchers.Main) {
dialog.dismiss()
}
}
}

View File

@ -148,8 +148,8 @@ object Util {
if (shortDuration) Toast.LENGTH_SHORT else Toast.LENGTH_LONG
}
toast!!.show()
} catch (_: Exception) {
// Ignore
} catch (all: Exception) {
Timber.w(all)
}
}