mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 07:50:31 +05:30
refactor: simplify add to playlist dialog (#7074)
This commit is contained in:
parent
20db67d229
commit
2ae689c74d
@ -71,7 +71,9 @@ object PlaylistsHelper {
|
|||||||
playlistsRepository.createPlaylist(playlistName)
|
playlistsRepository.createPlaylist(playlistName)
|
||||||
|
|
||||||
suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem) =
|
suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem) =
|
||||||
playlistsRepository.addToPlaylist(playlistId, *videos)
|
withContext(Dispatchers.IO) {
|
||||||
|
playlistsRepository.addToPlaylist(playlistId, *videos)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun renamePlaylist(playlistId: String, newName: String) =
|
suspend fun renamePlaylist(playlistId: String, newName: String) =
|
||||||
playlistsRepository.renamePlaylist(playlistId, newName)
|
playlistsRepository.renamePlaylist(playlistId, newName)
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package com.github.libretube.api.obj
|
package com.github.libretube.api.obj
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@Parcelize
|
||||||
data class Playlists(
|
data class Playlists(
|
||||||
val id: String? = null,
|
val id: String? = null,
|
||||||
var name: String? = null,
|
var name: String? = null,
|
||||||
var shortDescription: String? = null,
|
var shortDescription: String? = null,
|
||||||
val thumbnail: String? = null,
|
val thumbnail: String? = null,
|
||||||
val videos: Long = 0
|
val videos: Long = 0
|
||||||
)
|
) : Parcelable
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.github.libretube.ui.dialogs
|
package com.github.libretube.ui.dialogs
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -8,34 +7,24 @@ import android.util.Log
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import androidx.fragment.app.setFragmentResult
|
import androidx.fragment.app.setFragmentResult
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.PlaylistsHelper
|
|
||||||
import com.github.libretube.api.obj.Playlists
|
|
||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.databinding.DialogAddToPlaylistBinding
|
import com.github.libretube.databinding.DialogAddToPlaylistBinding
|
||||||
import com.github.libretube.extensions.TAG
|
|
||||||
import com.github.libretube.extensions.parcelable
|
import com.github.libretube.extensions.parcelable
|
||||||
import com.github.libretube.extensions.toastFromMainDispatcher
|
|
||||||
import com.github.libretube.ui.models.PlaylistViewModel
|
import com.github.libretube.ui.models.PlaylistViewModel
|
||||||
import com.github.libretube.util.PlayingQueue
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog to insert new videos to a playlist
|
* Dialog to insert new videos to a playlist
|
||||||
* videoId: The id of the video to add. If non is provided, insert the whole playing queue
|
* videoId: The id of the video to add. If non is provided, insert the whole playing queue
|
||||||
*/
|
*/
|
||||||
class AddToPlaylistDialog : DialogFragment() {
|
class AddToPlaylistDialog : DialogFragment() {
|
||||||
private var videoInfo: StreamItem? = null
|
|
||||||
private val viewModel: PlaylistViewModel by activityViewModels()
|
|
||||||
|
|
||||||
var playlists = emptyList<Playlists>()
|
private var videoInfo: StreamItem? = null
|
||||||
|
private val viewModel: PlaylistViewModel by viewModels { PlaylistViewModel.Factory }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -44,19 +33,39 @@ class AddToPlaylistDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val binding = DialogAddToPlaylistBinding.inflate(layoutInflater)
|
|
||||||
|
|
||||||
childFragmentManager.setFragmentResultListener(
|
childFragmentManager.setFragmentResultListener(
|
||||||
CreatePlaylistDialog.CREATE_PLAYLIST_DIALOG_REQUEST_KEY,
|
CreatePlaylistDialog.CREATE_PLAYLIST_DIALOG_REQUEST_KEY,
|
||||||
this
|
this
|
||||||
) { _, resultBundle ->
|
) { _, resultBundle ->
|
||||||
val addedToPlaylist = resultBundle.getBoolean(IntentData.playlistTask)
|
val addedToPlaylist = resultBundle.getBoolean(IntentData.playlistTask)
|
||||||
if (addedToPlaylist) {
|
if (addedToPlaylist) {
|
||||||
fetchPlaylists(binding)
|
viewModel.fetchPlaylists()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchPlaylists(binding)
|
val binding = DialogAddToPlaylistBinding.inflate(layoutInflater)
|
||||||
|
viewModel.uiState.observe(this) { (lastSelectedPlaylistId, playlists, msg, saved) ->
|
||||||
|
binding.playlistsSpinner.items = playlists.mapNotNull { it.name }
|
||||||
|
|
||||||
|
// select the last used playlist
|
||||||
|
lastSelectedPlaylistId?.let { id ->
|
||||||
|
binding.playlistsSpinner.selectedItemPosition = playlists
|
||||||
|
.indexOfFirst { it.id == id }
|
||||||
|
.takeIf { it >= 0 } ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
msg?.let {
|
||||||
|
with(binding.root.context) {
|
||||||
|
Toast.makeText(this, getString(it.resId, it.formatArgs), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
viewModel.onMessageShown()
|
||||||
|
}
|
||||||
|
|
||||||
|
saved?.let {
|
||||||
|
dismiss()
|
||||||
|
viewModel.onDismissed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return MaterialAlertDialogBuilder(requireContext())
|
return MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(R.string.addToPlaylist)
|
.setTitle(R.string.addToPlaylist)
|
||||||
@ -65,71 +74,17 @@ class AddToPlaylistDialog : DialogFragment() {
|
|||||||
.setView(binding.root)
|
.setView(binding.root)
|
||||||
.show()
|
.show()
|
||||||
.apply {
|
.apply {
|
||||||
|
// Click listeners without closing the dialog
|
||||||
getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener {
|
getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener {
|
||||||
CreatePlaylistDialog().show(childFragmentManager, null)
|
CreatePlaylistDialog().show(childFragmentManager, null)
|
||||||
}
|
}
|
||||||
getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
|
getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
|
||||||
val playlistIndex = binding.playlistsSpinner.selectedItemPosition
|
val selectedItemPosition = binding.playlistsSpinner.selectedItemPosition
|
||||||
|
viewModel.onAddToPlaylist(selectedItemPosition)
|
||||||
val playlist = playlists.getOrElse(playlistIndex) { return@setOnClickListener }
|
|
||||||
viewModel.lastSelectedPlaylistId = playlist.id!!
|
|
||||||
|
|
||||||
dialog?.hide()
|
|
||||||
lifecycleScope.launch {
|
|
||||||
addToPlaylist(playlist.id, playlist.name!!)
|
|
||||||
dialog?.dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchPlaylists(binding: DialogAddToPlaylistBinding) {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
|
||||||
playlists = try {
|
|
||||||
PlaylistsHelper.getPlaylists()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG(), e.toString())
|
|
||||||
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
|
||||||
return@repeatOnLifecycle
|
|
||||||
}.filter { !it.name.isNullOrEmpty() }
|
|
||||||
|
|
||||||
binding.playlistsSpinner.items = playlists.map { it.name!! }
|
|
||||||
|
|
||||||
if (playlists.isEmpty()) return@repeatOnLifecycle
|
|
||||||
|
|
||||||
// select the last used playlist
|
|
||||||
viewModel.lastSelectedPlaylistId?.let { id ->
|
|
||||||
binding.playlistsSpinner.selectedItemPosition = playlists
|
|
||||||
.indexOfFirst { it.id == id }
|
|
||||||
.takeIf { it >= 0 } ?: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("StringFormatInvalid")
|
|
||||||
private suspend fun addToPlaylist(playlistId: String, playlistName: String) {
|
|
||||||
val appContext = context?.applicationContext ?: return
|
|
||||||
val streams = videoInfo?.let { listOf(it) } ?: PlayingQueue.getStreams()
|
|
||||||
|
|
||||||
val success = try {
|
|
||||||
if (streams.isEmpty()) throw IllegalArgumentException()
|
|
||||||
PlaylistsHelper.addToPlaylist(playlistId, *streams.toTypedArray())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG(), e.toString())
|
|
||||||
appContext.toastFromMainDispatcher(R.string.unknown_error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (success) {
|
|
||||||
appContext.toastFromMainDispatcher(
|
|
||||||
appContext.getString(R.string.added_to_playlist, playlistName)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
appContext.toastFromMainDispatcher(R.string.fail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDismiss(dialog: DialogInterface) {
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
super.onDismiss(dialog)
|
super.onDismiss(dialog)
|
||||||
|
|
||||||
|
@ -1,7 +1,109 @@
|
|||||||
package com.github.libretube.ui.models
|
package com.github.libretube.ui.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
|
import androidx.lifecycle.createSavedStateHandle
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.api.PlaylistsHelper
|
||||||
|
import com.github.libretube.api.obj.Playlists
|
||||||
|
import com.github.libretube.api.obj.StreamItem
|
||||||
|
import com.github.libretube.constants.IntentData
|
||||||
|
import com.github.libretube.util.PlayingQueue
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import kotlinx.parcelize.RawValue
|
||||||
|
|
||||||
class PlaylistViewModel : ViewModel() {
|
class PlaylistViewModel(
|
||||||
var lastSelectedPlaylistId: String? = null
|
private val savedStateHandle: SavedStateHandle,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _uiState = savedStateHandle.getStateFlow(UI_STATE, UiState())
|
||||||
|
val uiState = _uiState.asLiveData()
|
||||||
|
|
||||||
|
init {
|
||||||
|
fetchPlaylists()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchPlaylists() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
kotlin.runCatching {
|
||||||
|
PlaylistsHelper.getPlaylists()
|
||||||
|
}.onSuccess { playlists ->
|
||||||
|
savedStateHandle[UI_STATE] = _uiState.value.copy(
|
||||||
|
playlists = playlists.filterNot { list -> list.name.isNullOrEmpty() }
|
||||||
|
)
|
||||||
|
}.onFailure {
|
||||||
|
savedStateHandle[UI_STATE] = _uiState.value.copy(
|
||||||
|
message = UiState.Message(R.string.unknown_error)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAddToPlaylist(playlistIndex: Int) {
|
||||||
|
val playlist = _uiState.value.playlists.getOrElse(playlistIndex) { return }
|
||||||
|
savedStateHandle[UI_STATE] = _uiState.value.copy(lastSelectedPlaylistId = playlist.id)
|
||||||
|
|
||||||
|
val videoInfo = savedStateHandle.get<StreamItem>(IntentData.videoInfo)
|
||||||
|
val streams = videoInfo?.let { listOf(it) } ?: PlayingQueue.getStreams()
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
kotlin.runCatching {
|
||||||
|
if (streams.isEmpty()) {
|
||||||
|
throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
PlaylistsHelper.addToPlaylist(playlist.id!!, *streams.toTypedArray())
|
||||||
|
}.onSuccess {
|
||||||
|
savedStateHandle[UI_STATE] = _uiState.value.copy(
|
||||||
|
message = UiState.Message(R.string.added_to_playlist, listOf(playlist.name!!)),
|
||||||
|
saved = Unit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.onFailure {
|
||||||
|
savedStateHandle[UI_STATE] = _uiState.value.copy(
|
||||||
|
message = UiState.Message(R.string.unknown_error)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMessageShown() {
|
||||||
|
savedStateHandle[UI_STATE] = _uiState.value.copy(message = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDismissed() {
|
||||||
|
savedStateHandle[UI_STATE] = _uiState.value.copy(saved = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class UiState(
|
||||||
|
val lastSelectedPlaylistId: String? = null,
|
||||||
|
val playlists: List<Playlists> = emptyList(),
|
||||||
|
val message: Message? = null,
|
||||||
|
val saved: Unit? = null,
|
||||||
|
) : Parcelable {
|
||||||
|
@Parcelize
|
||||||
|
data class Message(
|
||||||
|
@StringRes val resId: Int,
|
||||||
|
val formatArgs: List<@RawValue Any>? = null,
|
||||||
|
) : Parcelable
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val UI_STATE = "ui_state"
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
PlaylistViewModel(
|
||||||
|
savedStateHandle = createSavedStateHandle(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user