From c7893ddd389873d85fa798bde13e2f7c9d7ebf8b Mon Sep 17 00:00:00 2001 From: digiwizkid Date: Sat, 8 Jan 2022 13:18:41 +0530 Subject: [PATCH 1/3] Add to playlist feature initial commit --- .../adapter/MultiViewRecyclerViewHolder.kt | 196 +++++++++--------- .../peertube/database/AppDatabase.java | 4 +- .../net/schueller/peertube/database/Video.kt | 21 ++ .../schueller/peertube/database/VideoDao.kt | 39 ++++ .../peertube/database/VideoRepository.kt | 47 +++++ .../peertube/database/VideoRoomDatabase.java | 54 +++++ .../peertube/database/VideoViewModel.kt | 47 +++++ .../fragment/VideoMetaDataFragment.kt | 88 ++++---- 8 files changed, 346 insertions(+), 150 deletions(-) create mode 100644 app/src/main/java/net/schueller/peertube/database/Video.kt create mode 100644 app/src/main/java/net/schueller/peertube/database/VideoDao.kt create mode 100644 app/src/main/java/net/schueller/peertube/database/VideoRepository.kt create mode 100644 app/src/main/java/net/schueller/peertube/database/VideoRoomDatabase.java create mode 100644 app/src/main/java/net/schueller/peertube/database/VideoViewModel.kt diff --git a/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecyclerViewHolder.kt b/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecyclerViewHolder.kt index 80d1699..af85876 100644 --- a/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecyclerViewHolder.kt +++ b/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecyclerViewHolder.kt @@ -30,28 +30,26 @@ import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import com.google.gson.JsonObject +import com.mikepenz.iconics.Iconics.Builder import com.squareup.picasso.Picasso -import net.schueller.peertube.R.color -import net.schueller.peertube.R.string +import net.schueller.peertube.R +import net.schueller.peertube.R.* import net.schueller.peertube.activity.AccountActivity import net.schueller.peertube.activity.VideoListActivity import net.schueller.peertube.activity.VideoPlayActivity +import net.schueller.peertube.databinding.* +import net.schueller.peertube.fragment.VideoMetaDataFragment import net.schueller.peertube.helper.APIUrlHelper +import net.schueller.peertube.helper.MetaDataHelper.getCreatorAvatar +import net.schueller.peertube.helper.MetaDataHelper.getCreatorString import net.schueller.peertube.helper.MetaDataHelper.getDuration import net.schueller.peertube.helper.MetaDataHelper.getMetaString import net.schueller.peertube.helper.MetaDataHelper.getOwnerString -import com.mikepenz.iconics.Iconics.Builder -import net.schueller.peertube.R -import net.schueller.peertube.R.id -import net.schueller.peertube.R.menu -import net.schueller.peertube.databinding.* -import net.schueller.peertube.fragment.VideoMetaDataFragment -import net.schueller.peertube.helper.MetaDataHelper.getCreatorAvatar -import net.schueller.peertube.helper.MetaDataHelper.getCreatorString -import net.schueller.peertube.helper.MetaDataHelper.getTagsString +import net.schueller.peertube.helper.MetaDataHelper.isChannel import net.schueller.peertube.intents.Intents import net.schueller.peertube.model.* import net.schueller.peertube.model.ui.VideoMetaViewItem +import net.schueller.peertube.network.GetUserService import net.schueller.peertube.network.GetVideoDataService import net.schueller.peertube.network.RetrofitInstance import net.schueller.peertube.network.Session @@ -61,8 +59,6 @@ import okhttp3.ResponseBody import retrofit2.Call import retrofit2.Callback import retrofit2.Response -import net.schueller.peertube.helper.MetaDataHelper.isChannel -import net.schueller.peertube.network.GetUserService sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) { @@ -70,13 +66,13 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi var videoRating: Rating? = null var isLeaveAppExpected = false - class CategoryViewHolder(private val binding: ItemCategoryTitleBinding): MultiViewRecyclerViewHolder(binding) { + class CategoryViewHolder(private val binding: ItemCategoryTitleBinding) : MultiViewRecyclerViewHolder(binding) { fun bind(category: Category) { binding.textViewTitle.text = category.label } } - class VideoCommentsViewHolder(private val binding: ItemVideoCommentsOverviewBinding): MultiViewRecyclerViewHolder(binding) { + class VideoCommentsViewHolder(private val binding: ItemVideoCommentsOverviewBinding) : MultiViewRecyclerViewHolder(binding) { fun bind(commentThread: CommentThread) { binding.videoCommentsTotalCount.text = commentThread.total.toString() @@ -90,8 +86,8 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi val baseUrl = APIUrlHelper.getUrl(binding.videoHighlightedAvatar.context) val avatarPath = avatar.path Picasso.get() - .load(baseUrl + avatarPath) - .into(binding.videoHighlightedAvatar) + .load(baseUrl + avatarPath) + .into(binding.videoHighlightedAvatar) } binding.videoHighlightedComment.text = highlightedComment.text } @@ -99,7 +95,7 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi } - class VideoMetaViewHolder(private val binding: ItemVideoMetaBinding, private val videoMetaDataFragment: VideoMetaDataFragment?): MultiViewRecyclerViewHolder(binding) { + class VideoMetaViewHolder(private val binding: ItemVideoMetaBinding, private val videoMetaDataFragment: VideoMetaDataFragment?) : MultiViewRecyclerViewHolder(binding) { fun bind(videoMetaViewItem: VideoMetaViewItem) { val video = videoMetaViewItem.video @@ -109,16 +105,16 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi val context = binding.avatar.context val apiBaseURL = APIUrlHelper.getUrlWithVersion(context) val videoDataService = RetrofitInstance.getRetrofitInstance( - apiBaseURL, - APIUrlHelper.useInsecureConnection(context) + apiBaseURL, + APIUrlHelper.useInsecureConnection(context) ).create( - GetVideoDataService::class.java + GetVideoDataService::class.java ) val userService = RetrofitInstance.getRetrofitInstance( - apiBaseURL, - APIUrlHelper.useInsecureConnection(context) + apiBaseURL, + APIUrlHelper.useInsecureConnection(context) ).create( - GetUserService::class.java + GetUserService::class.java ) // Title @@ -137,27 +133,25 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi rateVideo(false, video, context, binding) } + // Add to playlist binding.videoAddToPlaylistWrapper.setOnClickListener { - Toast.makeText( - context, - context.getString(string.video_feature_not_yet_implemented), - Toast.LENGTH_SHORT - ).show() + videoMetaDataFragment.saveToPlaylist(video) + Toast.makeText(context, "Saved to playlist", Toast.LENGTH_SHORT).show() } binding.videoBlockWrapper.setOnClickListener { Toast.makeText( - context, - context.getString(string.video_feature_not_yet_implemented), - Toast.LENGTH_SHORT + context, + context.getString(string.video_feature_not_yet_implemented), + Toast.LENGTH_SHORT ).show() } binding.videoFlagWrapper.setOnClickListener { Toast.makeText( - context, - context.getString(string.video_feature_not_yet_implemented), - Toast.LENGTH_SHORT + context, + context.getString(string.video_feature_not_yet_implemented), + Toast.LENGTH_SHORT ).show() } @@ -198,10 +192,10 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi // created at / views binding.videoMeta.text = getMetaString( - video.createdAt, - video.views, - context, - true + video.createdAt, + video.views, + context, + true ) // owner / creator @@ -220,8 +214,8 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi val baseUrl = APIUrlHelper.getUrl(context) val avatarPath = avatar.path Picasso.get() - .load(baseUrl + avatarPath) - .into(binding.avatar) + .load(baseUrl + avatarPath) + .into(binding.avatar) } // videoOwnerSubscribers @@ -243,7 +237,6 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi } - // get subscription status var isSubscribed = false @@ -262,6 +255,7 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi } } } + override fun onFailure(call: Call, t: Throwable) { // Do nothing. } @@ -278,52 +272,54 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi val call = userService.subscribe(body) call.enqueue(object : Callback { override fun onResponse( - call: Call, - response: Response + call: Call, + response: Response ) { if (response.isSuccessful) { binding.videoOwnerSubscribeButton.setText(string.unsubscribe) isSubscribed = true } } + override fun onFailure(call: Call, t: Throwable) { // Do nothing. } }) } else { AlertDialog.Builder(context) - .setTitle(context.getString(string.video_sub_del_alert_title)) - .setMessage(context.getString(string.video_sub_del_alert_msg)) - .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> - // Yes - val payload = video.channel.name + "@" + video.channel.host - val call = userService.unsubscribe(payload) - call.enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (response.isSuccessful) { - binding.videoOwnerSubscribeButton.setText(string.subscribe) - isSubscribed = false + .setTitle(context.getString(string.video_sub_del_alert_title)) + .setMessage(context.getString(string.video_sub_del_alert_msg)) + .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> + // Yes + val payload = video.channel.name + "@" + video.channel.host + val call = userService.unsubscribe(payload) + call.enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + binding.videoOwnerSubscribeButton.setText(string.subscribe) + isSubscribed = false + } } - } - override fun onFailure(call: Call, t: Throwable) { - // Do nothing. - } - }) - } - .setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> - // No - } - .setIcon(android.R.drawable.ic_dialog_alert) - .show() + + override fun onFailure(call: Call, t: Throwable) { + // Do nothing. + } + }) + } + .setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> + // No + } + .setIcon(android.R.drawable.ic_dialog_alert) + .show() } } else { Toast.makeText( - context, - context.getString(string.video_login_required_for_service), - Toast.LENGTH_SHORT + context, + context.getString(string.video_login_required_for_service), + Toast.LENGTH_SHORT ).show() } } @@ -333,7 +329,7 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi } } - class ChannelViewHolder(private val binding: ItemChannelTitleBinding): MultiViewRecyclerViewHolder(binding) { + class ChannelViewHolder(private val binding: ItemChannelTitleBinding) : MultiViewRecyclerViewHolder(binding) { fun bind(channel: Channel) { val context = binding.avatar.context @@ -344,22 +340,22 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi if (avatar != null) { val avatarPath = avatar.path Picasso.get() - .load(baseUrl + avatarPath) - .placeholder(R.drawable.test_image) - .into(binding.avatar) + .load(baseUrl + avatarPath) + .placeholder(R.drawable.test_image) + .into(binding.avatar) } binding.textViewTitle.text = channel.displayName } } - class TagViewHolder(private val binding: ItemTagTitleBinding): MultiViewRecyclerViewHolder(binding) { + class TagViewHolder(private val binding: ItemTagTitleBinding) : MultiViewRecyclerViewHolder(binding) { fun bind(tag: TagVideo) { binding.textViewTitle.text = tag.tag } } - class VideoViewHolder(private val binding: RowVideoListBinding): MultiViewRecyclerViewHolder(binding) { + class VideoViewHolder(private val binding: RowVideoListBinding) : MultiViewRecyclerViewHolder(binding) { fun bind(video: Video) { @@ -368,18 +364,18 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi // Temp Loading Image Picasso.get() - .load(baseUrl + video.previewPath) - .placeholder(R.drawable.test_image) - .error(R.drawable.test_image) - .into(binding.thumb) + .load(baseUrl + video.previewPath) + .placeholder(R.drawable.test_image) + .error(R.drawable.test_image) + .into(binding.thumb) // Avatar val avatar = getCreatorAvatar(video, context) if (avatar != null) { val avatarPath = avatar.path Picasso.get() - .load(baseUrl + avatarPath) - .into(binding.avatar) + .load(baseUrl + avatarPath) + .into(binding.avatar) } // set Name binding.slRowName.text = video.name @@ -395,9 +391,9 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi // set age and view count binding.videoMeta.text = getMetaString( - video.createdAt, - video.views, - context + video.createdAt, + video.views, + context ) // set owner @@ -431,8 +427,8 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi binding.moreButton.setOnClickListener { v: View? -> val popup = PopupMenu( - context, - v!! + context, + v!! ) popup.setOnMenuItemClickListener { menuItem: MenuItem -> when (menuItem.itemId) { @@ -492,17 +488,17 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi val apiBaseURL = APIUrlHelper.getUrlWithVersion(context) val videoDataService = RetrofitInstance.getRetrofitInstance( - apiBaseURL, APIUrlHelper.useInsecureConnection( + apiBaseURL, APIUrlHelper.useInsecureConnection( context - ) + ) ).create( - GetVideoDataService::class.java + GetVideoDataService::class.java ) val call = videoDataService.rateVideo(video.id, body) call.enqueue(object : Callback { override fun onResponse( - call: Call, - response: Response + call: Call, + response: Response ) { // if 20x, update likes/dislikes if (response.isSuccessful) { @@ -539,17 +535,17 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi override fun onFailure(call: Call, t: Throwable) { Toast.makeText( - context, - context.getString(string.video_rating_failed), - Toast.LENGTH_SHORT + context, + context.getString(string.video_rating_failed), + Toast.LENGTH_SHORT ).show() } }) } else { Toast.makeText( - context, - context.getString(string.video_login_required_for_service), - Toast.LENGTH_SHORT + context, + context.getString(string.video_login_required_for_service), + Toast.LENGTH_SHORT ).show() } } diff --git a/app/src/main/java/net/schueller/peertube/database/AppDatabase.java b/app/src/main/java/net/schueller/peertube/database/AppDatabase.java index 79833de..6f9807b 100644 --- a/app/src/main/java/net/schueller/peertube/database/AppDatabase.java +++ b/app/src/main/java/net/schueller/peertube/database/AppDatabase.java @@ -19,7 +19,9 @@ package net.schueller.peertube.database; import androidx.room.Database; import androidx.room.RoomDatabase; -@Database(entities = {Server.class}, version = 1) +@Database(entities = {Server.class, Video.class}, version = 1) public abstract class AppDatabase extends RoomDatabase { public abstract ServerDao serverDao(); + + public abstract VideoDao videoDao(); } \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/database/Video.kt b/app/src/main/java/net/schueller/peertube/database/Video.kt new file mode 100644 index 0000000..1ac17f9 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/Video.kt @@ -0,0 +1,21 @@ +package net.schueller.peertube.database + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.parcelize.Parcelize + +@Parcelize +@Entity(tableName = "watch_later") +data class Video( + @PrimaryKey(autoGenerate = true) + var id: Int = 0, + + @ColumnInfo(name = "video_name") + var videoName: String, + + @ColumnInfo(name = "video_description") + var videoDescription: String? + +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/database/VideoDao.kt b/app/src/main/java/net/schueller/peertube/database/VideoDao.kt new file mode 100644 index 0000000..276904d --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/VideoDao.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.database + +import androidx.lifecycle.LiveData +import androidx.room.* + +@Dao +interface VideoDao { + + @Insert + suspend fun insert(video: Video) + + @Update + suspend fun update(video: Video) + + @Query("DELETE FROM watch_later") + suspend fun deleteAll() + + @Delete + suspend fun delete(video: Video) + + @get:Query("SELECT * from watch_later ORDER BY video_name DESC") + val allVideos: LiveData> +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/database/VideoRepository.kt b/app/src/main/java/net/schueller/peertube/database/VideoRepository.kt new file mode 100644 index 0000000..8fc219f --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/VideoRepository.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.database + +import android.app.Application +import androidx.lifecycle.LiveData +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +internal class VideoRepository(application: Application) { + + private val mVideoDao: VideoDao + + val allVideos: LiveData> + get() = mVideoDao.allVideos + + init { + val db = VideoRoomDatabase.getDatabase(application) + mVideoDao = db.videoDao() + } + + suspend fun update(video: Video) = withContext(Dispatchers.IO) { + mVideoDao.update(video) + } + + suspend fun insert(video: Video) = withContext(Dispatchers.IO) { + mVideoDao.insert(video) + } + + suspend fun delete(video: Video) = withContext(Dispatchers.IO) { + mVideoDao.delete(video) + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/database/VideoRoomDatabase.java b/app/src/main/java/net/schueller/peertube/database/VideoRoomDatabase.java new file mode 100644 index 0000000..53f6647 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/VideoRoomDatabase.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.database; + +import android.content.Context; +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Database(entities = {Video.class}, version = 1, exportSchema = false) +public abstract class VideoRoomDatabase extends RoomDatabase { + + public abstract VideoDao videoDao(); + + private static volatile VideoRoomDatabase INSTANCE; + + private static final int NUMBER_OF_THREADS = 4; + + static final ExecutorService databaseWriteExecutor = + Executors.newFixedThreadPool(NUMBER_OF_THREADS); + + public static VideoRoomDatabase getDatabase(final Context context) { + if (INSTANCE == null) { + synchronized (VideoRoomDatabase.class) { + if (INSTANCE == null) { + if (INSTANCE == null) { + INSTANCE = Room.databaseBuilder(context.getApplicationContext(), + VideoRoomDatabase.class, "playlist_database") + .build(); + } + } + } + } + return INSTANCE; + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/database/VideoViewModel.kt b/app/src/main/java/net/schueller/peertube/database/VideoViewModel.kt new file mode 100644 index 0000000..d4fbe86 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/database/VideoViewModel.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.database + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch + +class VideoViewModel(application: Application) : AndroidViewModel(application) { + + private val mRepository: VideoRepository = VideoRepository(application) + val allVideos: LiveData> = mRepository.allVideos + + fun insert(video: Video) { + viewModelScope.launch { + mRepository.insert(video) + } + } + + fun update(video: Video) { + viewModelScope.launch { + mRepository.update(video) + } + } + + fun delete(video: Video) { + viewModelScope.launch { + mRepository.delete(video) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.kt b/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.kt index cac3793..f594bb1 100644 --- a/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.kt +++ b/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.kt @@ -16,54 +16,36 @@ */ package net.schueller.peertube.fragment -import android.Manifest -import net.schueller.peertube.helper.MetaDataHelper.getMetaString -import net.schueller.peertube.helper.MetaDataHelper.getOwnerString -import android.content.res.ColorStateList -import android.view.LayoutInflater -import android.view.ViewGroup -import android.os.Bundle -import net.schueller.peertube.R -import net.schueller.peertube.service.VideoPlayerService import android.app.Activity import android.content.Context -import net.schueller.peertube.helper.APIUrlHelper -import net.schueller.peertube.network.GetVideoDataService -import net.schueller.peertube.network.RetrofitInstance -import net.schueller.peertube.helper.ErrorHelper -import androidx.core.app.ActivityCompat -import android.content.pm.PackageManager +import android.content.res.ColorStateList +import android.os.Bundle import android.util.Log -import android.widget.Toast -import com.squareup.picasso.Picasso -import android.widget.TextView -import android.util.TypedValue +import android.view.LayoutInflater import android.view.View -import android.widget.Button -import android.widget.ImageView +import android.view.ViewGroup +import android.widget.TextView import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentTransaction +import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.mikepenz.iconics.Iconics +import net.schueller.peertube.R import net.schueller.peertube.adapter.MultiViewRecycleViewAdapter -import net.schueller.peertube.intents.Intents +import net.schueller.peertube.database.VideoViewModel +import net.schueller.peertube.helper.APIUrlHelper +import net.schueller.peertube.helper.ErrorHelper import net.schueller.peertube.model.CommentThread import net.schueller.peertube.model.Rating import net.schueller.peertube.model.Video import net.schueller.peertube.model.VideoList import net.schueller.peertube.model.ui.VideoMetaViewItem -import net.schueller.peertube.network.GetUserService -import net.schueller.peertube.network.Session -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.ResponseBody +import net.schueller.peertube.network.GetVideoDataService +import net.schueller.peertube.network.RetrofitInstance +import net.schueller.peertube.service.VideoPlayerService import retrofit2.Call import retrofit2.Callback import retrofit2.Response -import java.lang.Exception class VideoMetaDataFragment : Fragment() { private var videoRating: Rating? = null @@ -73,12 +55,14 @@ class VideoMetaDataFragment : Fragment() { private lateinit var videoDescriptionFragment: VideoDescriptionFragment + private val mVideoViewModel: VideoViewModel by activityViewModels() + var isLeaveAppExpected = false private set override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment @@ -94,11 +78,11 @@ class VideoMetaDataFragment : Fragment() { // show full description fragment videoDescriptionFragment = VideoDescriptionFragment.newInstance(video, this) childFragmentManager.beginTransaction() - .add(R.id.video_meta_data_fragment, videoDescriptionFragment, VideoDescriptionFragment.TAG).commit() + .add(R.id.video_meta_data_fragment, videoDescriptionFragment, VideoDescriptionFragment.TAG).commit() } fun hideDescriptionFragment() { - val fragment: Fragment? = childFragmentManager.findFragmentByTag(VideoDescriptionFragment.TAG) + val fragment: Fragment? = childFragmentManager.findFragmentByTag(VideoDescriptionFragment.TAG) if (fragment != null) { childFragmentManager.beginTransaction().remove(fragment).commit() } @@ -113,10 +97,10 @@ class VideoMetaDataFragment : Fragment() { val activity: Activity? = activity val apiBaseURL = APIUrlHelper.getUrlWithVersion(context) val videoDataService = RetrofitInstance.getRetrofitInstance( - apiBaseURL, - APIUrlHelper.useInsecureConnection(context) + apiBaseURL, + APIUrlHelper.useInsecureConnection(context) ).create( - GetVideoDataService::class.java + GetVideoDataService::class.java ) // related videos @@ -149,8 +133,8 @@ class VideoMetaDataFragment : Fragment() { videoOptions.setOnClickListener { val videoOptionsFragment = VideoOptionsFragment.newInstance(mService, video.files) videoOptionsFragment.show( - getActivity()!!.supportFragmentManager, - VideoOptionsFragment.TAG + getActivity()!!.supportFragmentManager, + VideoOptionsFragment.TAG ) } } @@ -166,9 +150,9 @@ class VideoMetaDataFragment : Fragment() { // We set this to default to null so that on initial start there are videos listed. val apiBaseURL = APIUrlHelper.getUrlWithVersion(context) val service = - RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create( - GetVideoDataService::class.java - ) + RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create( + GetVideoDataService::class.java + ) val call: Call = service.getCommentThreads(videoId, start, count, sort) call.enqueue(object : Callback { @@ -176,7 +160,7 @@ class VideoMetaDataFragment : Fragment() { if (response.body() != null) { val commentThread = response.body() if (commentThread != null) { - mMultiViewAdapter!!.setVideoComment(commentThread); + mMultiViewAdapter!!.setVideoComment(commentThread) } } } @@ -197,8 +181,8 @@ class VideoMetaDataFragment : Fragment() { val filter: String? = null val sharedPref = context?.getSharedPreferences( - context.packageName + "_preferences", - Context.MODE_PRIVATE + context.packageName + "_preferences", + Context.MODE_PRIVATE ) var nsfw = "false" @@ -211,9 +195,9 @@ class VideoMetaDataFragment : Fragment() { // We set this to default to null so that on initial start there are videos listed. val apiBaseURL = APIUrlHelper.getUrlWithVersion(context) val service = - RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create( - GetVideoDataService::class.java - ) + RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create( + GetVideoDataService::class.java + ) val call: Call = service.getVideosData(start, count, sort, nsfw, filter, languages) /*Log the URL called*/Log.d("URL Called", call.request().url.toString() + "") @@ -234,6 +218,12 @@ class VideoMetaDataFragment : Fragment() { } }) } + + fun saveToPlaylist(video: Video) { + val playlistVideo: net.schueller.peertube.database.Video = net.schueller.peertube.database.Video(videoName = video.name, videoDescription = video.description) + mVideoViewModel.insert(playlistVideo) + } + companion object { const val TAG = "VMDF" } From 96ec510f40ddb5e03fc1ce4c951056204ce11f35 Mon Sep 17 00:00:00 2001 From: digiwizkid Date: Sat, 8 Jan 2022 15:26:48 +0530 Subject: [PATCH 2/3] Playlist UI WIP --- app/src/main/AndroidManifest.xml | 105 ++++--- .../peertube/activity/MeActivity.java | 23 +- .../peertube/activity/PlaylistActivity.kt | 100 ++++++ .../peertube/adapter/PlaylistAdapter.kt | 63 ++++ app/src/main/res/layout/activity_me.xml | 293 +++++++++--------- app/src/main/res/layout/activity_playlist.xml | 44 +++ app/src/main/res/layout/row_playlist.xml | 43 +++ 7 files changed, 470 insertions(+), 201 deletions(-) create mode 100644 app/src/main/java/net/schueller/peertube/activity/PlaylistActivity.kt create mode 100644 app/src/main/java/net/schueller/peertube/adapter/PlaylistAdapter.kt create mode 100644 app/src/main/res/layout/activity_playlist.xml create mode 100644 app/src/main/res/layout/row_playlist.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a18f528..4e294b1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,78 +1,83 @@ + xmlns:tools="http://schemas.android.com/tools" + package="net.schueller.peertube"> - - - + + + + android:name=".application.AppApplication" + android:allowBackup="true" + android:fullBackupContent="@xml/backup_descriptor" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:supportsRtl="true" + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> + android:name=".activity.ServerAddressBookActivity" + android:label="@string/title_activity_server_address_book" + android:theme="@style/AppTheme.NoActionBar"/> + android:name=".activity.VideoListActivity" + android:launchMode="singleTop" + android:theme="@style/AppTheme.NoActionBar" + android:exported="true"> - - + + - + + android:name="android.app.searchable" + android:resource="@xml/searchable"/> + android:name=".activity.VideoPlayActivity" + android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" + android:label="@string/title_activity_video_play" + android:launchMode="singleInstance" + android:supportsPictureInPicture="true" + android:theme="@style/AppTheme.NoActionBar"/> + + + + android:name=".activity.SettingsActivity" + android:label="@string/title_activity_settings" + android:theme="@style/AppTheme.NoActionBar"/> + android:name=".activity.SearchServerActivity" + android:label="@string/title_activity_select_server" + android:theme="@style/AppTheme.NoActionBar"/> + android:name=".activity.MeActivity" + android:label="@string/title_activity_me" + android:theme="@style/AppTheme.NoActionBar"/> + android:name=".activity.AccountActivity" + android:label="@string/title_activity_account" + android:theme="@style/AppTheme.NoActionBar"/> + android:name=".provider.SearchSuggestionsProvider" + android:authorities="net.schueller.peertube.provider.SearchSuggestionsProvider" + android:enabled="true" + android:exported="false"/> - + + android:exported="true"> - + diff --git a/app/src/main/java/net/schueller/peertube/activity/MeActivity.java b/app/src/main/java/net/schueller/peertube/activity/MeActivity.java index 36388af..57bc0b4 100644 --- a/app/src/main/java/net/schueller/peertube/activity/MeActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/MeActivity.java @@ -27,7 +27,9 @@ import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; - +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import com.squareup.picasso.Picasso; import net.schueller.peertube.R; import net.schueller.peertube.helper.APIUrlHelper; import net.schueller.peertube.helper.ErrorHelper; @@ -36,18 +38,12 @@ import net.schueller.peertube.model.Me; import net.schueller.peertube.network.GetUserService; import net.schueller.peertube.network.RetrofitInstance; import net.schueller.peertube.network.Session; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.Toolbar; - -import com.squareup.picasso.Picasso; - -import java.util.Objects; - import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; +import java.util.Objects; + import static net.schueller.peertube.application.AppApplication.getContext; public class MeActivity extends CommonActivity { @@ -85,11 +81,16 @@ public class MeActivity extends CommonActivity { getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_baseline_close_24); LinearLayout account = findViewById(R.id.a_me_account_line); + LinearLayout playlist = findViewById(R.id.a_me_playlist); LinearLayout settings = findViewById(R.id.a_me_settings); LinearLayout help = findViewById(R.id.a_me_helpnfeedback); TextView logout = findViewById(R.id.a_me_logout); + playlist.setOnClickListener(view -> { + Intent playlistActivity = new Intent(getContext(), PlaylistActivity.class); + startActivity(playlistActivity); + }); settings.setOnClickListener(view -> { Intent settingsActivity = new Intent(getContext(), SettingsActivity.class); @@ -124,7 +125,7 @@ public class MeActivity extends CommonActivity { call.enqueue(new Callback() { - LinearLayout account = findViewById(R.id.a_me_account_line); + final LinearLayout account = findViewById(R.id.a_me_account_line); @Override public void onResponse(@NonNull Call call, @NonNull Response response) { @@ -162,7 +163,7 @@ public class MeActivity extends CommonActivity { @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - ErrorHelper.showToastFromCommunicationError( MeActivity.this, t ); + ErrorHelper.showToastFromCommunicationError(MeActivity.this, t); account.setVisibility(View.GONE); } }); diff --git a/app/src/main/java/net/schueller/peertube/activity/PlaylistActivity.kt b/app/src/main/java/net/schueller/peertube/activity/PlaylistActivity.kt new file mode 100644 index 0000000..646b37b --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/activity/PlaylistActivity.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.activity + +import android.app.AlertDialog +import android.content.DialogInterface +import android.os.Bundle +import android.widget.Toast +import androidx.activity.viewModels +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import net.schueller.peertube.R +import net.schueller.peertube.adapter.PlaylistAdapter +import net.schueller.peertube.database.Video +import net.schueller.peertube.database.VideoViewModel +import net.schueller.peertube.databinding.ActivityPlaylistBinding + +class PlaylistActivity : CommonActivity() { + + private val TAG = "PlaylistAct" + + private val mVideoViewModel: VideoViewModel by viewModels() + + private lateinit var mBinding: ActivityPlaylistBinding + + override fun onSupportNavigateUp(): Boolean { + finish() // close this activity as oppose to navigating up + return false + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mBinding = ActivityPlaylistBinding.inflate(layoutInflater) + setContentView(mBinding.root) + + // Setting toolbar as the ActionBar with setSupportActionBar() call + setSupportActionBar(mBinding.toolBarServerAddressBook) + supportActionBar?.apply { + setDisplayHomeAsUpEnabled(true) + setHomeAsUpIndicator(R.drawable.ic_baseline_close_24) + } + + showServers() + } + + private fun onVideoClick(video: Video) { + Toast.makeText(this, "Clicked", Toast.LENGTH_SHORT).show() + } + + private fun showServers() { + val adapter = PlaylistAdapter(mutableListOf(), { onVideoClick(it) }).also { + mBinding.serverListRecyclerview.adapter = it + } + + // Delete items on swipe + val helper = ItemTouchHelper( + object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { + override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { + return false + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + AlertDialog.Builder(this@PlaylistActivity) + .setTitle("Remove Video") + .setMessage("Are you sure you want to remove this video from playlist?") + .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> + val position = viewHolder.bindingAdapterPosition + val video = adapter.getVideoAtPosition(position) + // Delete the video + mVideoViewModel.delete(video) + } + .setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> adapter.notifyItemChanged(viewHolder.bindingAdapterPosition) } + .setIcon(android.R.drawable.ic_dialog_alert) + .show() + } + }) + helper.attachToRecyclerView(mBinding.serverListRecyclerview) + + // Update the cached copy of the words in the adapter. + mVideoViewModel.allVideos.observe(this, { videos: List