add and remove playlist

This commit is contained in:
rimthekid 2022-04-17 11:50:10 -07:00
parent 42cbb58c15
commit 87466063d8
13 changed files with 296 additions and 91 deletions

View File

@ -0,0 +1,109 @@
package com.github.libretube
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.github.libretube.adapters.PlaylistsAdapter
import com.github.libretube.obj.PlaylistId
import retrofit2.HttpException
import java.io.IOException
class AddtoPlaylistDialog : DialogFragment() {
private val TAG = "AddToPlaylistDialog"
private lateinit var videoId: String
private lateinit var token: String
private lateinit var spinner: Spinner
private lateinit var button: Button
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
videoId = arguments?.getString("videoId")!!
val builder = AlertDialog.Builder(it)
// Get the layout inflater
val inflater = requireActivity().layoutInflater;
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
token = sharedPref?.getString("token","")!!
var view: View = inflater.inflate(R.layout.dialog_addtoplaylist, null)
spinner = view.findViewById(R.id.playlists_spinner)
button = view.findViewById(R.id.addToPlaylist)
if(token!=""){
fetchPlaylists()
}
builder.setView(view)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
private fun fetchPlaylists(){
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.playlists(token)
}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")
Toast.makeText(context,R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
}
if (response.isNotEmpty()){
var names = emptyList<String>().toMutableList()
for(playlist in response){
names.add(playlist.name!!)
}
val arrayAdapter = ArrayAdapter(requireContext(),android.R.layout.simple_spinner_item,names)
arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = arrayAdapter
runOnUiThread {
button.setOnClickListener {
addToPlaylist(response[spinner.selectedItemPosition].id!!)
}
}
}else{
}
}
}
run()
}
private fun addToPlaylist(playlistId: String){
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.addToPlaylist(token, PlaylistId(playlistId, videoId))
}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")
Toast.makeText(context,R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
}
if (response.message == "ok"){
Toast.makeText(context,R.string.success, Toast.LENGTH_SHORT).show()
dialog?.dismiss()
}else{
Toast.makeText(context,R.string.fail, Toast.LENGTH_SHORT).show()
}
}
}
run()
}
private fun Fragment?.runOnUiThread(action: () -> Unit) {
this ?: return
if (!isAdded) return // Fragment not attached to an Activity
activity?.runOnUiThread(action)
}
}

View File

@ -1,44 +0,0 @@
package com.github.libretube
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.github.libretube.obj.Login
class CreatePlaylistDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
// Get the layout inflater
val inflater = requireActivity().layoutInflater;
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
val token = sharedPref?.getString("token","")
var view: View = inflater.inflate(R.layout.dialog_createplaylist, null)
Log.e("dafaq",token!!)
if(token!=""){
val sharedPref2 = context?.getSharedPreferences("username", Context.MODE_PRIVATE)
val user = sharedPref2?.getString("username","")
view.findViewById<TextView>(R.id.user).text = view.findViewById<TextView>(R.id.user).text.toString()+" ("+user+")"
view.findViewById<Button>(R.id.logout).setOnClickListener {
Toast.makeText(context,R.string.loggedout, Toast.LENGTH_SHORT).show()
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
with (sharedPref!!.edit()) {
putString("token","")
apply()
}
dialog?.dismiss()
}
dialog?.dismiss()
}
builder.setView(view)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}

View File

@ -62,6 +62,7 @@ class Library : Fragment() {
val playlistName = view.findViewById<EditText>(R.id.playlists_name)
view.findViewById<Button>(R.id.create_playlist).setOnClickListener {
if(playlistName.text.toString()!="") createPlaylist(playlistName.text.toString(),view)
hideKeyboard()
}
} else{
with(view.findViewById<ImageView>(R.id.boogh2)){
@ -102,7 +103,7 @@ class Library : Fragment() {
visibility=View.GONE
}
}
val playlistsAdapter = PlaylistsAdapter(response.toMutableList())
val playlistsAdapter = PlaylistsAdapter(response.toMutableList(),requireActivity())
playlistRecyclerView.adapter= playlistsAdapter
}else{
runOnUiThread {
@ -153,4 +154,4 @@ class Library : Fragment() {
if (!isAdded) return // Fragment not attached to an Activity
activity?.runOnUiThread(action)
}
}
}

View File

