Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for changing the playlist description #3981

Merged
merged 3 commits into from
Jun 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
502 changes: 502 additions & 0 deletions app/schemas/com.github.libretube.db.AppDatabase/12.json

Large diffs are not rendered by default.

23 changes: 15 additions & 8 deletions app/src/main/java/com/github/libretube/api/PipedApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import com.github.libretube.api.obj.Login
import com.github.libretube.api.obj.Message
import com.github.libretube.api.obj.PipedConfig
import com.github.libretube.api.obj.Playlist
import com.github.libretube.api.obj.PlaylistId
import com.github.libretube.api.obj.EditPlaylistBody
import com.github.libretube.api.obj.Playlists
import com.github.libretube.api.obj.SearchResult
import com.github.libretube.api.obj.SegmentData
Expand All @@ -21,6 +21,7 @@ import com.github.libretube.api.obj.Token
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
Expand Down Expand Up @@ -159,39 +160,45 @@ interface PipedApi {
@POST("import/playlist")
suspend fun clonePlaylist(
@Header("Authorization") token: String,
@Body playlistId: PlaylistId,
): PlaylistId
@Body editPlaylistBody: EditPlaylistBody,
): EditPlaylistBody

@GET("user/playlists")
suspend fun getUserPlaylists(@Header("Authorization") token: String): List<Playlists>

@POST("user/playlists/rename")
suspend fun renamePlaylist(
@Header("Authorization") token: String,
@Body playlistId: PlaylistId,
@Body editPlaylistBody: EditPlaylistBody,
): Message

@PATCH("user/playlists/description")
suspend fun changePlaylistDescription(
@Header("Authorization") token: String,
@Body editPlaylistBody: EditPlaylistBody,
): Message

@POST("user/playlists/delete")
suspend fun deletePlaylist(
@Header("Authorization") token: String,
@Body playlistId: PlaylistId,
@Body editPlaylistBody: EditPlaylistBody,
): Message

@POST("user/playlists/create")
suspend fun createPlaylist(
@Header("Authorization") token: String,
@Body name: Playlists,
): PlaylistId
): EditPlaylistBody

@POST("user/playlists/add")
suspend fun addToPlaylist(
@Header("Authorization") token: String,
@Body playlistId: PlaylistId,
@Body editPlaylistBody: EditPlaylistBody,
): Message

@POST("user/playlists/remove")
suspend fun removeFromPlaylist(
@Header("Authorization") token: String,
@Body playlistId: PlaylistId,
@Body editPlaylistBody: EditPlaylistBody,
): Message
}
38 changes: 28 additions & 10 deletions app/src/main/java/com/github/libretube/api/PlaylistsHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package com.github.libretube.api

