mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-15 17:00:36 +03:00
Use Coroutines for triggering the download or playback of music through the context menus
This commit is contained in:
parent
842cb36ecb
commit
cd982814cf
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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 -> {
|
||||
|
@ -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 -> {
|
||||
|
@ -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 -> {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user