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
This commit is contained in:
Grady Clark 2020-02-07 23:57:36 -06:00 committed by Stypox
parent 8265922d68
commit 66c95f901d
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
4 changed files with 207 additions and 3 deletions

View File

@ -2,6 +2,7 @@ package org.schabi.newpipe.local.playlist;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.text.TextUtils; import android.text.TextUtils;
@ -24,11 +25,13 @@ import org.reactivestreams.Subscription;
import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem; 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.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.BaseLocalListFragment; 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.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
@ -39,11 +42,13 @@ import org.schabi.newpipe.util.StreamDialogEntry;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State; import icepick.State;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
@ -71,6 +76,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private View headerPlayAllButton; private View headerPlayAllButton;
private View headerPopupButton; private View headerPopupButton;
private View headerBackgroundButton; private View headerBackgroundButton;
private View headerRemoveWatchedButton;
private ItemTouchHelper itemTouchHelper; private ItemTouchHelper itemTouchHelper;
private LocalPlaylistManager playlistManager; private LocalPlaylistManager playlistManager;
@ -142,10 +149,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
headerStreamCount = headerRootLayout.findViewById(R.id.playlist_stream_count); headerStreamCount = headerRootLayout.findViewById(R.id.playlist_stream_count);
playlistControl = headerRootLayout.findViewById(R.id.playlist_control); playlistControl = headerRootLayout.findViewById(R.id.local_playlist_control);
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button); headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button); headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button); headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button);
headerRemoveWatchedButton = headerRootLayout.findViewById(R.id.playlist_ctrl_remove_watched_button);
return headerRootLayout; return headerRootLayout;
} }
@ -260,6 +268,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
if (headerPopupButton != null) { if (headerPopupButton != null) {
headerPopupButton.setOnClickListener(null); headerPopupButton.setOnClickListener(null);
} }
if (headerRemoveWatchedButton != null) {
headerRemoveWatchedButton.setOnClickListener(null);
}
if (databaseSubscription != null) { if (databaseSubscription != null) {
databaseSubscription.cancel(); databaseSubscription.cancel();
@ -358,6 +369,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view -> headerBackgroundButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); 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 -> { headerPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true); NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true);
@ -684,5 +702,74 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
} }
return new SinglePlayQueue(streamInfoItems, index); return new SinglePlayQueue(streamInfoItems, index);
} }
private class RemoveWatchedStreams extends AsyncTask<String, Long, Long> {
List<PlaylistStreamEntry> 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<StreamHistoryEntry> it_history;
StreamHistoryEntry history_item;
Flowable<List<PlaylistStreamEntry>> playlist = playlistManager.getPlaylistStreams(playlistId);
Iterator<PlaylistStreamEntry> 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();
}
}
} }

View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/local_playlist_control"
xmlns:android="http://schemas.android.com/apk/res/android"
android:visibility="invisible"
tools:visibility="visible">
<LinearLayout
android:id="@+id/playlist_ctrl_play_bg_button"
android:layout_width="match_parent"
android:layout_height="@dimen/playlist_ctrl_height"
android:layout_weight="1"
android:gravity="center"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="@string/controls_background_title"
android:textSize="@dimen/channel_rss_title_size"
android:textColor="?attr/colorAccent"
android:drawablePadding="4dp"
android:drawableLeft="?attr/audio"
android:drawableStart="?attr/audio"/>
</LinearLayout>
<View android:id="@+id/anchorLeft"
android:layout_width="1dp"
android:layout_height="match_parent"
android:clickable="false"
android:layout_marginBottom="@dimen/playlist_ctrl_seperator_margin"
android:layout_marginTop="@dimen/playlist_ctrl_seperator_margin"
android:background="?attr/colorAccent"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:id="@+id/playlist_ctrl_play_all_button">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="@string/play_all"
android:textSize="@dimen/channel_rss_title_size"
android:textColor="?attr/colorAccent"/>
</LinearLayout>
<View android:id="@+id/anchorMiddle"
android:layout_width="1dp"
android:layout_height="match_parent"
android:clickable="false"
android:layout_marginBottom="@dimen/playlist_ctrl_seperator_margin"
android:layout_marginTop="@dimen/playlist_ctrl_seperator_margin"
android:background="?attr/colorAccent"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:id="@+id/playlist_ctrl_play_popup_button">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="@string/controls_popup_title"
android:textSize="@dimen/channel_rss_title_size"
android:textColor="?attr/colorAccent"
android:drawablePadding="4dp"
android:drawableLeft="?attr/popup"
android:drawableStart="?attr/popup"/>
</LinearLayout>
<View android:id="@+id/anchorRight"
android:layout_width="1dp"
android:layout_height="match_parent"
android:clickable="false"
android:layout_marginBottom="@dimen/playlist_ctrl_seperator_margin"
android:layout_marginTop="@dimen/playlist_ctrl_seperator_margin"
android:background="?attr/colorAccent"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.2"
android:gravity="center"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:id="@+id/playlist_ctrl_remove_watched_button">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="15px"
android:gravity="center_vertical"
android:text="@string/remove_watched"
android:textColor="?attr/colorAccent"
android:textSize="@dimen/channel_rss_title_size" />
</LinearLayout>
</LinearLayout>

View File

@ -50,7 +50,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/playlist_stream_count"> android:layout_below="@id/playlist_stream_count">
<include layout="@layout/playlist_control"/> <include layout="@layout/local_playlist_control" />
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -637,4 +637,6 @@
<string name="feed_use_dedicated_fetch_method_enable_button">Enable fast mode</string> <string name="feed_use_dedicated_fetch_method_enable_button">Enable fast mode</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Disable fast mode</string> <string name="feed_use_dedicated_fetch_method_disable_button">Disable fast mode</string>
<string name="feed_use_dedicated_fetch_method_help_text">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.</string> <string name="feed_use_dedicated_fetch_method_help_text">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.</string>
<string name="remove_watched">Remove Watched</string>
</resources> </resources>