From 66c95f901dfa31c193375abfdef8028f527834de Mon Sep 17 00:00:00 2001 From: Grady Clark <50427197+GradyClark@users.noreply.github.com> Date: Fri, 7 Feb 2020 23:57:36 -0600 Subject: [PATCH 01/13] Added the ability to remove all watched videos from local playlists Changes: - local_playlist_control.xml * A copy of playlist_control.xml * To hold the 'Remove Watched Videos' buttton - local_playlist_header.xml * Changed the include layout to now include local_playlist_control.xml - strings.xml * added string 'remove_watched' with value 'Remove Watched' - LocalPlaylistFragment.java * Added the functionality to remove watched videos, to the 'Remove Watched Videos' button in local_playlist_control.xml. In the background via AsyncTask. This will also change the playlist's thumbnail, if the thumbnail video is removed. Tested on: - Pixel --- .../local/playlist/LocalPlaylistFragment.java | 89 +++++++++++++- .../res/layout/local_playlist_control.xml | 115 ++++++++++++++++++ .../main/res/layout/local_playlist_header.xml | 2 +- app/src/main/res/values/strings.xml | 4 +- 4 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/layout/local_playlist_control.xml diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index d430afa5c..a3611fa85 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -2,6 +2,7 @@ package org.schabi.newpipe.local.playlist; import android.app.Activity; import android.content.Context; +import android.os.AsyncTask; import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; @@ -24,11 +25,13 @@ import org.reactivestreams.Subscription; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; +import org.schabi.newpipe.database.history.model.StreamHistoryEntry; import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.local.BaseLocalListFragment; +import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.report.UserAction; @@ -39,11 +42,13 @@ import org.schabi.newpipe.util.StreamDialogEntry; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import icepick.State; +import io.reactivex.Flowable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; @@ -71,6 +76,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); + headerRemoveWatchedButton.setOnClickListener( + view -> { + //Solution, Scorched Earth, Copy non duplicates, clear playlist, then copy back over + //Other options didn't work as intended, or crashed. Like deleteItem(playlist_item) crashes when called in this function. + new RemoveWatchedStreams().execute(); + } + ); headerPopupButton.setOnLongClickListener(view -> { NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true); @@ -684,5 +702,74 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { + List localItems = new ArrayList<>(); + Long RemovedItemCount = 0l; + boolean thumbNailVideoRemoved = false; + + @Override + protected void onPreExecute() { + super.onPreExecute(); + showLoading(); + localItems.clear(); + } + + @Override + protected Long doInBackground(String... urls) { + + HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); + Iterator it_history; + StreamHistoryEntry history_item; + + Flowable> playlist = playlistManager.getPlaylistStreams(playlistId); + Iterator it_playlist = playlist.blockingFirst().iterator(); + PlaylistStreamEntry playlist_item = null; + + boolean isNonDuplicate; + + while (it_playlist.hasNext()) { + playlist_item = it_playlist.next(); + + it_history = recordManager.getStreamHistory().blockingFirst().iterator(); + + isNonDuplicate = true; + while (it_history.hasNext()) { + history_item = it_history.next(); + if (history_item.streamId == playlist_item.streamId) { + isNonDuplicate = false; + break; + } + } + if (isNonDuplicate) { + localItems.add(playlist_item); + } + else + { + RemovedItemCount++; + if(playlistManager.getPlaylistThumbnail(playlistId).equals(playlist_item.thumbnailUrl)) + { + thumbNailVideoRemoved = true; + } + } + } + return this.RemovedItemCount; + } + + @Override + protected void onPostExecute(Long result) { + itemListAdapter.clearStreamItemList(); + itemListAdapter.addItems(localItems); + localItems.clear(); + + if (thumbNailVideoRemoved) + updateThumbnailUrl(); + + setVideoCount(itemListAdapter.getItemsList().size()); + + saveChanges(); + hideLoading(); + } + } } diff --git a/app/src/main/res/layout/local_playlist_control.xml b/app/src/main/res/layout/local_playlist_control.xml new file mode 100644 index 000000000..8465c9ba5 --- /dev/null +++ b/app/src/main/res/layout/local_playlist_control.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/local_playlist_header.xml b/app/src/main/res/layout/local_playlist_header.xml index 420da04ee..372187a64 100644 --- a/app/src/main/res/layout/local_playlist_header.xml +++ b/app/src/main/res/layout/local_playlist_header.xml @@ -50,7 +50,7 @@ android:layout_height="wrap_content" android:layout_below="@id/playlist_stream_count"> - + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ef09be9f4..770d3d4e8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -637,4 +637,6 @@ Enable fast mode Disable fast mode Do you think feed loading is too slow? If so, try enabling fast loading (you can change it in settings or by pressing the button below).\n\nNewPipe offers two feed loading strategies:\n• Fetching the whole subscription channel, which is slow but complete.\n• Using a dedicated service endpoint, which is fast but usually not complete.\n\nThe difference between the two is that the fast one usually lacks some information, like the item\'s duration or type (can\'t distinguish between live videos and normal ones) and it may return less items.\n\nYouTube is an example of a service that offers this fast method with its RSS feed.\n\nSo the choice boils down to what you prefer: speed or precise information. - \ No newline at end of file + + Remove Watched + From 954399b2557eb43976dd344daee25b53d629811e Mon Sep 17 00:00:00 2001 From: Grady Clark <50427197+GradyClark@users.noreply.github.com> Date: Thu, 5 Mar 2020 07:49:04 -0600 Subject: [PATCH 02/13] Moved the 'Remove Watched' button to the three dot menu button. --- .../local/playlist/LocalPlaylistFragment.java | 49 +++++++++++++------ .../res/layout/local_playlist_control.xml | 33 ++----------- app/src/main/res/menu/menu_local_playlist.xml | 10 ++++ 3 files changed, 47 insertions(+), 45 deletions(-) create mode 100644 app/src/main/res/menu/menu_local_playlist.xml diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index a3611fa85..c68cd2f55 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -8,6 +8,9 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; @@ -76,7 +79,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment result) { super.handleResult(result); @@ -369,13 +389,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); - headerRemoveWatchedButton.setOnClickListener( - view -> { - //Solution, Scorched Earth, Copy non duplicates, clear playlist, then copy back over - //Other options didn't work as intended, or crashed. Like deleteItem(playlist_item) crashes when called in this function. - new RemoveWatchedStreams().execute(); - } - ); headerPopupButton.setOnLongClickListener(view -> { NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true); @@ -706,7 +719,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { List localItems = new ArrayList<>(); Long RemovedItemCount = 0l; - boolean thumbNailVideoRemoved = false; + boolean thumbnailVideoRemoved = false; @Override protected void onPreExecute() { @@ -749,7 +762,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment + + + - - - - - - - - diff --git a/app/src/main/res/menu/menu_local_playlist.xml b/app/src/main/res/menu/menu_local_playlist.xml new file mode 100644 index 000000000..4c499a51d --- /dev/null +++ b/app/src/main/res/menu/menu_local_playlist.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file From 8cab79003052ef8b1b0bd3782498a04ce9b287c9 Mon Sep 17 00:00:00 2001 From: Grady Clark <50427197+GradyClark@users.noreply.github.com> Date: Sun, 8 Mar 2020 07:42:52 -0500 Subject: [PATCH 03/13] - Will now use ReactiveX instead of AsyncTask, when removing watched videos. - Removed redundant file 'local_playlist_control' - Fixed grammer issue --- .../local/playlist/LocalPlaylistFragment.java | 45 ++++++---- .../res/layout/local_playlist_control.xml | 90 ------------------- .../main/res/layout/local_playlist_header.xml | 2 +- app/src/main/res/values/strings.xml | 3 +- 4 files changed, 31 insertions(+), 109 deletions(-) delete mode 100644 app/src/main/res/layout/local_playlist_control.xml diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index c68cd2f55..315056f6c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -2,7 +2,6 @@ package org.schabi.newpipe.local.playlist; import android.app.Activity; import android.content.Context; -import android.os.AsyncTask; import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; @@ -56,6 +55,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposables; +import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -79,7 +79,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment debouncedSaveSignal; private CompositeDisposable disposables; + private Disposable removeWatchedWorker; /* Has the playlist been fully loaded from db */ private AtomicBoolean isLoadingComplete; @@ -151,7 +152,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist) -> { + remover.doInBackground(playlist); + return true; + } + ).observeOn(AndroidSchedulers.mainThread()) + .subscribe(playlist -> { + remover.onPostExecute(); + }, (@io.reactivex.annotations.NonNull Throwable throwable) -> { + onError(throwable); + }); break; default: return super.onOptionsItemSelected(item); @@ -716,27 +732,24 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { + private class RemoveWatchedStreams{ List localItems = new ArrayList<>(); Long RemovedItemCount = 0l; boolean thumbnailVideoRemoved = false; - @Override + // Do this in the main thread protected void onPreExecute() { - super.onPreExecute(); showLoading(); - localItems.clear(); } - @Override - protected Long doInBackground(String... urls) { + // Do not do this in the main thread + protected Long doInBackground(List playlist) { HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); Iterator it_history; StreamHistoryEntry history_item; - Flowable> playlist = playlistManager.getPlaylistStreams(playlistId); - Iterator it_playlist = playlist.blockingFirst().iterator(); + Iterator it_playlist = playlist.iterator(); PlaylistStreamEntry playlist_item = null; boolean isNonDuplicate; @@ -769,8 +782,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/local_playlist_header.xml b/app/src/main/res/layout/local_playlist_header.xml index 372187a64..04fe32ab0 100644 --- a/app/src/main/res/layout/local_playlist_header.xml +++ b/app/src/main/res/layout/local_playlist_header.xml @@ -50,7 +50,7 @@ android:layout_height="wrap_content" android:layout_below="@id/playlist_stream_count"> - + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 770d3d4e8..e0591e7fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -593,6 +593,7 @@ Choose an instance App language System default + Remove watched Due to ExoPlayer constraints the seek duration was set to %d seconds @@ -637,6 +638,4 @@ Enable fast mode Disable fast mode Do you think feed loading is too slow? If so, try enabling fast loading (you can change it in settings or by pressing the button below).\n\nNewPipe offers two feed loading strategies:\n• Fetching the whole subscription channel, which is slow but complete.\n• Using a dedicated service endpoint, which is fast but usually not complete.\n\nThe difference between the two is that the fast one usually lacks some information, like the item\'s duration or type (can\'t distinguish between live videos and normal ones) and it may return less items.\n\nYouTube is an example of a service that offers this fast method with its RSS feed.\n\nSo the choice boils down to what you prefer: speed or precise information. - - Remove Watched From 98fc88dec6daa0a0ef5b8507bc1a03569b163543 Mon Sep 17 00:00:00 2001 From: Grady Clark <50427197+GradyClark@users.noreply.github.com> Date: Mon, 16 Mar 2020 16:08:21 -0500 Subject: [PATCH 04/13] Simplified the removal of watched videos, into the function `removeWatchedStreams` Replaced unnecessary nested class. Fixed formating issues --- .../local/playlist/LocalPlaylistFragment.java | 164 +++++++++--------- 1 file changed, 78 insertions(+), 86 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 315056f6c..8813f7d35 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -300,7 +300,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist) -> { - remover.doInBackground(playlist); - return true; - } - ).observeOn(AndroidSchedulers.mainThread()) - .subscribe(playlist -> { - remover.onPostExecute(); - }, (@io.reactivex.annotations.NonNull Throwable throwable) -> { - onError(throwable); - }); + removeWatchedStreams(); break; default: return super.onOptionsItemSelected(item); @@ -378,6 +364,82 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist) -> { + List localItems = new ArrayList<>(); + boolean thumbnailVideoRemoved = false; + Long removedItemCount = 0l; + + HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); + Iterator it_history; + StreamHistoryEntry history_item; + + Iterator it_playlist = playlist.iterator(); + PlaylistStreamEntry playlist_item = null; + + boolean isNonDuplicate; + + while (it_playlist.hasNext()) { + playlist_item = it_playlist.next(); + + it_history = recordManager.getStreamHistory().blockingFirst().iterator(); + + isNonDuplicate = true; + while (it_history.hasNext()) { + history_item = it_history.next(); + if (history_item.streamId == playlist_item.streamId) { + isNonDuplicate = false; + break; + } + } + if (isNonDuplicate) { + localItems.add(playlist_item); + } else { + removedItemCount++; + if (playlistManager.getPlaylistThumbnail(playlistId).equals(playlist_item.thumbnailUrl)) { + thumbnailVideoRemoved = true; + } + } + } + return Flowable.just(localItems, removedItemCount, thumbnailVideoRemoved); + } + ) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + flow -> { + List localItems = (List) flow.blockingFirst(); + Boolean thumbnailVideoRemoved = (Boolean) flow.blockingLast(); + + itemListAdapter.clearStreamItemList(); + itemListAdapter.addItems(localItems); + localItems.clear(); + + if (thumbnailVideoRemoved) + updateThumbnailUrl(); + + int amountOfVideos = itemListAdapter.getItemsList().size(); + setVideoCount(amountOfVideos); + + saveChanges(); + hideLoading(); + + if (amountOfVideos == 0) + showEmptyState(); + }, (@io.reactivex.annotations.NonNull Throwable throwable) -> { + onError(throwable); + } + ); + } + @Override public void handleResult(@NonNull final List result) { super.handleResult(result); @@ -731,75 +793,5 @@ public class LocalPlaylistFragment extends BaseLocalListFragment localItems = new ArrayList<>(); - Long RemovedItemCount = 0l; - boolean thumbnailVideoRemoved = false; - - // Do this in the main thread - protected void onPreExecute() { - showLoading(); - } - - // Do not do this in the main thread - protected Long doInBackground(List playlist) { - - HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); - Iterator it_history; - StreamHistoryEntry history_item; - - Iterator it_playlist = playlist.iterator(); - PlaylistStreamEntry playlist_item = null; - - boolean isNonDuplicate; - - while (it_playlist.hasNext()) { - playlist_item = it_playlist.next(); - - it_history = recordManager.getStreamHistory().blockingFirst().iterator(); - - isNonDuplicate = true; - while (it_history.hasNext()) { - history_item = it_history.next(); - if (history_item.streamId == playlist_item.streamId) { - isNonDuplicate = false; - break; - } - } - if (isNonDuplicate) { - localItems.add(playlist_item); - } - else - { - RemovedItemCount++; - if(playlistManager.getPlaylistThumbnail(playlistId).equals(playlist_item.thumbnailUrl)) - { - thumbnailVideoRemoved = true; - } - } - } - return this.RemovedItemCount; - } - - // Do this in the main thread - protected void onPostExecute() { - itemListAdapter.clearStreamItemList(); - itemListAdapter.addItems(localItems); - localItems.clear(); - - if (thumbnailVideoRemoved) - updateThumbnailUrl(); - - int amountOfVideos = itemListAdapter.getItemsList().size(); - setVideoCount(amountOfVideos); - - saveChanges(); - hideLoading(); - - if(amountOfVideos == 0) - showEmptyState(); - } - } } From 0ac2865b7484b64b00a2da1178d015ea06d831c0 Mon Sep 17 00:00:00 2001 From: developer <50427197+GradyClark@users.noreply.github.com> Date: Wed, 18 Mar 2020 01:49:46 -0500 Subject: [PATCH 05/13] Optimised 'removeWatchedStreams' Removed merge mistake Reordered code Refactored 'removeWatchedWorker' to 'removeWatchedDisposable' --- .../local/playlist/LocalPlaylistFragment.java | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 8813f7d35..1b52705b2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -87,7 +87,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment debouncedSaveSignal; private CompositeDisposable disposables; - private Disposable removeWatchedWorker; + private Disposable removeWatchedDisposable; /* Has the playlist been fully loaded from db */ private AtomicBoolean isLoadingComplete; @@ -300,12 +300,12 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist) -> { List localItems = new ArrayList<>(); @@ -380,36 +380,42 @@ public class LocalPlaylistFragment extends BaseLocalListFragment it_history; - StreamHistoryEntry history_item; Iterator it_playlist = playlist.iterator(); PlaylistStreamEntry playlist_item = null; boolean isNonDuplicate; - while (it_playlist.hasNext()) { + Iterator it_history = recordManager.getStreamHistory().blockingFirst().iterator(); + ArrayList history_streamIds = new ArrayList<>(); + + while(it_history.hasNext()) + { + history_streamIds.add(it_history.next().getStreamId()); + } + + while(it_playlist.hasNext()) + { playlist_item = it_playlist.next(); - - it_history = recordManager.getStreamHistory().blockingFirst().iterator(); - isNonDuplicate = true; - while (it_history.hasNext()) { - history_item = it_history.next(); - if (history_item.streamId == playlist_item.streamId) { + + for (long history_id : history_streamIds) { + if (history_id == playlist_item.getStreamId()) { isNonDuplicate = false; break; } } + if (isNonDuplicate) { localItems.add(playlist_item); } else { removedItemCount++; - if (playlistManager.getPlaylistThumbnail(playlistId).equals(playlist_item.thumbnailUrl)) { + if (!thumbnailVideoRemoved && playlistManager.getPlaylistThumbnail(playlistId).equals(playlist_item.getStreamEntity().getThumbnailUrl())) { thumbnailVideoRemoved = true; } } } + return Flowable.just(localItems, removedItemCount, thumbnailVideoRemoved); } ) From 9c3f138b8e30e17bad4530e4b85b81fcec4f44bf Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 3 Apr 2020 18:58:47 +0200 Subject: [PATCH 06/13] Use binary search to remove watched items from playlists --- .../history/dao/StreamHistoryDAO.java | 7 ++ .../local/history/HistoryRecordManager.java | 4 + .../local/playlist/LocalPlaylistFragment.java | 96 ++++++++----------- 3 files changed, 49 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java index 7daf21e6e..c716a2d91 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java @@ -49,6 +49,13 @@ public abstract class StreamHistoryDAO implements HistoryDAO> getHistory(); + + @Query("SELECT * FROM " + STREAM_TABLE + + " INNER JOIN " + STREAM_HISTORY_TABLE + + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID + + " ORDER BY " + STREAM_ID + " ASC") + public abstract Flowable> getHistorySortedById(); + @Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1") @Nullable diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java index ba90ae05a..96a385ca8 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java @@ -120,6 +120,10 @@ public class HistoryRecordManager { return streamHistoryTable.getHistory().subscribeOn(Schedulers.io()); } + public Flowable> getStreamHistorySortedById() { + return streamHistoryTable.getHistorySortedById().subscribeOn(Schedulers.io()); + } + public Flowable> getStreamStatistics() { return streamHistoryTable.getStatistics().subscribeOn(Schedulers.io()); } diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 1b52705b2..edfdc022f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -375,74 +375,54 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist) -> { - List localItems = new ArrayList<>(); - boolean thumbnailVideoRemoved = false; - Long removedItemCount = 0l; + HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); + Iterator playlistIter = playlist.iterator(); + Iterator historyIter = recordManager + .getStreamHistorySortedById().blockingFirst().iterator(); - HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); + // already sorted by ^ getStreamHistorySortedById(), binary search can be used + ArrayList historyStreamIds = new ArrayList<>(); + while(historyIter.hasNext()) { + historyStreamIds.add(historyIter.next().getStreamId()); + } - Iterator it_playlist = playlist.iterator(); - PlaylistStreamEntry playlist_item = null; + List notWatchedItems = new ArrayList<>(); + boolean thumbnailVideoRemoved = false; + while(playlistIter.hasNext()) { + PlaylistStreamEntry playlistItem = playlistIter.next(); + int indexInHistory = Collections.binarySearch(historyStreamIds, playlistItem.getStreamId()); - boolean isNonDuplicate; - - Iterator it_history = recordManager.getStreamHistory().blockingFirst().iterator(); - ArrayList history_streamIds = new ArrayList<>(); - - while(it_history.hasNext()) - { - history_streamIds.add(it_history.next().getStreamId()); - } - - while(it_playlist.hasNext()) - { - playlist_item = it_playlist.next(); - isNonDuplicate = true; - - for (long history_id : history_streamIds) { - if (history_id == playlist_item.getStreamId()) { - isNonDuplicate = false; - break; - } - } - - if (isNonDuplicate) { - localItems.add(playlist_item); - } else { - removedItemCount++; - if (!thumbnailVideoRemoved && playlistManager.getPlaylistThumbnail(playlistId).equals(playlist_item.getStreamEntity().getThumbnailUrl())) { - thumbnailVideoRemoved = true; - } - } - } - - return Flowable.just(localItems, removedItemCount, thumbnailVideoRemoved); + if (indexInHistory < 0) { + notWatchedItems.add(playlistItem); + } else if (!thumbnailVideoRemoved && playlistManager.getPlaylistThumbnail(playlistId).equals(playlistItem.getStreamEntity().getThumbnailUrl())) { + thumbnailVideoRemoved = true; } - ) + } + + return Flowable.just(notWatchedItems, thumbnailVideoRemoved); + }) .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - flow -> { - List localItems = (List) flow.blockingFirst(); - Boolean thumbnailVideoRemoved = (Boolean) flow.blockingLast(); + .subscribe(flow -> { + List notWatchedItems = (List) flow.blockingFirst(); + boolean thumbnailVideoRemoved = (Boolean) flow.blockingLast(); - itemListAdapter.clearStreamItemList(); - itemListAdapter.addItems(localItems); - localItems.clear(); + itemListAdapter.clearStreamItemList(); + itemListAdapter.addItems(notWatchedItems); + saveChanges(); - if (thumbnailVideoRemoved) - updateThumbnailUrl(); - int amountOfVideos = itemListAdapter.getItemsList().size(); - setVideoCount(amountOfVideos); + if (thumbnailVideoRemoved) { + updateThumbnailUrl(); + } - saveChanges(); - hideLoading(); + long videoCount = itemListAdapter.getItemsList().size(); + setVideoCount(videoCount); + if (videoCount == 0) { + showEmptyState(); + } - if (amountOfVideos == 0) - showEmptyState(); - }, (@io.reactivex.annotations.NonNull Throwable throwable) -> { - onError(throwable); - } + hideLoading(); + }, this::onError ); } From 6571fdbaa282be16fa2675623ba5af496ef6f39b Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 3 Apr 2020 20:13:56 +0200 Subject: [PATCH 07/13] Fix checkstyle errors --- .../local/playlist/LocalPlaylistFragment.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index edfdc022f..0881c6b97 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -79,7 +79,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist) -> { HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); @@ -382,19 +387,22 @@ public class LocalPlaylistFragment extends BaseLocalListFragment historyStreamIds = new ArrayList<>(); - while(historyIter.hasNext()) { + while (historyIter.hasNext()) { historyStreamIds.add(historyIter.next().getStreamId()); } List notWatchedItems = new ArrayList<>(); boolean thumbnailVideoRemoved = false; - while(playlistIter.hasNext()) { + while (playlistIter.hasNext()) { PlaylistStreamEntry playlistItem = playlistIter.next(); - int indexInHistory = Collections.binarySearch(historyStreamIds, playlistItem.getStreamId()); + int indexInHistory = Collections.binarySearch(historyStreamIds, + playlistItem.getStreamId()); if (indexInHistory < 0) { notWatchedItems.add(playlistItem); - } else if (!thumbnailVideoRemoved && playlistManager.getPlaylistThumbnail(playlistId).equals(playlistItem.getStreamEntity().getThumbnailUrl())) { + } else if (!thumbnailVideoRemoved + && playlistManager.getPlaylistThumbnail(playlistId) + .equals(playlistItem.getStreamEntity().getThumbnailUrl())) { thumbnailVideoRemoved = true; } } @@ -403,7 +411,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { - List notWatchedItems = (List) flow.blockingFirst(); + List notWatchedItems = + (List) flow.blockingFirst(); boolean thumbnailVideoRemoved = (Boolean) flow.blockingLast(); itemListAdapter.clearStreamItemList(); From 92ca1e6e0910178d82063e275dfd6e77c46de27e Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 3 Apr 2020 20:34:01 +0200 Subject: [PATCH 08/13] Check if already running before removing watched --- .../local/playlist/LocalPlaylistFragment.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 0881c6b97..8e8ad44ec 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -369,12 +369,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment Date: Tue, 14 Apr 2020 13:15:07 -0500 Subject: [PATCH 09/13] Changed "Remove Watched": - Will now execute on the io thread - Added confirmation dialog - Warning the user, and asking if they also want to remove partially watched videos --- .../local/playlist/LocalPlaylistFragment.java | 76 ++++++++++++++----- app/src/main/res/values/strings.xml | 5 ++ 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 8e8ad44ec..6af10bf21 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -2,6 +2,7 @@ package org.schabi.newpipe.local.playlist; import android.app.Activity; import android.content.Context; +import android.content.DialogInterface; import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; @@ -29,6 +30,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.history.model.StreamHistoryEntry; import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; +import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.info_list.InfoItemDialog; @@ -360,7 +362,18 @@ public class LocalPlaylistFragment extends BaseLocalListFragment removeWatchedStreams(false)) + .setNeutralButton( + R.string.remove_watched_popup_yes_and_partially_watched_videos, + (DialogInterface d, int id) -> removeWatchedStreams(true)) + .setNegativeButton(R.string.remove_watched_popup_cancel, + (DialogInterface d, int id) -> d.cancel()); + builder.create().show(); break; default: return super.onOptionsItemSelected(item); @@ -368,21 +381,32 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist) -> { + removeWatchedDisposable = Flowable.just(Flowable.just(removePartiallyWatched, + playlistManager.getPlaylistStreams(playlistId).blockingFirst())) + .subscribeOn(Schedulers.io()) + .map(flow -> { + boolean localRemovePartiallyWatched = (boolean) flow.blockingFirst(); + List playlist + = (List) flow.blockingLast(); HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); Iterator playlistIter = playlist.iterator(); Iterator historyIter = recordManager .getStreamHistorySortedById().blockingFirst().iterator(); + List notWatchedItems = new ArrayList<>(); + Iterator streamStatesIter = null; + boolean thumbnailVideoRemoved = false; + + if (!localRemovePartiallyWatched) { + streamStatesIter = recordManager.loadLocalStreamStateBatch(playlist) + .blockingGet().iterator(); + } // already sorted by ^ getStreamHistorySortedById(), binary search can be used ArrayList historyStreamIds = new ArrayList<>(); @@ -390,19 +414,35 @@ public class LocalPlaylistFragment extends BaseLocalListFragment notWatchedItems = new ArrayList<>(); - boolean thumbnailVideoRemoved = false; - while (playlistIter.hasNext()) { - PlaylistStreamEntry playlistItem = playlistIter.next(); - int indexInHistory = Collections.binarySearch(historyStreamIds, - playlistItem.getStreamId()); + if (localRemovePartiallyWatched) { + while (playlistIter.hasNext()) { + PlaylistStreamEntry playlistItem = playlistIter.next(); + int indexInHistory = Collections.binarySearch(historyStreamIds, + playlistItem.getStreamId()); - if (indexInHistory < 0) { - notWatchedItems.add(playlistItem); - } else if (!thumbnailVideoRemoved - && playlistManager.getPlaylistThumbnail(playlistId) - .equals(playlistItem.getStreamEntity().getThumbnailUrl())) { - thumbnailVideoRemoved = true; + if (indexInHistory < 0) { + notWatchedItems.add(playlistItem); + } else if (!thumbnailVideoRemoved + && playlistManager.getPlaylistThumbnail(playlistId) + .equals(playlistItem.getStreamEntity().getThumbnailUrl())) { + thumbnailVideoRemoved = true; + } + } + } else { + boolean hasState = false; + while (playlistIter.hasNext()) { + PlaylistStreamEntry playlistItem = playlistIter.next(); + int indexInHistory = Collections.binarySearch(historyStreamIds, + playlistItem.getStreamId()); + + hasState = streamStatesIter.next() != null; + if (indexInHistory < 0 || hasState) { + notWatchedItems.add(playlistItem); + } else if (!thumbnailVideoRemoved + && playlistManager.getPlaylistThumbnail(playlistId) + .equals(playlistItem.getStreamEntity().getThumbnailUrl())) { + thumbnailVideoRemoved = true; + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e0591e7fd..5df137c54 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -594,6 +594,11 @@ App language System default Remove watched + Remove watched videos? + "Videos that have been watched\nbefore and after being added to the playlist will be removed.\nAre you sure? This cannot be undone! + Yes + Cancel + Yes, and partially watched videos Due to ExoPlayer constraints the seek duration was set to %d seconds From db335d5cec3b77be37d897279973f1fbf94537af Mon Sep 17 00:00:00 2001 From: Grady Clark <50427197+GradyClark@users.noreply.github.com> Date: Thu, 16 Apr 2020 12:58:16 -0500 Subject: [PATCH 10/13] Removed redundant code reorganized code --- .../local/playlist/LocalPlaylistFragment.java | 44 +++++++++++-------- app/src/main/res/values/strings.xml | 2 - 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 6af10bf21..088615f9c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -362,18 +362,18 @@ public class LocalPlaylistFragment extends BaseLocalListFragment removeWatchedStreams(false)) .setNeutralButton( R.string.remove_watched_popup_yes_and_partially_watched_videos, (DialogInterface d, int id) -> removeWatchedStreams(true)) - .setNegativeButton(R.string.remove_watched_popup_cancel, - (DialogInterface d, int id) -> d.cancel()); - builder.create().show(); + .setNegativeButton(R.string.cancel, + (DialogInterface d, int id) -> d.cancel()) + .create() + .show(); break; default: return super.onOptionsItemSelected(item); @@ -388,22 +388,23 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { - boolean localRemovePartiallyWatched = (boolean) flow.blockingFirst(); - List playlist - = (List) flow.blockingLast(); - HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); + .map((List playlist) -> { + //Playlist data Iterator playlistIter = playlist.iterator(); + + //History data + HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); Iterator historyIter = recordManager .getStreamHistorySortedById().blockingFirst().iterator(); - List notWatchedItems = new ArrayList<>(); Iterator streamStatesIter = null; + + //Remove Watched, Functionality data + List notWatchedItems = new ArrayList<>(); boolean thumbnailVideoRemoved = false; - if (!localRemovePartiallyWatched) { + if (!removePartiallyWatched) { streamStatesIter = recordManager.loadLocalStreamStateBatch(playlist) .blockingGet().iterator(); } @@ -414,7 +415,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragmentRemove watched Remove watched videos? "Videos that have been watched\nbefore and after being added to the playlist will be removed.\nAre you sure? This cannot be undone! - Yes - Cancel Yes, and partially watched videos Due to ExoPlayer constraints the seek duration was set to %d seconds From 776ddddc8374e2bc2cabb189ca4c4ad6ec5b990c Mon Sep 17 00:00:00 2001 From: Grady Clark <50427197+GradyClark@users.noreply.github.com> Date: Tue, 21 Apr 2020 01:03:42 -0500 Subject: [PATCH 11/13] fixed naming and formatting issues --- .../local/playlist/LocalPlaylistFragment.java | 16 ++++++++-------- app/src/main/res/menu/menu_local_playlist.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 088615f9c..462bffb4e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -259,8 +259,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist) -> { - //Playlist data + // Playlist data Iterator playlistIter = playlist.iterator(); - //History data + // History data HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); Iterator historyIter = recordManager .getStreamHistorySortedById().blockingFirst().iterator(); Iterator streamStatesIter = null; - //Remove Watched, Functionality data + // Remove Watched, Functionality data List notWatchedItems = new ArrayList<>(); boolean thumbnailVideoRemoved = false; @@ -471,8 +471,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8671565f9..ae02b5797 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -603,7 +603,7 @@ System default Remove watched Remove watched videos? - "Videos that have been watched\nbefore and after being added to the playlist will be removed.\nAre you sure? This cannot be undone! + Videos that have been watched before and after being added to the playlist will be removed.\nAre you sure? This cannot be undone! Yes, and partially watched videos Due to ExoPlayer constraints the seek duration was set to %d seconds From 73611004a0484fce5f6be54654d8107294e53543 Mon Sep 17 00:00:00 2001 From: Grady Clark <50427197+GradyClark@users.noreply.github.com> Date: Tue, 21 Apr 2020 01:57:23 -0500 Subject: [PATCH 12/13] Code cleanup, and best practices --- .../local/playlist/LocalPlaylistFragment.java | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 462bffb4e..3ec9ae216 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -392,32 +392,27 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist) -> { // Playlist data - Iterator playlistIter = playlist.iterator(); + final Iterator playlistIter = playlist.iterator(); // History data - HistoryRecordManager recordManager = new HistoryRecordManager(getContext()); - Iterator historyIter = recordManager + final HistoryRecordManager recordManager + = new HistoryRecordManager(getContext()); + final Iterator historyIter = recordManager .getStreamHistorySortedById().blockingFirst().iterator(); - Iterator streamStatesIter = null; // Remove Watched, Functionality data - List notWatchedItems = new ArrayList<>(); + final List notWatchedItems = new ArrayList<>(); boolean thumbnailVideoRemoved = false; - if (!removePartiallyWatched) { - streamStatesIter = recordManager.loadLocalStreamStateBatch(playlist) - .blockingGet().iterator(); - } - // already sorted by ^ getStreamHistorySortedById(), binary search can be used - ArrayList historyStreamIds = new ArrayList<>(); + final ArrayList historyStreamIds = new ArrayList<>(); while (historyIter.hasNext()) { historyStreamIds.add(historyIter.next().getStreamId()); } if (removePartiallyWatched) { while (playlistIter.hasNext()) { - PlaylistStreamEntry playlistItem = playlistIter.next(); + final PlaylistStreamEntry playlistItem = playlistIter.next(); int indexInHistory = Collections.binarySearch(historyStreamIds, playlistItem.getStreamId()); @@ -430,12 +425,15 @@ public class LocalPlaylistFragment extends BaseLocalListFragment streamStatesIter = recordManager + .loadLocalStreamStateBatch(playlist).blockingGet().iterator(); + while (playlistIter.hasNext()) { PlaylistStreamEntry playlistItem = playlistIter.next(); - int indexInHistory = Collections.binarySearch(historyStreamIds, + final int indexInHistory = Collections.binarySearch(historyStreamIds, playlistItem.getStreamId()); - boolean hasState = streamStatesIter.next() != null; + final boolean hasState = streamStatesIter.next() != null; if (indexInHistory < 0 || hasState) { notWatchedItems.add(playlistItem); } else if (!thumbnailVideoRemoved @@ -450,9 +448,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { - List notWatchedItems = + final List notWatchedItems = (List) flow.blockingFirst(); - boolean thumbnailVideoRemoved = (Boolean) flow.blockingLast(); + final boolean thumbnailVideoRemoved = (Boolean) flow.blockingLast(); itemListAdapter.clearStreamItemList(); itemListAdapter.addItems(notWatchedItems); @@ -463,7 +461,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment Date: Thu, 23 Apr 2020 23:34:24 +0200 Subject: [PATCH 13/13] Use centralized CompositeDisposable instead of custom Disposable Also do not show any dialog if the user is aready removing watched videos in a local playlist --- .../local/playlist/LocalPlaylistFragment.java | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index 3ec9ae216..485d3f391 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -89,12 +89,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment debouncedSaveSignal; private CompositeDisposable disposables; - private Disposable removeWatchedDisposable; /* Has the playlist been fully loaded from db */ private AtomicBoolean isLoadingComplete; /* Has the playlist been modified (e.g. items reordered or deleted) */ private AtomicBoolean isModified; + /* Is the playlist currently being processed to remove watched videos */ + private boolean isRemovingWatched = false; public static LocalPlaylistFragment getInstance(final long playlistId, final String name) { LocalPlaylistFragment instance = new LocalPlaylistFragment(); @@ -304,14 +305,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment removeWatchedStreams(false)) - .setNeutralButton( - R.string.remove_watched_popup_yes_and_partially_watched_videos, - (DialogInterface d, int id) -> removeWatchedStreams(true)) - .setNegativeButton(R.string.cancel, - (DialogInterface d, int id) -> d.cancel()) - .create() - .show(); + if (!isRemovingWatched) { + new AlertDialog.Builder(requireContext()) + .setMessage(R.string.remove_watched_popup_warning) + .setTitle(R.string.remove_watched_popup_title) + .setPositiveButton(R.string.yes, + (DialogInterface d, int id) -> removeWatchedStreams(false)) + .setNeutralButton( + R.string.remove_watched_popup_yes_and_partially_watched_videos, + (DialogInterface d, int id) -> removeWatchedStreams(true)) + .setNegativeButton(R.string.cancel, + (DialogInterface d, int id) -> d.cancel()) + .create() + .show(); + } break; default: return super.onOptionsItemSelected(item); @@ -382,13 +380,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist) -> { // Playlist data @@ -468,13 +466,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment