From 373b297f97cdcc56cf2e519e55ac310c219a2131 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Thu, 26 Oct 2023 15:57:17 +0200 Subject: [PATCH] feat: add undo action after removing video from playlist --- .../libretube/ui/adapters/PlaylistAdapter.kt | 68 +++++++++++++++---- .../ui/fragments/PlaylistFragment.kt | 2 +- .../com/github/libretube/util/TextUtils.kt | 5 ++ app/src/main/res/values/strings.xml | 1 + 4 files changed, 62 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/github/libretube/ui/adapters/PlaylistAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/PlaylistAdapter.kt index 7eac58b45..3343df08a 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/PlaylistAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/PlaylistAdapter.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.util.Log import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.core.view.isGone @@ -27,9 +28,12 @@ import com.github.libretube.ui.extensions.setWatchProgressLength import com.github.libretube.ui.sheets.VideoOptionsBottomSheet import com.github.libretube.ui.sheets.VideoOptionsBottomSheet.Companion.VIDEO_OPTIONS_SHEET_REQUEST_KEY import com.github.libretube.ui.viewholders.PlaylistViewHolder +import com.github.libretube.util.TextUtils +import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * @param originalFeed original, unsorted feed, needed in order to delete the proper video from @@ -112,29 +116,67 @@ class PlaylistAdapter( } } - fun removeFromPlaylist(context: Context, position: Int) { + fun removeFromPlaylist(rootView: View, sortedFeedPosition: Int) { + val video = sortedFeed[sortedFeedPosition] + // get the index of the video in the playlist // could vary due to playlist sorting by the user - val playlistIndex = originalFeed.indexOfFirst { - it.url == sortedFeed[position].url - }.takeIf { it >= 0 } ?: return + val originalPlaylistPosition = originalFeed + .indexOfFirst { it.url == video.url } + .takeIf { it >= 0 } ?: return - originalFeed.removeAt(playlistIndex) - sortedFeed.removeAt(position) - visibleCount -= 1 - (context as Activity).runOnUiThread { - notifyItemRemoved(position) - notifyItemRangeChanged(position, itemCount) + sortedFeed.removeAt(sortedFeedPosition) + originalFeed.removeAt(originalPlaylistPosition) + visibleCount-- + + (rootView.context as Activity).runOnUiThread { + notifyItemRemoved(sortedFeedPosition) + notifyItemRangeChanged(sortedFeedPosition, itemCount) } - val appContext = context.applicationContext + val appContext = rootView.context.applicationContext - CoroutineScope(Dispatchers.IO).launch { + // try to remove the video from the playlist and show an undo snackbar if successful + CoroutineScope(Dispatchers.Main).launch { try { - PlaylistsHelper.removeFromPlaylist(playlistId, playlistIndex) + withContext(Dispatchers.IO) { + PlaylistsHelper.removeFromPlaylist(playlistId, originalPlaylistPosition) + } + + val shortTitle = TextUtils.limitTextToLength(video.title.orEmpty(), 50) + val snackBarText = rootView.context.getString(R.string.successfully_removed_from_playlist, shortTitle) + Snackbar.make(rootView, snackBarText, Snackbar.LENGTH_LONG) + .setTextMaxLines(3) + .setAction(R.string.undo) { + reAddToPlaylist(appContext, video, sortedFeedPosition, originalPlaylistPosition) + } + .show() } catch (e: Exception) { Log.e(TAG(), e.toString()) appContext.toastFromMainDispatcher(R.string.unknown_error) } } } + + private fun reAddToPlaylist( + context: Context, + streamItem: StreamItem, + sortedFeedPosition: Int, + originalPlaylistPosition: Int + ) { + CoroutineScope(Dispatchers.IO).launch { + try { + PlaylistsHelper.addToPlaylist(playlistId, streamItem) + sortedFeed.add(sortedFeedPosition, streamItem) + originalFeed.add(originalPlaylistPosition, streamItem) + visibleCount++ + + withContext(Dispatchers.Main) { + notifyItemInserted(sortedFeedPosition) + } + } catch (e: Exception) { + Log.e(TAG(), e.toString()) + context.toastFromMainDispatcher(R.string.unknown_error) + } + } + } } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt index 5d5bdd378..626373e18 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt @@ -358,7 +358,7 @@ class PlaylistFragment : Fragment() { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { playlistAdapter!!.removeFromPlaylist( - requireContext(), + _binding?.root ?: return, viewHolder.absoluteAdapterPosition ) } diff --git a/app/src/main/java/com/github/libretube/util/TextUtils.kt b/app/src/main/java/com/github/libretube/util/TextUtils.kt index 0f0741924..d9540e57d 100644 --- a/app/src/main/java/com/github/libretube/util/TextUtils.kt +++ b/app/src/main/java/com/github/libretube/util/TextUtils.kt @@ -102,4 +102,9 @@ object TextUtils { bitrate ?: return "" return "${bitrate / 1024}kbps" } + + fun limitTextToLength(text: String, maxLength: Int): String { + if (text.length <= maxLength) return text + return text.take(maxLength) + "…" + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4426cb2d3..2aad149fa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -491,6 +491,7 @@ SponsorBlock UserID Filename too long! Remember playback speed + Successfully removed \"%1$s\" from the playlist. Download Service