@ -64,6 +64,12 @@ interface PipedApi {
@POST("user/playlists/create")
suspend fun createPlaylist(@Header("Authorization") token: String, @Body name: Playlists): PlaylistId
@POST("user/playlists/add")
suspend fun addToPlaylist(@Header("Authorization") token: String, @Body playlistId: PlaylistId): Message
@POST("user/playlists/remove")
suspend fun removeFromPlaylist(@Header("Authorization") token: String, @Body playlistId: PlaylistId): Message
//only for fetching servers list
@GET
suspend fun getInstances(@Url url: String): List<Instances>

View File

@ -502,6 +502,13 @@ class PlayerFragment : Fragment() {
val channelId = response.uploaderUrl?.replace("/channel/", "")
val subButton = view.findViewById<MaterialButton>(R.id.player_subscribe)
isSubscribed(subButton, channelId!!)
view.findViewById<RelativeLayout>(R.id.save).setOnClickListener {
val newFragment = AddtoPlaylistDialog()
var bundle = Bundle()
bundle.putString("videoId", videoId)
newFragment.arguments = bundle
newFragment.show(childFragmentManager, "AddToPlaylist")
}
}
// share button
view.findViewById<RelativeLayout>(R.id.relPlayer_share).setOnClickListener {

View File

@ -1,5 +1,6 @@
package com.github.libretube
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
@ -69,7 +70,13 @@ class PlaylistFragment : Fragment() {
view.findViewById<TextView>(R.id.playlist_name).text=response.name
view.findViewById<TextView>(R.id.playlist_uploader).text=response.uploader
view.findViewById<TextView>(R.id.playlist_totVideos).text=response.videos.toString()+" Videos"
playlistAdapter = PlaylistAdapter(response.relatedStreams!!.toMutableList())
val sharedPref2 = context?.getSharedPreferences("username", Context.MODE_PRIVATE)
val user = sharedPref2?.getString("username","")
var isOwner = false
if(response.uploaderUrl == null && response.uploader == user){
isOwner = true
}
playlistAdapter = PlaylistAdapter(response.relatedStreams!!.toMutableList(), playlist_id!!, isOwner, requireActivity())
view.findViewById<RecyclerView>(R.id.playlist_recView).adapter = playlistAdapter
val scrollView = view.findViewById<ScrollView>(R.id.playlist_scrollview)
scrollView.viewTreeObserver

View File

@ -1,7 +1,10 @@
package com.github.libretube.adapters
import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.text.format.DateUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -12,9 +15,16 @@ import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import com.github.libretube.PlayerFragment
import com.github.libretube.R
import com.github.libretube.RetrofitInstance
import com.github.libretube.obj.PlaylistId
import com.github.libretube.obj.StreamItem
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
class PlaylistAdapter(private val videoFeed: MutableList<StreamItem>): RecyclerView.Adapter<PlaylistViewHolder>() {
class PlaylistAdapter(private val videoFeed: MutableList<StreamItem>, private val playlistId: String, private val isOwner: Boolean, private val activity: Activity): RecyclerView.Adapter<PlaylistViewHolder>() {
private val TAG = "PlaylistAdapter"
override fun getItemCount(): Int {
return videoFeed.size
}
@ -25,16 +35,16 @@ class PlaylistAdapter(private val videoFeed: MutableList<StreamItem>): RecyclerV
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaylistViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.video_channel_row,parent,false)
val cell = layoutInflater.inflate(R.layout.playlist_row,parent,false)
return PlaylistViewHolder(cell)
}
override fun onBindViewHolder(holder: PlaylistViewHolder, position: Int) {
val streamItem = videoFeed[position]
holder.v.findViewById<TextView>(R.id.channel_description).text = streamItem.title
holder.v.findViewById<TextView>(R.id.channel_views).text = streamItem.uploaderName
holder.v.findViewById<TextView>(R.id.channel_duration).text = DateUtils.formatElapsedTime(streamItem.duration!!)
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.channel_thumbnail)
holder.v.findViewById<TextView>(R.id.playlist_title).text = streamItem.title
holder.v.findViewById<TextView>(R.id.playlist_description).text = streamItem.uploaderName
holder.v.findViewById<TextView>(R.id.playlist_duration).text = DateUtils.formatElapsedTime(streamItem.duration!!)
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.playlist_thumbnail)
Picasso.get().load(streamItem.thumbnail).into(thumbnailImage)
holder.v.setOnClickListener{
var bundle = Bundle()
@ -49,6 +59,50 @@ class PlaylistAdapter(private val videoFeed: MutableList<StreamItem>): RecyclerV
.replace(R.id.container, frag)
.commitNow()
}
if(isOwner){
val delete = holder.v.findViewById<ImageView>(R.id.delete_playlist)
delete.visibility = View.VISIBLE
delete.setOnClickListener {
val sharedPref = holder.v.context.getSharedPreferences("token", Context.MODE_PRIVATE)
val token = sharedPref?.getString("token","")!!
removeFromPlaylist(token, position)
}
}
}
private fun removeFromPlaylist(token: String, position: Int) {
fun run() {
GlobalScope.launch{
val response = try {
RetrofitInstance.api.removeFromPlaylist(token, PlaylistId(playlistId = playlistId, index = position))
}catch(e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
return@launch
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response")
return@launch
}finally {
}
try{
if(response.message == "ok"){
Log.d(TAG,"deleted!")
videoFeed.removeAt(position)
// FIXME: This needs to run on UI thread?
activity.runOnUiThread { notifyDataSetChanged() }
/*if(playlists.isEmpty()){
view.findViewById<ImageView>(R.id.boogh2).visibility=View.VISIBLE
}*/
}
}catch (e:Exception){
Log.e(TAG,e.toString())
}
}
}
run()
}
}
class PlaylistViewHolder(val v: View): RecyclerView.ViewHolder(v){

View File

@ -1,35 +1,27 @@
package com.github.libretube.adapters
import android.app.Activity
import android.content.Context
import android.media.Image
import android.os.Bundle
import android.text.format.DateUtils
import android.os.Handler
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.*
import com.github.libretube.obj.PlaylistId
import com.squareup.picasso.Picasso
import com.github.libretube.obj.StreamItem
import com.github.libretube.obj.Playlists
import com.squareup.picasso.Picasso
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
class PlaylistsAdapter(private val playlists: MutableList<Playlists>): RecyclerView.Adapter<PlaylistsViewHolder>() {
class PlaylistsAdapter(private val playlists: MutableList<Playlists>, private val activity: Activity): RecyclerView.Adapter<PlaylistsViewHolder>() {
val TAG = "PlaylistsAdapter"
override fun getItemCount(): Int {
return playlists.size
@ -91,7 +83,8 @@ class PlaylistsAdapter(private val playlists: MutableList<Playlists>): RecyclerV
Log.d(TAG,"deleted!")
playlists.removeAt(position)
// FIXME: This needs to run on UI thread?
notifyDataSetChanged()
activity.runOnUiThread { notifyDataSetChanged() }
/*if(playlists.isEmpty()){
view.findViewById<ImageView>(R.id.boogh2).visibility=View.VISIBLE
}*/
@ -105,6 +98,7 @@ class PlaylistsAdapter(private val playlists: MutableList<Playlists>): RecyclerV
run()
}
}
class PlaylistsViewHolder(val v: View): RecyclerView.ViewHolder(v){
init {

View File

@ -6,4 +6,5 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
data class PlaylistId(
var playlistId: String? = null,
var videoId: String? = null,
var index: Int = -1,
)

View File

@ -14,36 +14,22 @@
android:scaleType="center"
android:background="#CD5757"
android:contentDescription="@string/app_name" />
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hintEnabled="false"
android:layout_marginTop="16dp"
android:layout_marginLeft="7dp"
android:layout_marginRight="7dp"
android:layout_marginBottom="4dp"
>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/username"
<Spinner
android:id="@+id/playlists_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/playlistName"
android:inputType="text"
android:padding="12dp" />
</com.google.android.material.textfield.TextInputLayout>
android:padding="8dp"
android:layout_margin="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<Button
android:id="@+id/create_playlist_button"
android:id="@+id/addToPlaylist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/createPlaylist"
android:text="@string/addToPlaylist"
android:padding="8dp"
android:layout_margin="8dp"/>

View File

@ -49,7 +49,7 @@
android:padding="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:text="Title"
android:text="Loading..."
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/player_description_arrow"
@ -62,7 +62,7 @@
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:text="TextView"
android:text=""
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@id/player_description_arrow"
app:layout_constraintStart_toStartOf="parent"
@ -192,9 +192,11 @@
android:text="@string/vlc" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1">
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/player_save"

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_marginBottom="16dp"
android:background="?android:attr/selectableItemBackground"
android:id="@+id/video_search"
>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent=".5"/>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_search_thumbnail"
app:cardCornerRadius="8dp"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/guideline">
<ImageView
android:id="@+id/playlist_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:srcCompat="@tools:sample/backgrounds/scenic" />
<TextView
android:id="@+id/playlist_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="5dp"
android:textColor="@color/duration_text_color"
android:background="@color/duration_background_color"
android:padding="0.5dp"/>
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/playlist_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="TextView"
app:layout_constraintEnd_toStartOf="@+id/delete_playlist"
app:layout_constraintStart_toEndOf="@+id/card_search_thumbnail"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/playlist_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="TextView"
app:layout_constraintEnd_toStartOf="@+id/delete_playlist"
app:layout_constraintStart_toEndOf="@+id/card_search_thumbnail"
app:layout_constraintTop_toBottomOf="@+id/playlist_title" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/delete_playlist"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:shapeAppearanceOverlay="@style/roundedImageViewRounded"
android:src="@drawable/ic_delete"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp"
android:visibility="gone"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -46,4 +46,7 @@
<string name="createPlaylist">Create Playlist</string>
<string name="playlistCreated">Playlist created!</string>
<string name="playlistName">Playlist Name</string>
<string name="addToPlaylist">Add to Playlist</string>
<string name="success">Success!</string>
<string name="fail">Failed :(</string>
</resources>