import androidx.core.text.isDigitsOnly
import com.github.libretube.api.obj.Playlist
import com.github.libretube.api.obj.PlaylistId
import com.github.libretube.api.obj.EditPlaylistBody
import com.github.libretube.api.obj.Message
import com.github.libretube.api.obj.Playlists
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.constants.YOUTUBE_FRONTEND_URL
Expand All @@ -28,6 +29,8 @@ object PlaylistsHelper {

val loggedIn: Boolean get() = token.isNotEmpty()

private fun Message.isOk() = this.message == "ok"

suspend fun getPlaylists(): List<Playlists> = withContext(Dispatchers.IO) {
if (loggedIn) {
RetrofitInstance.authApi.getUserPlaylists(token)
Expand All @@ -37,6 +40,7 @@ object PlaylistsHelper {
Playlists(
id = it.playlist.id.toString(),
name = it.playlist.name,
shortDescription = it.playlist.description,
thumbnail = ProxyHelper.rewriteUrl(it.playlist.thumbnailUrl),
videos = it.videos.size.toLong(),
)
Expand All @@ -54,6 +58,7 @@ object PlaylistsHelper {
.first { it.playlist.id.toString() == playlistId }
return Playlist(
name = relation.playlist.name,
description = relation.playlist.description,
thumbnailUrl = ProxyHelper.rewriteUrl(relation.playlist.thumbnailUrl),
videos = relation.videos.size,
relatedStreams = relation.videos.map { it.toStreamItem() },
Expand Down Expand Up @@ -97,8 +102,8 @@ object PlaylistsHelper {
return true
}

val playlist = PlaylistId(playlistId, videoIds = videos.map { it.url!!.toID() })
return RetrofitInstance.authApi.addToPlaylist(token, playlist).message == "ok"
val playlist = EditPlaylistBody(playlistId, videoIds = videos.map { it.url!!.toID() })
return RetrofitInstance.authApi.addToPlaylist(token, playlist).isOk()
}

suspend fun renamePlaylist(playlistId: String, newName: String): Boolean {
Expand All @@ -109,8 +114,21 @@ object PlaylistsHelper {
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)
true
} else {
val playlist = PlaylistId(playlistId, newName = newName)
RetrofitInstance.authApi.renamePlaylist(token, playlist).message == "ok"
val playlist = EditPlaylistBody(playlistId, newName = newName)
RetrofitInstance.authApi.renamePlaylist(token, playlist).isOk()
}
}

suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean {
return if (!loggedIn) {
val playlist = DatabaseHolder.Database.localPlaylistsDao().getAll()
.first { it.playlist.id.toString() == playlistId }.playlist
playlist.description = newDescription
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)
true
} else {
val playlist = EditPlaylistBody(playlistId, description = newDescription)
RetrofitInstance.authApi.changePlaylistDescription(token, playlist).isOk()
}
}

Expand All @@ -130,8 +148,8 @@ object PlaylistsHelper {
} else {
RetrofitInstance.authApi.removeFromPlaylist(
PreferenceHelper.getToken(),
PlaylistId(playlistId = playlistId, index = index),
).message == "ok"
EditPlaylistBody(playlistId = playlistId, index = index),
).isOk()
}
}

Expand Down Expand Up @@ -214,7 +232,7 @@ object PlaylistsHelper {
return playlistId
}

return RetrofitInstance.authApi.clonePlaylist(token, PlaylistId(playlistId)).playlistId
return RetrofitInstance.authApi.clonePlaylist(token, EditPlaylistBody(playlistId)).playlistId
}

