add frontend support

This commit is contained in:
Bnyro 2022-11-20 15:54:55 +01:00
parent ea1d2765c9
commit 0d65071c79
26 changed files with 635 additions and 236 deletions

View File

@ -0,0 +1,330 @@
{
"formatVersion": 1,
"database": {
"version": 9,
"identityHash": "8c1e428cb526415347639e49f7757f76",
"entities": [
{
"tableName": "watchHistoryItem",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `title` TEXT, `uploadDate` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, `thumbnailUrl` TEXT, `duration` INTEGER, PRIMARY KEY(`videoId`))",
"fields": [
{
"fieldPath": "videoId",
"columnName": "videoId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploadDate",
"columnName": "uploadDate",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploaderUrl",
"columnName": "uploaderUrl",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploaderAvatar",
"columnName": "uploaderAvatar",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnailUrl",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"videoId"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "watchPosition",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `position` INTEGER NOT NULL, PRIMARY KEY(`videoId`))",
"fields": [
{
"fieldPath": "videoId",
"columnName": "videoId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "position",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"videoId"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "searchHistoryItem",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`query` TEXT NOT NULL, PRIMARY KEY(`query`))",
"fields": [
{
"fieldPath": "query",
"columnName": "query",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"query"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "customInstance",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `apiUrl` TEXT NOT NULL, `frontendUrl` TEXT NOT NULL, PRIMARY KEY(`name`))",
"fields": [
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "apiUrl",
"columnName": "apiUrl",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "frontendUrl",
"columnName": "frontendUrl",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"name"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "localSubscription",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`channelId` TEXT NOT NULL, PRIMARY KEY(`channelId`))",
"fields": [
{
"fieldPath": "channelId",
"columnName": "channelId",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"channelId"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "playlistBookmark",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlistId` TEXT NOT NULL, `playlistName` TEXT, `thumbnailUrl` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, PRIMARY KEY(`playlistId`))",
"fields": [
{
"fieldPath": "playlistId",
"columnName": "playlistId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "playlistName",
"columnName": "playlistName",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnailUrl",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploaderUrl",
"columnName": "uploaderUrl",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploaderAvatar",
"columnName": "uploaderAvatar",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"playlistId"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LocalPlaylist",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `thumbnailUrl` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnailUrl",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "LocalPlaylistItem",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `playlistId` INTEGER NOT NULL, `videoId` TEXT NOT NULL, `title` TEXT, `uploadDate` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, `thumbnailUrl` TEXT, `duration` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "playlistId",
"columnName": "playlistId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "videoId",
"columnName": "videoId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploadDate",
"columnName": "uploadDate",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploader",
"columnName": "uploader",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploaderUrl",
"columnName": "uploaderUrl",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uploaderAvatar",
"columnName": "uploaderAvatar",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "thumbnailUrl",
"columnName": "thumbnailUrl",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8c1e428cb526415347639e49f7757f76')"
]
}
}

View File

@ -3,7 +3,6 @@ package com.github.libretube
import android.app.Application import android.app.Application
import android.os.StrictMode import android.os.StrictMode
import android.os.StrictMode.VmPolicy import android.os.StrictMode.VmPolicy
import android.util.Log
import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
@ -13,10 +12,6 @@ import com.github.libretube.constants.BACKGROUND_CHANNEL_ID
import com.github.libretube.constants.DOWNLOAD_CHANNEL_ID import com.github.libretube.constants.DOWNLOAD_CHANNEL_ID
import com.github.libretube.constants.PUSH_CHANNEL_ID import com.github.libretube.constants.PUSH_CHANNEL_ID
import com.github.libretube.db.DatabaseHolder import com.github.libretube.db.DatabaseHolder
import com.github.libretube.db.obj.LocalPlaylist
import com.github.libretube.db.obj.LocalPlaylistItem
import com.github.libretube.extensions.awaitQuery
import com.github.libretube.extensions.query
import com.github.libretube.util.ExceptionHandler import com.github.libretube.util.ExceptionHandler
import com.github.libretube.util.ImageHelper import com.github.libretube.util.ImageHelper
import com.github.libretube.util.NotificationHelper import com.github.libretube.util.NotificationHelper
@ -41,8 +36,6 @@ class LibreTubeApp : Application() {
*/ */
DatabaseHolder().initializeDatabase(this) DatabaseHolder().initializeDatabase(this)
runDatabaseTests()
/** /**
* Bypassing fileUriExposedException, see https://stackoverflow.com/questions/38200282/android-os-fileuriexposedexception-file-storage-emulated-0-test-txt-exposed * Bypassing fileUriExposedException, see https://stackoverflow.com/questions/38200282/android-os-fileuriexposedexception-file-storage-emulated-0-test-txt-exposed
*/ */
@ -107,23 +100,4 @@ class LibreTubeApp : Application() {
) )
) )
} }
private fun runDatabaseTests() {
awaitQuery {
val playlist = LocalPlaylist(
name = "TEstlist",
thumbnailUrl = "thumb"
)
DatabaseHolder.Database.localPlaylistsDao().createPlaylist(playlist)
val playlistId = DatabaseHolder.Database.localPlaylistsDao().getAll().first().playlist.id
val video = LocalPlaylistItem(
videoId = "video",
playlistId = playlistId,
title = "awesomePlaylistTitle"
)
DatabaseHolder.Database.localPlaylistsDao().addPlaylistVideo(video)
val lists = DatabaseHolder.Database.localPlaylistsDao().getAll()
Log.e("lists", lists.toString())
}
}
} }

View File

@ -0,0 +1,127 @@
package com.github.libretube.api
import android.content.Context
import android.util.Log
import com.github.libretube.R
import com.github.libretube.api.obj.Playlist
import com.github.libretube.api.obj.PlaylistId
import com.github.libretube.api.obj.Playlists
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.db.obj.LocalPlaylist
import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.awaitQuery
import com.github.libretube.extensions.toLocalPlaylistItem
import com.github.libretube.extensions.toStreamItem
import com.github.libretube.extensions.toastFromMainThread
import com.github.libretube.util.PreferenceHelper
import retrofit2.HttpException
import java.io.IOException
object PlaylistsHelper {
val token get() = PreferenceHelper.getToken()
suspend fun getPlaylists(): List<Playlists> {
if (token != "") return RetrofitInstance.authApi.getUserPlaylists(token)
val localPlaylists = awaitQuery {
DatabaseHolder.Database.localPlaylistsDao().getAll()
}
val playlists = mutableListOf<Playlists>()
localPlaylists.forEach {
playlists.add(
Playlists(
id = it.playlist.id.toString(),
name = it.playlist.name,
thumbnail = it.playlist.thumbnailUrl,
videos = it.videos.size.toLong()
)
)
}
return playlists
}
suspend fun getPlaylist(playlistType: PlaylistType, playlistId: String): Playlist {
// load locally stored playlists with the auth api
return when (playlistType) {
PlaylistType.OWNED -> RetrofitInstance.authApi.getPlaylist(playlistId)
PlaylistType.PUBLIC -> RetrofitInstance.api.getPlaylist(playlistId)
PlaylistType.LOCAL -> {
val relation = awaitQuery {
DatabaseHolder.Database.localPlaylistsDao().getAll()
}.first { it.playlist.id.toString() == playlistId }
return Playlist(
name = relation.playlist.name,
thumbnailUrl = relation.playlist.thumbnailUrl,
videos = relation.videos.size,
relatedStreams = relation.videos.map { it.toStreamItem() }
)
}
}
}
suspend fun createPlaylist(playlistName: String, appContext: Context, onSuccess: () -> Unit) {
if (token == "") {
awaitQuery {
DatabaseHolder.Database.localPlaylistsDao().createPlaylist(
LocalPlaylist(
name = playlistName,
thumbnailUrl = ""
)
)
}
onSuccess.invoke()
return
}
val response = try {
RetrofitInstance.authApi.createPlaylist(
token,
Playlists(name = playlistName)
)
} catch (e: IOException) {
appContext.toastFromMainThread(R.string.unknown_error)
return
} catch (e: HttpException) {
Log.e(TAG(), e.toString())
appContext.toastFromMainThread(R.string.server_error)
return
}
if (response.playlistId != null) {
appContext.toastFromMainThread(R.string.playlistCreated)
onSuccess.invoke()
} else {
appContext.toastFromMainThread(R.string.unknown_error)
}
}
suspend fun addToPlaylist(playlistId: String, videoId: String): Boolean {
if (token == "") {
val localPlaylistItem = RetrofitInstance.api.getStreams(videoId).toLocalPlaylistItem(playlistId, videoId)
awaitQuery {
DatabaseHolder.Database.localPlaylistsDao().addPlaylistVideo(localPlaylistItem)
val localPlaylist = DatabaseHolder.Database.localPlaylistsDao().getAll()
.first { it.playlist.id.toString() == playlistId }.playlist
if (localPlaylist.thumbnailUrl == "") {
localPlaylistItem.thumbnailUrl?.let {
localPlaylist.thumbnailUrl = it
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(localPlaylist)
}
}
}
return true
}
return RetrofitInstance.authApi.addToPlaylist(
token,
PlaylistId(playlistId, videoId)
).message == "ok"
}
fun getType(): PlaylistType {
return if (PreferenceHelper.getToken() != "") {
PlaylistType.PUBLIC
} else {
PlaylistType.LOCAL
}
}
}

View File

@ -9,4 +9,5 @@ object IntentData {
const val position = "position" const val position = "position"
const val fileName = "fileName" const val fileName = "fileName"
const val openQueueOnce = "openQueue" const val openQueueOnce = "openQueue"
const val playlistType = "playlistType"
} }

View File

@ -4,16 +4,16 @@ import androidx.room.AutoMigration
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.github.libretube.db.dao.CustomInstanceDao import com.github.libretube.db.dao.CustomInstanceDao
import com.github.libretube.db.dao.LocalSubscriptionDao
import com.github.libretube.db.dao.LocalPlaylistsDao import com.github.libretube.db.dao.LocalPlaylistsDao
import com.github.libretube.db.dao.LocalSubscriptionDao
import com.github.libretube.db.dao.PlaylistBookmarkDao import com.github.libretube.db.dao.PlaylistBookmarkDao
import com.github.libretube.db.dao.SearchHistoryDao import com.github.libretube.db.dao.SearchHistoryDao
import com.github.libretube.db.dao.WatchHistoryDao import com.github.libretube.db.dao.WatchHistoryDao
import com.github.libretube.db.dao.WatchPositionDao import com.github.libretube.db.dao.WatchPositionDao
import com.github.libretube.db.obj.CustomInstance import com.github.libretube.db.obj.CustomInstance
import com.github.libretube.db.obj.LocalSubscription
import com.github.libretube.db.obj.LocalPlaylist import com.github.libretube.db.obj.LocalPlaylist
import com.github.libretube.db.obj.LocalPlaylistItem import com.github.libretube.db.obj.LocalPlaylistItem
import com.github.libretube.db.obj.LocalSubscription
import com.github.libretube.db.obj.PlaylistBookmark import com.github.libretube.db.obj.PlaylistBookmark
import com.github.libretube.db.obj.SearchHistoryItem import com.github.libretube.db.obj.SearchHistoryItem
import com.github.libretube.db.obj.WatchHistoryItem import com.github.libretube.db.obj.WatchHistoryItem

View File

@ -25,9 +25,15 @@ interface LocalPlaylistsDao {
@Delete @Delete
fun deletePlaylist(playlist: LocalPlaylist) fun deletePlaylist(playlist: LocalPlaylist)
@Query("DELETE FROM localPlaylist WHERE id = :playlistId")
fun deletePlaylistById(playlistId: String)
@Insert @Insert
fun addPlaylistVideo(playlistVideo: LocalPlaylistItem) fun addPlaylistVideo(playlistVideo: LocalPlaylistItem)
@Delete @Delete
fun removePlaylistVideo(playlistVideo: LocalPlaylistItem) fun removePlaylistVideo(playlistVideo: LocalPlaylistItem)
@Query("DELETE FROM localPlaylistItem WHERE playlistId = :playlistId")
fun deletePlaylistItemsByPlaylistId(playlistId: String)
} }

View File

@ -8,5 +8,5 @@ data class LocalPlaylist(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val id: Int = 0, val id: Int = 0,
val name: String, val name: String,
val thumbnailUrl: String var thumbnailUrl: String
) )

View File

@ -0,0 +1,7 @@
package com.github.libretube.enums
enum class PlaylistType {
LOCAL,
OWNED,
PUBLIC
}

View File

@ -0,0 +1,10 @@
package com.github.libretube.ui.extensions
import android.os.Build
import android.os.Bundle
import java.io.Serializable
inline fun <reified T : Serializable> Bundle.serializable(key: String): T? = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializable(key, T::class.java)
else -> @Suppress("DEPRECATION") getSerializable(key) as? T
}

View File

@ -0,0 +1,18 @@
package com.github.libretube.extensions
import com.github.libretube.api.obj.Streams
import com.github.libretube.db.obj.LocalPlaylistItem
fun Streams.toLocalPlaylistItem(playlistId: String, videoId: String): LocalPlaylistItem {
return LocalPlaylistItem(
playlistId = playlistId.toInt(),
videoId = videoId,
title = title,
thumbnailUrl = thumbnailUrl,
uploader = uploader,
uploaderUrl = uploaderUrl,
uploaderAvatar = uploaderAvatar,
uploadDate = uploadDate,
duration = duration
)
}

View File

@ -2,6 +2,7 @@ package com.github.libretube.extensions
import com.github.libretube.api.obj.StreamItem import com.github.libretube.api.obj.StreamItem
import com.github.libretube.api.obj.Streams import com.github.libretube.api.obj.Streams
import com.github.libretube.db.obj.LocalPlaylistItem
fun Streams.toStreamItem(videoId: String): StreamItem { fun Streams.toStreamItem(videoId: String): StreamItem {
return StreamItem( return StreamItem(
@ -19,3 +20,17 @@ fun Streams.toStreamItem(videoId: String): StreamItem {
shortDescription = description shortDescription = description
) )
} }
fun LocalPlaylistItem.toStreamItem(): StreamItem {
return StreamItem(
url = videoId,
title = title,
thumbnail = thumbnailUrl,
uploaderName = uploader,
uploaderUrl = uploaderUrl,
uploaderAvatar = uploaderAvatar,
uploadedDate = uploadDate,
uploaded = null,
duration = duration
)
}

View File

@ -5,16 +5,6 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.widget.Toast import android.widget.Toast
fun Context.toastFromMainThread(stringId: Int) {
Handler(Looper.getMainLooper()).post {
Toast.makeText(
this,
stringId,
Toast.LENGTH_SHORT
).show()
}
}
fun Context.toastFromMainThread(text: String) { fun Context.toastFromMainThread(text: String) {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
Toast.makeText( Toast.makeText(
@ -24,3 +14,7 @@ fun Context.toastFromMainThread(text: String) {
).show() ).show()
} }
} }
fun Context.toastFromMainThread(stringId: Int) {
toastFromMainThread(getString(stringId))
}

View File

@ -11,6 +11,7 @@ import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.PlaylistId import com.github.libretube.api.obj.PlaylistId
import com.github.libretube.api.obj.StreamItem import com.github.libretube.api.obj.StreamItem
import com.github.libretube.databinding.PlaylistRowBinding import com.github.libretube.databinding.PlaylistRowBinding
import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.ui.base.BaseActivity import com.github.libretube.ui.base.BaseActivity
@ -30,7 +31,7 @@ import java.io.IOException
class PlaylistAdapter( class PlaylistAdapter(
private val videoFeed: MutableList<StreamItem>, private val videoFeed: MutableList<StreamItem>,
private val playlistId: String, private val playlistId: String,
private val isOwner: Boolean private val playlistType: PlaylistType
) : RecyclerView.Adapter<PlaylistViewHolder>() { ) : RecyclerView.Adapter<PlaylistViewHolder>() {
override fun getItemCount(): Int { override fun getItemCount(): Int {
@ -70,7 +71,7 @@ class PlaylistAdapter(
true true
} }
if (isOwner) { if (playlistType != PlaylistType.PUBLIC) {
deletePlaylist.visibility = View.VISIBLE deletePlaylist.visibility = View.VISIBLE
deletePlaylist.setOnClickListener { deletePlaylist.setOnClickListener {
removeFromPlaylist(root.context, position) removeFromPlaylist(root.context, position)

View File

@ -6,6 +6,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.databinding.PlaylistBookmarkRowBinding import com.github.libretube.databinding.PlaylistBookmarkRowBinding
import com.github.libretube.db.obj.PlaylistBookmark import com.github.libretube.db.obj.PlaylistBookmark
import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.toDp import com.github.libretube.extensions.toDp
import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet
import com.github.libretube.ui.viewholders.PlaylistBookmarkViewHolder import com.github.libretube.ui.viewholders.PlaylistBookmarkViewHolder
@ -39,14 +40,14 @@ class PlaylistBookmarkAdapter(
uploaderName.text = bookmark.uploader uploaderName.text = bookmark.uploader
root.setOnClickListener { root.setOnClickListener {
NavigationHelper.navigatePlaylist(root.context, bookmark.playlistId, false) NavigationHelper.navigatePlaylist(root.context, bookmark.playlistId, PlaylistType.PUBLIC)
} }
root.setOnLongClickListener { root.setOnLongClickListener {
PlaylistOptionsBottomSheet( PlaylistOptionsBottomSheet(
playlistId = bookmark.playlistId, playlistId = bookmark.playlistId,
playlistName = bookmark.playlistName ?: "", playlistName = bookmark.playlistName ?: "",
isOwner = false playlistType = PlaylistType.PUBLIC
).show( ).show(
(root.context as AppCompatActivity).supportFragmentManager (root.context as AppCompatActivity).supportFragmentManager
) )

View File

@ -6,6 +6,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.obj.Playlists import com.github.libretube.api.obj.Playlists
import com.github.libretube.databinding.PlaylistsRowBinding import com.github.libretube.databinding.PlaylistsRowBinding
import com.github.libretube.enums.PlaylistType
import com.github.libretube.ui.base.BaseActivity import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.dialogs.DeletePlaylistDialog import com.github.libretube.ui.dialogs.DeletePlaylistDialog
import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet
@ -14,7 +15,8 @@ import com.github.libretube.util.ImageHelper
import com.github.libretube.util.NavigationHelper import com.github.libretube.util.NavigationHelper
class PlaylistsAdapter( class PlaylistsAdapter(
private val playlists: MutableList<Playlists> private val playlists: MutableList<Playlists>,
private val playlistType: PlaylistType
) : RecyclerView.Adapter<PlaylistsViewHolder>() { ) : RecyclerView.Adapter<PlaylistsViewHolder>() {
override fun getItemCount(): Int { override fun getItemCount(): Int {
@ -48,7 +50,7 @@ class PlaylistsAdapter(
videoCount.text = playlist.videos.toString() videoCount.text = playlist.videos.toString()
deletePlaylist.setOnClickListener { deletePlaylist.setOnClickListener {
DeletePlaylistDialog(playlist.id!!) { DeletePlaylistDialog(playlist.id!!, playlistType) {
playlists.removeAt(position) playlists.removeAt(position)
(root.context as BaseActivity).runOnUiThread { (root.context as BaseActivity).runOnUiThread {
notifyItemRemoved(position) notifyItemRemoved(position)
@ -60,14 +62,14 @@ class PlaylistsAdapter(
) )
} }
root.setOnClickListener { root.setOnClickListener {
NavigationHelper.navigatePlaylist(root.context, playlist.id, true) NavigationHelper.navigatePlaylist(root.context, playlist.id, playlistType)
} }
root.setOnLongClickListener { root.setOnLongClickListener {
val playlistOptionsDialog = PlaylistOptionsBottomSheet( val playlistOptionsDialog = PlaylistOptionsBottomSheet(
playlistId = playlist.id!!, playlistId = playlist.id!!,
playlistName = playlist.name!!, playlistName = playlist.name!!,
isOwner = true playlistType = playlistType
) )
playlistOptionsDialog.show( playlistOptionsDialog.show(
(root.context as BaseActivity).supportFragmentManager, (root.context as BaseActivity).supportFragmentManager,

View File

@ -10,6 +10,7 @@ import com.github.libretube.api.obj.ContentItem
import com.github.libretube.databinding.ChannelRowBinding import com.github.libretube.databinding.ChannelRowBinding
import com.github.libretube.databinding.PlaylistsRowBinding import com.github.libretube.databinding.PlaylistsRowBinding
import com.github.libretube.databinding.VideoRowBinding import com.github.libretube.databinding.VideoRowBinding
import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.formatShort import com.github.libretube.extensions.formatShort
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.ui.base.BaseActivity import com.github.libretube.ui.base.BaseActivity
@ -140,13 +141,13 @@ class SearchAdapter(
playlistTitle.text = item.name playlistTitle.text = item.name
playlistDescription.text = item.uploaderName playlistDescription.text = item.uploaderName
root.setOnClickListener { root.setOnClickListener {
NavigationHelper.navigatePlaylist(root.context, item.url, false) NavigationHelper.navigatePlaylist(root.context, item.url, PlaylistType.PUBLIC)
} }
deletePlaylist.visibility = View.GONE deletePlaylist.visibility = View.GONE
root.setOnLongClickListener { root.setOnLongClickListener {
val playlistId = item.url!!.toID() val playlistId = item.url!!.toID()
val playlistName = item.name!! val playlistName = item.name!!
PlaylistOptionsBottomSheet(playlistId, playlistName, false) PlaylistOptionsBottomSheet(playlistId, playlistName, PlaylistType.PUBLIC)
.show((root.context as BaseActivity).supportFragmentManager, PlaylistOptionsBottomSheet::class.java.name) .show((root.context as BaseActivity).supportFragmentManager, PlaylistOptionsBottomSheet::class.java.name)
true true
} }

View File

@ -10,28 +10,23 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.obj.PlaylistId
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.TAG
import com.github.libretube.extensions.toastFromMainThread import com.github.libretube.extensions.toastFromMainThread
import com.github.libretube.ui.models.PlaylistViewModel import com.github.libretube.ui.models.PlaylistViewModel
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.ThemeHelper import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
class AddToPlaylistDialog : DialogFragment() { class AddToPlaylistDialog : DialogFragment() {
private lateinit var binding: DialogAddtoplaylistBinding private lateinit var binding: DialogAddtoplaylistBinding
private val viewModel: PlaylistViewModel by activityViewModels() private val viewModel: PlaylistViewModel by activityViewModels()
private lateinit var videoId: String private lateinit var videoId: String
private lateinit var token: String
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
videoId = arguments?.getString(IntentData.videoId)!! videoId = arguments?.getString(IntentData.videoId)!!
@ -46,9 +41,7 @@ class AddToPlaylistDialog : DialogFragment() {
} }
} }
token = PreferenceHelper.getToken() fetchPlaylists()
if (token != "") fetchPlaylists()
return MaterialAlertDialogBuilder(requireContext()) return MaterialAlertDialogBuilder(requireContext())
.setView(binding.root) .setView(binding.root)
@ -58,16 +51,11 @@ class AddToPlaylistDialog : DialogFragment() {
private fun fetchPlaylists() { private fun fetchPlaylists() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.authApi.getUserPlaylists(token) PlaylistsHelper.getPlaylists()
} catch (e: IOException) { } catch (e: Exception) {
println(e) Log.e(TAG(), e.toString())
Log.e(TAG(), "IOException, you might not have internet connection")
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG(), "HttpException, unexpected response")
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
} }
if (response.isNotEmpty()) { if (response.isNotEmpty()) {
val names = response.map { it.name } val names = response.map { it.name }
@ -81,8 +69,7 @@ class AddToPlaylistDialog : DialogFragment() {
var selectionIndex = 0 var selectionIndex = 0
response.forEachIndexed { index, playlist -> response.forEachIndexed { index, playlist ->
if (playlist.id == viewModel.lastSelectedPlaylistId) { if (playlist.id == viewModel.lastSelectedPlaylistId) {
selectionIndex = selectionIndex = index
index
} }
} }
binding.playlistsSpinner.setSelection(selectionIndex) binding.playlistsSpinner.setSelection(selectionIndex)
@ -102,23 +89,15 @@ class AddToPlaylistDialog : DialogFragment() {
private fun addToPlaylist(playlistId: String) { private fun addToPlaylist(playlistId: String) {
val appContext = context?.applicationContext ?: return val appContext = context?.applicationContext ?: return
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val response = try { val success = try {
RetrofitInstance.authApi.addToPlaylist( PlaylistsHelper.addToPlaylist(playlistId, videoId)
token, } catch (e: Exception) {
PlaylistId(playlistId, videoId) Log.e(TAG(), e.toString())
)
} catch (e: IOException) {
println(e)
Log.e(TAG(), "IOException, you might not have internet connection")
appContext.toastFromMainThread(R.string.unknown_error) appContext.toastFromMainThread(R.string.unknown_error)
return@launch return@launch
} catch (e: HttpException) {
Log.e(TAG(), "HttpException, unexpected response")
appContext.toastFromMainThread(R.string.server_error)
return@launch
} }
appContext.toastFromMainThread( appContext.toastFromMainThread(
if (response.message == "ok") R.string.added_to_playlist else R.string.fail if (success) R.string.added_to_playlist else R.string.fail
) )
} }
} }

View File

@ -2,25 +2,18 @@ package com.github.libretube.ui.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.obj.Playlists
import com.github.libretube.databinding.DialogCreatePlaylistBinding import com.github.libretube.databinding.DialogCreatePlaylistBinding
import com.github.libretube.extensions.TAG
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.ThemeHelper import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException
import java.io.IOException
class CreatePlaylistDialog( class CreatePlaylistDialog(
private val onSuccess: () -> Unit = {} private val onSuccess: () -> Unit = {}
) : DialogFragment() { ) : DialogFragment() {
private var token: String = ""
private lateinit var binding: DialogCreatePlaylistBinding private lateinit var binding: DialogCreatePlaylistBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@ -32,14 +25,17 @@ class CreatePlaylistDialog(
dismiss() dismiss()
} }
token = PreferenceHelper.getToken()
binding.createNewPlaylist.setOnClickListener { binding.createNewPlaylist.setOnClickListener {
// avoid creating the same playlist multiple times by spamming the button // avoid creating the same playlist multiple times by spamming the button
binding.createNewPlaylist.setOnClickListener(null) binding.createNewPlaylist.setOnClickListener(null)
val listName = binding.playlistName.text.toString() val listName = binding.playlistName.text.toString()
if (listName != "") { if (listName != "") {
createPlaylist(listName) lifecycleScope.launchWhenCreated {
PlaylistsHelper.createPlaylist(listName, requireContext().applicationContext) {
onSuccess.invoke()
dismiss()
}
}
} else { } else {
Toast.makeText(context, R.string.emptyPlaylistName, Toast.LENGTH_LONG).show() Toast.makeText(context, R.string.emptyPlaylistName, Toast.LENGTH_LONG).show()
} }
@ -49,37 +45,4 @@ class CreatePlaylistDialog(
.setView(binding.root) .setView(binding.root)
.show() .show()
} }
private fun createPlaylist(name: String) {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.authApi.createPlaylist(
token,
Playlists(name = name)
)
} catch (e: IOException) {
println(e)
Log.e(TAG(), "IOException, you might not have internet connection")
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG(), "HttpException, unexpected response $e")
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
}
if (response.playlistId != null) {
Toast.makeText(context, R.string.playlistCreated, Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, getString(R.string.unknown_error), Toast.LENGTH_SHORT)
.show()
}
// refresh the playlists in the library
try {
onSuccess.invoke()
} catch (e: Exception) {
Log.e(TAG(), e.toString())
}
dismiss()
}
}
} }

View File

@ -7,7 +7,10 @@ import androidx.fragment.app.DialogFragment
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.PlaylistId import com.github.libretube.api.obj.PlaylistId
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.awaitQuery
import com.github.libretube.util.PreferenceHelper import com.github.libretube.util.PreferenceHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -16,6 +19,7 @@ import kotlinx.coroutines.launch
class DeletePlaylistDialog( class DeletePlaylistDialog(
private val playlistId: String, private val playlistId: String,
private val playlistType: PlaylistType,
private val onSuccess: () -> Unit = {} private val onSuccess: () -> Unit = {}
) : DialogFragment() { ) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@ -31,6 +35,14 @@ class DeletePlaylistDialog(
} }
private fun deletePlaylist() { private fun deletePlaylist() {
if (playlistType == PlaylistType.LOCAL) {
awaitQuery {
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistById(playlistId)
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByPlaylistId(playlistId)
}
return
}
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val response = try { val response = try {
RetrofitInstance.authApi.deletePlaylist( RetrofitInstance.authApi.deletePlaylist(

View File

@ -11,6 +11,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.SubscriptionHelper import com.github.libretube.api.SubscriptionHelper
import com.github.libretube.databinding.FragmentHomeBinding import com.github.libretube.databinding.FragmentHomeBinding
@ -101,13 +102,12 @@ class HomeFragment : BaseFragment() {
} }
runOrError { runOrError {
if (token == "") return@runOrError val playlists = PlaylistsHelper.getPlaylists().withMaxSize(20)
val playlists = RetrofitInstance.authApi.getUserPlaylists(token).withMaxSize(20)
if (playlists.isEmpty()) return@runOrError if (playlists.isEmpty()) return@runOrError
runOnUiThread { runOnUiThread {
makeVisible(binding.playlistsRV, binding.playlistsTV) makeVisible(binding.playlistsRV, binding.playlistsTV)
binding.playlistsRV.layoutManager = LinearLayoutManager(context) binding.playlistsRV.layoutManager = LinearLayoutManager(context)
binding.playlistsRV.adapter = PlaylistsAdapter(playlists.toMutableList()) binding.playlistsRV.adapter = PlaylistsAdapter(playlists.toMutableList(), PlaylistsHelper.getType())
binding.playlistsRV.adapter?.registerAdapterDataObserver(object : binding.playlistsRV.adapter?.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() { RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {

View File

@ -12,7 +12,7 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.constants.PreferenceKeys import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.FragmentLibraryBinding import com.github.libretube.databinding.FragmentLibraryBinding
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
@ -22,8 +22,6 @@ import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.dialogs.CreatePlaylistDialog import com.github.libretube.ui.dialogs.CreatePlaylistDialog
import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.models.PlayerViewModel
import com.github.libretube.util.PreferenceHelper import com.github.libretube.util.PreferenceHelper
import retrofit2.HttpException
import java.io.IOException
class LibraryFragment : BaseFragment() { class LibraryFragment : BaseFragment() {
@ -70,26 +68,16 @@ class LibraryFragment : BaseFragment() {
findNavController().navigate(R.id.downloadsFragment) findNavController().navigate(R.id.downloadsFragment)
} }
if (token != "") { fetchPlaylists()
binding.boogh.setImageResource(R.drawable.ic_list)
binding.textLike.text = getString(R.string.emptyList)
binding.loginOrRegister.visibility = View.GONE binding.playlistRefresh.isEnabled = true
binding.playlistRefresh.setOnRefreshListener {
fetchPlaylists() fetchPlaylists()
}
binding.playlistRefresh.isEnabled = true binding.createPlaylist.setOnClickListener {
binding.playlistRefresh.setOnRefreshListener { CreatePlaylistDialog {
fetchPlaylists() fetchPlaylists()
} }.show(childFragmentManager, CreatePlaylistDialog::class.java.name)
binding.createPlaylist.setOnClickListener {
val newFragment = CreatePlaylistDialog {
fetchPlaylists()
}
newFragment.show(childFragmentManager, CreatePlaylistDialog::class.java.name)
}
} else {
binding.playlistRefresh.isEnabled = false
binding.createPlaylist.visibility = View.GONE
} }
} }
@ -101,26 +89,19 @@ class LibraryFragment : BaseFragment() {
binding.createPlaylist.layoutParams = layoutParams binding.createPlaylist.layoutParams = layoutParams
} }
fun fetchPlaylists() { private fun fetchPlaylists() {
binding.playlistRefresh.isRefreshing = true binding.playlistRefresh.isRefreshing = true
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
var playlists = try { var playlists = try {
RetrofitInstance.authApi.getUserPlaylists(token) PlaylistsHelper.getPlaylists()
} catch (e: IOException) { } catch (e: Exception) {
println(e) Log.e(TAG(), e.toString())
Log.e(TAG(), "IOException, you might not have internet connection")
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG(), "HttpException, unexpected response")
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
} finally { } finally {
binding.playlistRefresh.isRefreshing = false binding.playlistRefresh.isRefreshing = false
} }
if (playlists.isNotEmpty()) { if (playlists.isNotEmpty()) {
binding.loginOrRegister.visibility = View.GONE
playlists = when ( playlists = when (
PreferenceHelper.getString( PreferenceHelper.getString(
PreferenceKeys.PLAYLISTS_ORDER, PreferenceKeys.PLAYLISTS_ORDER,
@ -135,16 +116,15 @@ class LibraryFragment : BaseFragment() {
} }
val playlistsAdapter = PlaylistsAdapter( val playlistsAdapter = PlaylistsAdapter(
playlists.toMutableList() playlists.toMutableList(),
PlaylistsHelper.getType()
) )
// listen for playlists to become deleted // listen for playlists to become deleted
playlistsAdapter.registerAdapterDataObserver(object : playlistsAdapter.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() { RecyclerView.AdapterDataObserver() {
override fun onChanged() { override fun onChanged() {
if (playlistsAdapter.itemCount == 0) { binding.nothingHere.visibility = if (playlistsAdapter.itemCount == 0) View.VISIBLE else View.GONE
binding.loginOrRegister.visibility = View.VISIBLE
}
super.onChanged() super.onChanged()
} }
}) })
@ -152,7 +132,7 @@ class LibraryFragment : BaseFragment() {
binding.playlistRecView.adapter = playlistsAdapter binding.playlistRecView.adapter = playlistsAdapter
} else { } else {
runOnUiThread { runOnUiThread {
binding.loginOrRegister.visibility = View.VISIBLE binding.nothingHere.visibility = View.VISIBLE
} }
} }
} }

View File

@ -11,17 +11,20 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.FragmentPlaylistBinding import com.github.libretube.databinding.FragmentPlaylistBinding
import com.github.libretube.db.DatabaseHolder import com.github.libretube.db.DatabaseHolder
import com.github.libretube.db.obj.PlaylistBookmark import com.github.libretube.db.obj.PlaylistBookmark
import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.awaitQuery import com.github.libretube.extensions.awaitQuery
import com.github.libretube.extensions.query import com.github.libretube.extensions.query
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.ui.adapters.PlaylistAdapter import com.github.libretube.ui.adapters.PlaylistAdapter
import com.github.libretube.ui.base.BaseFragment import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.extensions.serializable
import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet
import com.github.libretube.util.ImageHelper import com.github.libretube.util.ImageHelper
import com.github.libretube.util.NavigationHelper import com.github.libretube.util.NavigationHelper
@ -34,7 +37,7 @@ class PlaylistFragment : BaseFragment() {
private var playlistId: String? = null private var playlistId: String? = null
private var playlistName: String? = null private var playlistName: String? = null
private var isOwner: Boolean = false private var playlistType: PlaylistType = PlaylistType.PUBLIC
private var nextPage: String? = null private var nextPage: String? = null
private var playlistAdapter: PlaylistAdapter? = null private var playlistAdapter: PlaylistAdapter? = null
private var isLoading = true private var isLoading = true
@ -44,7 +47,7 @@ class PlaylistFragment : BaseFragment() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments?.let {
playlistId = it.getString(IntentData.playlistId) playlistId = it.getString(IntentData.playlistId)
isOwner = it.getBoolean("isOwner") playlistType = it.serializable(IntentData.playlistType)!!
} }
} }
@ -84,12 +87,7 @@ class PlaylistFragment : BaseFragment() {
binding.playlistScrollview.visibility = View.GONE binding.playlistScrollview.visibility = View.GONE
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
// load locally stored playlists with the auth api PlaylistsHelper.getPlaylist(playlistType, playlistId!!)
if (isOwner) {
RetrofitInstance.authApi.getPlaylist(playlistId!!)
} else {
RetrofitInstance.api.getPlaylist(playlistId!!)
}
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG(), "IOException, you might not have internet connection") Log.e(TAG(), "IOException, you might not have internet connection")
@ -116,7 +114,7 @@ class PlaylistFragment : BaseFragment() {
// show playlist options // show playlist options
binding.optionsMenu.setOnClickListener { binding.optionsMenu.setOnClickListener {
PlaylistOptionsBottomSheet(playlistId!!, playlistName ?: "", isOwner).show( PlaylistOptionsBottomSheet(playlistId!!, playlistName ?: "", playlistType).show(
childFragmentManager, childFragmentManager,
PlaylistOptionsBottomSheet::class.java.name PlaylistOptionsBottomSheet::class.java.name
) )
@ -131,7 +129,7 @@ class PlaylistFragment : BaseFragment() {
) )
} }
if (isOwner) binding.bookmark.visibility = View.GONE if (playlistType != PlaylistType.PUBLIC) binding.bookmark.visibility = View.GONE
binding.bookmark.setOnClickListener { binding.bookmark.setOnClickListener {
isBookmarked = !isBookmarked isBookmarked = !isBookmarked
@ -157,7 +155,7 @@ class PlaylistFragment : BaseFragment() {
playlistAdapter = PlaylistAdapter( playlistAdapter = PlaylistAdapter(
response.relatedStreams.orEmpty().toMutableList(), response.relatedStreams.orEmpty().toMutableList(),
playlistId!!, playlistId!!,
isOwner playlistType
) )
// listen for playlist items to become deleted // listen for playlist items to become deleted
@ -189,7 +187,7 @@ class PlaylistFragment : BaseFragment() {
/** /**
* listener for swiping to the left or right * listener for swiping to the left or right
*/ */
if (isOwner) { if (playlistType != PlaylistType.PUBLIC) {
val itemTouchCallback = object : ItemTouchHelper.SimpleCallback( val itemTouchCallback = object : ItemTouchHelper.SimpleCallback(
0, 0,
ItemTouchHelper.LEFT ItemTouchHelper.LEFT
@ -219,34 +217,27 @@ class PlaylistFragment : BaseFragment() {
} }
private fun fetchNextPage() { private fun fetchNextPage() {
fun run() { lifecycleScope.launchWhenCreated {
lifecycleScope.launchWhenCreated { val response = try {
val response = try { // load locally stored playlists with the auth api
// load locally stored playlists with the auth api if (playlistType == PlaylistType.OWNED) {
if (isOwner) { RetrofitInstance.authApi.getPlaylistNextPage(
RetrofitInstance.authApi.getPlaylistNextPage( playlistId!!,
playlistId!!, nextPage!!
nextPage!! )
) } else {
} else { RetrofitInstance.api.getPlaylistNextPage(
RetrofitInstance.api.getPlaylistNextPage( playlistId!!,
playlistId!!, nextPage!!
nextPage!! )
)
}
} catch (e: IOException) {
println(e)
Log.e(TAG(), "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG(), "HttpException, unexpected response," + e.response())
return@launchWhenCreated
} }
nextPage = response.nextpage } catch (e: Exception) {
playlistAdapter?.updateItems(response.relatedStreams!!) Log.e(TAG(), e.toString())
isLoading = false return@launchWhenCreated
} }
nextPage = response.nextpage
playlistAdapter?.updateItems(response.relatedStreams!!)
isLoading = false
} }
run()
} }
} }

View File

@ -7,6 +7,7 @@ import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.PlaylistId import com.github.libretube.api.obj.PlaylistId
import com.github.libretube.databinding.DialogTextPreferenceBinding import com.github.libretube.databinding.DialogTextPreferenceBinding
import com.github.libretube.enums.PlaylistType
import com.github.libretube.enums.ShareObjectType import com.github.libretube.enums.ShareObjectType
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.extensions.toastFromMainThread import com.github.libretube.extensions.toastFromMainThread
@ -25,8 +26,8 @@ import java.io.IOException
class PlaylistOptionsBottomSheet( class PlaylistOptionsBottomSheet(
private val playlistId: String, private val playlistId: String,
private val playlistName: String, playlistName: String,
private val isOwner: Boolean private val playlistType: PlaylistType
) : BaseBottomSheet() { ) : BaseBottomSheet() {
private val shareData = ShareData(currentPlaylist = playlistName) private val shareData = ShareData(currentPlaylist = playlistName)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -37,7 +38,7 @@ class PlaylistOptionsBottomSheet(
context?.getString(R.string.share)!! context?.getString(R.string.share)!!
) )
if (isOwner) { if (playlistType != PlaylistType.PUBLIC) {
optionsList = optionsList + optionsList = optionsList +
context?.getString(R.string.renamePlaylist)!! + context?.getString(R.string.renamePlaylist)!! +
context?.getString(R.string.deletePlaylist)!! - context?.getString(R.string.deletePlaylist)!! -
@ -50,7 +51,7 @@ class PlaylistOptionsBottomSheet(
context?.getString(R.string.playOnBackground) -> { context?.getString(R.string.playOnBackground) -> {
runBlocking { runBlocking {
val playlist = val playlist =
if (isOwner) { if (playlistType == PlaylistType.OWNED) {
RetrofitInstance.authApi.getPlaylist(playlistId) RetrofitInstance.authApi.getPlaylist(playlistId)
} else { } else {
RetrofitInstance.api.getPlaylist(playlistId) RetrofitInstance.api.getPlaylist(playlistId)
@ -82,7 +83,7 @@ class PlaylistOptionsBottomSheet(
shareDialog.show(parentFragmentManager, ShareDialog::class.java.name) shareDialog.show(parentFragmentManager, ShareDialog::class.java.name)
} }
context?.getString(R.string.deletePlaylist) -> { context?.getString(R.string.deletePlaylist) -> {
DeletePlaylistDialog(playlistId) DeletePlaylistDialog(playlistId, playlistType)
.show(parentFragmentManager, null) .show(parentFragmentManager, null)
} }
context?.getString(R.string.renamePlaylist) -> { context?.getString(R.string.renamePlaylist) -> {

View File

@ -1,7 +1,7 @@
package com.github.libretube.ui.sheets package com.github.libretube.ui.sheets
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import androidx.core.os.bundleOf
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
@ -13,7 +13,6 @@ import com.github.libretube.ui.dialogs.DownloadDialog
import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.util.BackgroundHelper import com.github.libretube.util.BackgroundHelper
import com.github.libretube.util.PlayingQueue import com.github.libretube.util.PlayingQueue
import com.github.libretube.util.PreferenceHelper
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -37,14 +36,6 @@ class VideoOptionsBottomSheet(
context?.getString(R.string.share)!! context?.getString(R.string.share)!!
) )
// remove the add to playlist option if not logged in
if (PreferenceHelper.getToken() == "") {
optionsList.remove(
context?.getString(R.string.addToPlaylist)
)
}
/** /**
* Check whether the player is running and add queue options * Check whether the player is running and add queue options
*/ */
@ -61,19 +52,12 @@ class VideoOptionsBottomSheet(
} }
// Add Video to Playlist Dialog // Add Video to Playlist Dialog
context?.getString(R.string.addToPlaylist) -> { context?.getString(R.string.addToPlaylist) -> {
val token = PreferenceHelper.getToken() AddToPlaylistDialog().apply {
if (token != "") { arguments = bundleOf(IntentData.videoId to videoId)
val newFragment = AddToPlaylistDialog() }.show(
val bundle = Bundle() parentFragmentManager,
bundle.putString(IntentData.videoId, videoId) AddToPlaylistDialog::class.java.name
newFragment.arguments = bundle )
newFragment.show(
parentFragmentManager,
AddToPlaylistDialog::class.java.name
)
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
} }
context?.getString(R.string.download) -> { context?.getString(R.string.download) -> {
val downloadDialog = DownloadDialog(videoId) val downloadDialog = DownloadDialog(videoId)

View File

@ -9,6 +9,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.ui.activities.MainActivity import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.fragments.PlayerFragment import com.github.libretube.ui.fragments.PlayerFragment
@ -59,14 +60,14 @@ object NavigationHelper {
fun navigatePlaylist( fun navigatePlaylist(
context: Context, context: Context,
playlistId: String?, playlistId: String?,
isOwner: Boolean playlistType: PlaylistType
) { ) {
if (playlistId == null) return if (playlistId == null) return
val activity = context as MainActivity val activity = context as MainActivity
val bundle = Bundle() val bundle = Bundle()
bundle.putString(IntentData.playlistId, playlistId) bundle.putString(IntentData.playlistId, playlistId)
bundle.putBoolean("isOwner", isOwner) bundle.putSerializable(IntentData.playlistType, playlistType)
activity.navController.navigate(R.id.playlistFragment, bundle) activity.navController.navigate(R.id.playlistFragment, bundle)
} }

View File

@ -7,9 +7,10 @@
tools:context=".ui.fragments.LibraryFragment"> tools:context=".ui.fragments.LibraryFragment">
<RelativeLayout <RelativeLayout
android:id="@+id/loginOrRegister" android:id="@+id/nothing_here"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:visibility="gone">
<ImageView <ImageView
android:id="@+id/boogh" android:id="@+id/boogh"
@ -17,7 +18,7 @@
android:layout_height="100dp" android:layout_height="100dp"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:src="@drawable/ic_login" /> android:src="@drawable/ic_list" />
<TextView <TextView
android:id="@+id/text_like" android:id="@+id/text_like"
@ -27,7 +28,7 @@
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginHorizontal="10dp" android:layout_marginHorizontal="10dp"
android:gravity="center" android:gravity="center"
android:text="@string/please_login" android:text="@string/emptyList"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />
</RelativeLayout> </RelativeLayout>