suspend fun deletePlaylist(playlistId: String, playlistType: PlaylistType): Boolean {
Expand All @@ -227,8 +245,8 @@ object PlaylistsHelper {
return runCatching {
RetrofitInstance.authApi.deletePlaylist(
PreferenceHelper.getToken(),
PlaylistId(playlistId),
).message == "ok"
EditPlaylistBody(playlistId),
).isOk()
}.getOrDefault(false)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package com.github.libretube.api.obj
import kotlinx.serialization.Serializable

@Serializable
data class PlaylistId(
data class EditPlaylistBody(
val playlistId: String? = null,
val videoId: String? = null,
val videoIds: List<String> = emptyList(),
val newName: String? = null,
val description: String? = null,
val index: Int = -1,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable
data class Playlists(
val id: String? = null,
var name: String? = null,
val shortDescription: String? = null,
var shortDescription: String? = null,
val thumbnail: String? = null,
val videos: Long = 0,
)
2 changes: 1 addition & 1 deletion app/src/main/java/com/github/libretube/db/AppDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import com.github.libretube.db.obj.WatchPosition
DownloadItem::class,
SubscriptionGroup::class,
],
version = 11,
version = 12,
autoMigrations = [
AutoMigration(from = 7, to = 8),
AutoMigration(from = 8, to = 9),
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/com/github/libretube/db/DatabaseHolder.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package com.github.libretube.db

import androidx.room.Room
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.github.libretube.LibreTubeApp
import com.github.libretube.constants.DATABASE_NAME

object DatabaseHolder {
private val MIGRATION_11_12 = object : Migration(11, 12) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE 'localPlaylist' ADD COLUMN 'description' TEXT DEFAULT NULL")
}
}

val Database by lazy {
Room.databaseBuilder(LibreTubeApp.instance, AppDatabase::class.java, DATABASE_NAME)
.addMigrations(MIGRATION_11_12)
.fallbackToDestructiveMigration()
.build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ data class LocalPlaylist(
val id: Int = 0,
var name: String = "",
var thumbnailUrl: String = "",
var description: String? = ""
)
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ class PlaylistsAdapter(
playlistTitle.text = it
playlist.name = it
},
onChangeDescription = {
playlistDescription.text = it
playlist.shortDescription = it
}
)
playlistOptionsDialog.show(
(root.context as BaseActivity).supportFragmentManager,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.github.libretube.ui.dialogs

import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.text.InputType
import android.util.Log
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.databinding.DialogTextPreferenceBinding
import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.toastFromMainDispatcher
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class PlaylistDescriptionDialog(
private val playlistId: String,
private val currentPlaylistDescription: String,
private val onSuccess: (String) -> Unit,
) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogTextPreferenceBinding.inflate(layoutInflater)
binding.input.inputType = InputType.TYPE_CLASS_TEXT
binding.input.hint = getString(R.string.playlist_description)
binding.input.setText(currentPlaylistDescription)

return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.change_playlist_description)
.setView(binding.root)
.setPositiveButton(R.string.okay, null)
.setNegativeButton(R.string.cancel, null)
.show()
.apply {
getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
val newDescription = binding.input.text?.toString()
if (newDescription.isNullOrEmpty()) {
Toast.makeText(
context, R.string.emptyPlaylistDescription,
Toast.LENGTH_SHORT
).show()
return@setOnClickListener
}
if (newDescription == currentPlaylistDescription) {
dismiss()
return@setOnClickListener
}
val appContext = requireContext().applicationContext

lifecycleScope.launch {
requireDialog().hide()
val success = try {
withContext(Dispatchers.IO) {
PlaylistsHelper.changePlaylistDescription(playlistId, newDescription)
}
} catch (e: Exception) {
Log.e(TAG(), e.toString())
e.localizedMessage?.let { appContext.toastFromMainDispatcher(it) }
return@launch
}
if (success) {
appContext.toastFromMainDispatcher(R.string.success)
onSuccess.invoke(newDescription)
} else {
appContext.toastFromMainDispatcher(R.string.server_error)
}
dismiss()
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ class PlaylistFragment : Fragment() {
binding.playlistName.text = it
playlistName = it
},
onChangeDescription = {
binding.playlistDescription.text = it
}
).show(
childFragmentManager,
PlaylistOptionsBottomSheet::class.java.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.github.libretube.extensions.toastFromMainDispatcher
import com.github.libretube.helpers.BackgroundHelper
import com.github.libretube.obj.ShareData
import com.github.libretube.ui.dialogs.DeletePlaylistDialog
import com.github.libretube.ui.dialogs.PlaylistDescriptionDialog
import com.github.libretube.ui.dialogs.RenamePlaylistDialog
import com.github.libretube.ui.dialogs.ShareDialog
import kotlinx.coroutines.Dispatchers
Expand All @@ -23,6 +24,7 @@ class PlaylistOptionsBottomSheet(
private val playlistName: String,
private val playlistType: PlaylistType,
private val onRename: (newName: String) -> Unit = {},
private val onChangeDescription: (newDescription: String) -> Unit = {},
private val onDelete: () -> Unit = {},
) : BaseBottomSheet() {
private val shareData = ShareData(currentPlaylist = playlistName)
Expand All @@ -45,8 +47,9 @@ class PlaylistOptionsBottomSheet(
getString(if (isBookmarked) R.string.remove_bookmark else R.string.add_to_bookmarks),
)
} else {
optionsList.add(context?.getString(R.string.renamePlaylist)!!)
optionsList.add(context?.getString(R.string.deletePlaylist)!!)
optionsList.add(getString(R.string.renamePlaylist))
optionsList.add(getString(R.string.change_playlist_description))
optionsList.add(getString(R.string.deletePlaylist))
}

setSimpleItems(optionsList) { which ->
Expand Down Expand Up @@ -92,6 +95,10 @@ class PlaylistOptionsBottomSheet(
RenamePlaylistDialog(playlistId, playlistName, onRename)
.show(parentFragmentManager, null)
}
getString(R.string.change_playlist_description) -> {
PlaylistDescriptionDialog(playlistId, "", onChangeDescription)
.show(parentFragmentManager, null)
}
else -> {
withContext(Dispatchers.IO) {
if (isBookmarked) {
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,9 @@
<string name="choose_instance">Please choose an instance first!</string>
<string name="choose_instance_long">Please choose a Piped instance to use from below. The Piped instance will act as the middleman between you and YouTube. These instances are located at different physical locations - indicated by their country flag(s). Instances physically closer to you are likely faster than instances that are not. You can change the instance you use in the settings at any time.</string>
<string name="mark_as_unwatched">Mark as unwatched</string>
<string name="change_playlist_description">Change playlist description</string>
<string name="playlist_description">Playlist description</string>
<string name="emptyPlaylistDescription">The playlist description can\'t be empty</string>

<!-- Backup & Restore Settings -->
<string name="import_subscriptions_from">Import subscriptions from</string>
Expand Down