From 34ab93c9bd214c02882386db09392e02b59b0540 Mon Sep 17 00:00:00 2001
From: ktprograms 
+     * Should be called once the initial items inside {@link #startLoading(boolean)}
+     * has been loaded and added to the {@link #itemsList}.
+     * 
+     * Otherwise the loading indicator is always shown but no data can be loaded
+     * because the view is not scrollable; see also #1974.
+     */
+    protected void ifMoreItemsLoadableLoadUntilScrollable() {
+        ifMoreItemsLoadableLoadUntilScrollable(0);
+    }
+
+    /**
+     * If more items are loadable and the itemList is not scrollable -> load more data.
+     *
+     * @param recursiveCallCount Amount of recursive calls that occurred
+     * @see #ifMoreItemsLoadableLoadUntilScrollable()
+     */
+    protected void ifMoreItemsLoadableLoadUntilScrollable(final int recursiveCallCount) {
+        // Try to prevent malfunction / stackoverflow
+        if (recursiveCallCount > 100) {
+            Log.w(TAG, "loadEnoughInitialData - Too many recursive calls - Aborting");
+            return;
+        }
+        if (!hasMoreItems()) {
+            if (DEBUG) {
+                Log.d(TAG, "loadEnoughInitialData - OK: No more items to load");
+            }
+            return;
+        }
+        if (itemsList.canScrollVertically(1)
+                || itemsList.canScrollVertically(-1)) {
+            if (DEBUG) {
+                Log.d(TAG, "loadEnoughInitial - OK: itemList is scrollable");
+            }
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "loadEnoughInitialData - View is not scrollable "
+                    + "but it could load more items -> Loading more");
+        }
+        loadMoreItems(() ->
+                ifMoreItemsLoadableLoadUntilScrollable(recursiveCallCount + 1));
+    }
+
+    /**
+     * Loads more items.
+     * @param initialDataLoadCallback
+     *          Callback used in {@link #ifMoreItemsLoadableLoadUntilScrollable()}.
+     *          
+     *          Execute it once the data was loaded and added to the {@link #itemsList}.
+     *          
+     *          Might be null.
+     */
+    protected abstract void loadMoreItems(@Nullable Runnable initialDataLoadCallback);
+
+    protected void loadMoreItems() {
+        loadMoreItems(null);
+    }
 
     protected abstract boolean hasMoreItems();
 
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
index e98dc9fda..87f031c12 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
@@ -6,6 +6,7 @@ import android.util.Log;
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.UserAction;
@@ -65,7 +66,7 @@ public abstract class BaseListInfoFragment
         super.onResume();
         // Check if it was loading when the fragment was stopped/paused,
         if (wasLoading.getAndSet(false)) {
-            if (hasMoreItems() && infoListAdapter.getItemsList().size() > 0) {
+            if (hasMoreItems() && !infoListAdapter.getItemsList().isEmpty()) {
                 loadMoreItems();
             } else {
                 doInitialLoadLogic();
@@ -105,6 +106,7 @@ public abstract class BaseListInfoFragment
     // Load and handle
     //////////////////////////////////////////////////////////////////////////*/
 
+    @Override
     protected void doInitialLoadLogic() {
         if (DEBUG) {
             Log.d(TAG, "doInitialLoadLogic() called");
@@ -144,6 +146,7 @@ public abstract class BaseListInfoFragment
                     currentInfo = result;
                     currentNextPage = result.getNextPage();
                     handleResult(result);
+                    ifMoreItemsLoadableLoadUntilScrollable();
                 }, throwable ->
                         showError(new ErrorInfo(throwable, errorUserAction,
                                 "Start loading: " + url, serviceId)));
@@ -158,7 +161,8 @@ public abstract class BaseListInfoFragment
      */
     protected abstract Single> getPlaylistBookmarkSubscriber() {
-        return new Subscriber
>() {
+        return new Subscriber<>() {
             @Override
             public void onSubscribe(final Subscription s) {
                 if (bookmarkReactor != null) {
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
index 6532417c0..285024484 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/videos/RelatedItemsFragment.java
@@ -26,12 +26,11 @@ import org.schabi.newpipe.util.RelatedItemInfo;
 import java.io.Serializable;
 
 import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
 
 public class RelatedItemsFragment extends BaseListInfoFragment
-     * Should be called once the initial items inside {@link #startLoading(boolean)}
-     * has been loaded and added to the {@link #itemsList}.
-     * 
-     * Otherwise the loading indicator is always shown but no data can be loaded
-     * because the view is not scrollable; see also #1974.
-     */
-    protected void ifMoreItemsLoadableLoadUntilScrollable() {
-        ifMoreItemsLoadableLoadUntilScrollable(0);
-    }
-
-    /**
-     * If more items are loadable and the itemList is not scrollable -> load more data.
-     *
-     * @param recursiveCallCount Amount of recursive calls that occurred
-     * @see #ifMoreItemsLoadableLoadUntilScrollable()
-     */
-    protected void ifMoreItemsLoadableLoadUntilScrollable(final int recursiveCallCount) {
-        // Try to prevent malfunction / stackoverflow
-        if (recursiveCallCount > 100) {
-            Log.w(TAG, "loadEnoughInitialData - Too many recursive calls - Aborting");
-            return;
-        }
-        if (!hasMoreItems()) {
-            if (DEBUG) {
-                Log.d(TAG, "loadEnoughInitialData - OK: No more items to load");
-            }
-            return;
-        }
-        if (itemsList.canScrollVertically(1)
-                || itemsList.canScrollVertically(-1)) {
-            if (DEBUG) {
-                Log.d(TAG, "loadEnoughInitialData - OK: itemList is scrollable");
-            }
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "loadEnoughInitialData - View is not scrollable "
-                    + "but it could load more items -> Loading more");
-        }
-        loadMoreItems(() ->
-                ifMoreItemsLoadableLoadUntilScrollable(recursiveCallCount + 1));
-    }
-
-    /**
-     * Loads more items.
-     * @param initialDataLoadCallback
-     *          Callback used in {@link #ifMoreItemsLoadableLoadUntilScrollable()}.
-     *          
-     *          Execute it once the data was loaded and added to the {@link #itemsList}.
-     *          
-     *          Might be null.
-     */
-    protected abstract void loadMoreItems(@Nullable Runnable initialDataLoadCallback);
-
-    protected void loadMoreItems() {
-        loadMoreItems(null);
-    }
+    protected abstract void loadMoreItems();
 
     protected abstract boolean hasMoreItems();
 
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
index 87f031c12..ebd586e35 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java
@@ -6,7 +6,6 @@ import android.util.Log;
 import android.view.View;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.UserAction;
@@ -146,7 +145,6 @@ public abstract class BaseListInfoFragment
                     currentInfo = result;
                     currentNextPage = result.getNextPage();
                     handleResult(result);
-                    ifMoreItemsLoadableLoadUntilScrollable();
                 }, throwable ->
                         showError(new ErrorInfo(throwable, errorUserAction,
                                 "Start loading: " + url, serviceId)));
@@ -162,7 +160,7 @@ public abstract class BaseListInfoFragment
     protected abstract Single
+     * Which tries to load more items when not enough are in the view (not scrollable)
+     * and more are available.
+     * 
+     * Note: This method only works because "This callback will also be called if visible
+     * item range changes after a layout calculation. In that case, dx and dy will be 0."
+     * - which might be unexpected because no actual scrolling occurs...
+     * 
+     * This listener will be replaced by DefaultItemListOnScrolledDownListener when
+     * 
+     *     
+     */
+    protected void setItemsListInitialScrollListener() {
+        if (DEBUG) {
+            Log.d(TAG, "setItemsListInitialScrollListener called");
+        }
         itemsList.clearOnScrollListeners();
-
-        /*
-         * Add initial scroll listener - which tries to load more items when not enough
-         * are in the view (not scrollable) and more are available.
-         *
-         * Note: This method only works because "This callback will also be called if visible
-         * item range changes after a layout calculation. In that case, dx and dy will be 0."
-         * - which might be unexpected because no actual scrolling occurs...
-         *
-         * This listener will be replaced by DefaultItemListOnScrolledDownListener when
-         * * the view was actually scrolled
-         * * the view is scrollable
-         * * No more items can be loaded
-         */
         itemsList.addOnScrollListener(new DefaultItemListOnScrolledDownListener() {
             @Override
             public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
@@ -360,7 +368,6 @@ public abstract class BaseListFragment extends BaseStateFragment
             }
 
             private void useNormalScrollListener() {
-                log("Unregistering and using normal listener");
                 itemsList.removeOnScrollListener(this);
                 itemsList.addOnScrollListener(new DefaultItemListOnScrolledDownListener());
             }
@@ -467,6 +474,12 @@ public abstract class BaseListFragment extends BaseStateFragment
     // Load and handle
     //////////////////////////////////////////////////////////////////////////*/
 
+    @Override
+    protected void startLoading(final boolean forceLoad) {
+        setItemsListInitialScrollListener();
+        super.startLoading(forceLoad);
+    }
+
     protected abstract void loadMoreItems();
 
     protected abstract boolean hasMoreItems();
From 01683aa816b7792bab769b18b0509275b7eed6cc Mon Sep 17 00:00:00 2001
From: litetex <40789489+litetex@users.noreply.github.com>
Date: Mon, 24 Jan 2022 20:57:23 +0100
Subject: [PATCH 016/107] Code improvements
---
 .../newpipe/info_list/InfoListAdapter.java    | 20 +++++++++----------
 1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
index 839125fd1..718f7004c 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
@@ -79,7 +79,7 @@ public class InfoListAdapter extends RecyclerView.Adapter
      * Which tries to load more items when not enough are in the view (not scrollable)
      * and more are available.
@@ -329,9 +343,9 @@ public abstract class BaseListFragment extends BaseStateFragment
      *     >
 
+    @RewriteQueriesToDropUnusedColumns
     @Query(
         """
         SELECT * FROM subscriptions s
@@ -47,6 +49,7 @@ abstract class SubscriptionDAO : BasicDAO
>
 
+    @RewriteQueriesToDropUnusedColumns
     @Query(
         """
         SELECT * FROM subscriptions s
From 62abfa96b82d4b197d55c0eabe5da0d1b3b33516 Mon Sep 17 00:00:00 2001
From: Stypox 
true or false
+     */
+    public Optionaltrue or false
+     * @return {@link Optional#empty()} if nothing was resolved, otherwise {@code true} or
+     * {@code false}
      */
     public Optional+ * The play queue manager needs to be reloaded if the video renderer index is not known and if + * the content is not an audio content, but also if none of the following cases is met: + * + *
Builder to generate a {@link InfoItemDialog}.
+ * Use {@link #addEntry(StreamDialogDefaultEntry)} + * and {@link #addAllEntries(StreamDialogDefaultEntry...)} to add options to the dialog. + *Create an instance of this Builder
+ * @param activity + * @param fragment + * @param infoItem + * @param addDefaultEntriesAutomatically whether default entries added with + * {@link #addDefaultEntriesAtBeginning()} and + * {@link #addDefaultEntriesAtEnd()} + * are added automatically when generating + * the {@link InfoItemDialog}. + */ + public Builder(@NonNull final Activity activity, + @NonNull final Fragment fragment, + @NonNull final StreamInfoItem infoItem, + final boolean addDefaultEntriesAutomatically) { this.activity = activity; this.fragment = fragment; - this.item = item; + this.infoItem = infoItem; + this.addDefaultEntriesAutomatically = addDefaultEntriesAutomatically; + if (addDefaultEntriesAutomatically) { + addDefaultEntriesAtBeginning(); + } } public void addEntry(@NonNull final StreamDialogDefaultEntry entry) { @@ -98,17 +121,26 @@ public final class InfoItemDialog { } } + /** + *Change an entries' action that is called when the entry is selected.
+ *Warning: Only use this method when the entry has been already added. + * Changing the action of an entry which has not been added to the Builder yet + * does not have an effect.
+ * @param entry the entry to change + * @param action the action to perform when the entry is selected + */ public void setAction(@NonNull final StreamDialogDefaultEntry entry, @NonNull final StreamDialogEntry.StreamDialogEntryAction action) { for (int i = 0; i < entries.size(); i++) { if (entries.get(i).resource == entry.resource) { entries.set(i, new StreamDialogEntry(entry.resource, action)); + return; } } } public void addChannelDetailsEntryIfPossible() { - if (!isNullOrEmpty(item.getUploaderUrl())) { + if (!isNullOrEmpty(infoItem.getUploaderUrl())) { addEntry(StreamDialogDefaultEntry.SHOW_CHANNEL_DETAILS); } } @@ -125,8 +157,8 @@ public final class InfoItemDialog { public void addStartHereEntries() { addEntry(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND); - if (item.getStreamType() != StreamType.AUDIO_STREAM - && item.getStreamType() != StreamType.AUDIO_LIVE_STREAM) { + if (infoItem.getStreamType() != StreamType.AUDIO_STREAM + && infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) { addEntry(StreamDialogDefaultEntry.START_HERE_ON_POPUP); } } @@ -134,21 +166,20 @@ public final class InfoItemDialog { /** * Adds {@link StreamDialogDefaultEntry.MARK_AS_WATCHED} if the watch history is enabled * and the stream is not a livestream. - * @param streamType the item's stream type */ - public void addMarkAsWatchedEntryIfNeeded(final StreamType streamType) { + public void addMarkAsWatchedEntryIfNeeded() { final boolean isWatchHistoryEnabled = PreferenceManager .getDefaultSharedPreferences(activity) .getBoolean(activity.getString(R.string.enable_watch_history_key), false); - if (streamType != StreamType.AUDIO_LIVE_STREAM - && streamType != StreamType.LIVE_STREAM - && isWatchHistoryEnabled) { + if (isWatchHistoryEnabled + && infoItem.getStreamType() != StreamType.LIVE_STREAM + && infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) { addEntry(StreamDialogDefaultEntry.MARK_AS_WATCHED); } } public void addPlayWithKodiEntryIfNeeded() { - if (KoreUtils.shouldShowPlayWithKodi(activity, item.getServiceId())) { + if (KoreUtils.shouldShowPlayWithKodi(activity, infoItem.getServiceId())) { addEntry(StreamDialogDefaultEntry.PLAY_WITH_KODI); } } @@ -165,7 +196,7 @@ public final class InfoItemDialog { StreamDialogDefaultEntry.OPEN_IN_BROWSER ); addPlayWithKodiEntryIfNeeded(); - addMarkAsWatchedEntryIfNeeded(item.getStreamType()); + addMarkAsWatchedEntryIfNeeded(); addChannelDetailsEntryIfPossible(); } @@ -174,7 +205,10 @@ public final class InfoItemDialog { * @return a new instance of {@link InfoItemDialog} */ public InfoItemDialog create() { - return new InfoItemDialog(this.activity, this.fragment, this.item, this.entries); + if (addDefaultEntriesAutomatically) { + addDefaultEntriesAtEnd(); + } + return new InfoItemDialog(this.activity, this.fragment, this.infoItem, this.entries); } } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index bda829907..63e918739 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -357,12 +357,7 @@ class FeedFragment : BaseStateFragmentCreate a Builder instance that automatically adds the some default entries + * at the top and bottom of the dialog.
+ * The dialog has the following structure: + *+ * + - - - - - - - - - - - - - - - - - - - - - -+ + * | ENQUEUE | + * | ENQUEUE_HERE | + * | START_ON_BACKGROUND | + * | START_ON_POPUP | + * + - - - - - - - - - - - - - - - - - - - - - -+ + * | entries added manually with | + * | addEntry() and addAllEntries() | + * + - - - - - - - - - - - - - - - - - - - - - -+ + * | APPEND_PLAYLIST | + * | SHARE | + * | OPEN_IN_BROWSER | + * | PLAY_WITH_KODI | + * | MARK_AS_WATCHED | + * | SHOW_CHANNEL_DETAILS | + * + - - - - - - - - - - - - - - - - - - - - - -+ + *+ * Please note that some entries are not added depending on the user's preferences, + * the item's {@link StreamType} and the current player state. + * + * @param activity + * @param context + * @param fragment + * @param infoItem the item for this dialog; all entries and their actions work with + * this {@link org.schabi.newpipe.extractor.InfoItem} + */ public Builder(@NonNull final Activity activity, + @NonNull final Context context, @NonNull final Fragment fragment, @NonNull final StreamInfoItem infoItem) { - this(activity, fragment, infoItem, true); + this(activity, context, fragment, infoItem, true); } /** - *
Create an instance of this Builder
+ *Create an instance of this Builder.
+ *If {@code addDefaultEntriesAutomatically} is set to {@code true}, + * some default entries are added to the top and bottom of the dialog.
+ * The dialog has the following structure: + *+ * + - - - - - - - - - - - - - - - - - - - - - -+ + * | ENQUEUE | + * | ENQUEUE_HERE | + * | START_ON_BACKGROUND | + * | START_ON_POPUP | + * + - - - - - - - - - - - - - - - - - - - - - -+ + * | entries added manually with | + * | addEntry() and addAllEntries() | + * + - - - - - - - - - - - - - - - - - - - - - -+ + * | APPEND_PLAYLIST | + * | SHARE | + * | OPEN_IN_BROWSER | + * | PLAY_WITH_KODI | + * | MARK_AS_WATCHED | + * | SHOW_CHANNEL_DETAILS | + * + - - - - - - - - - - - - - - - - - - - - - -+ + *+ * Please note that some entries are not added depending on the user's preferences, + * the item's {@link StreamType} and the current player state. + * * @param activity + * @param context * @param fragment * @param infoItem - * @param addDefaultEntriesAutomatically whether default entries added with - * {@link #addDefaultEntriesAtBeginning()} and - * {@link #addDefaultEntriesAtEnd()} - * are added automatically when generating - * the {@link InfoItemDialog}. + * @param addDefaultEntriesAutomatically + * whether default entries added with {@link #addDefaultBeginningEntries()} + * and {@link #addDefaultEndEntries()} are added automatically when generating + * the {@link InfoItemDialog}. + *
+ *     These entries contain a String {@link #resource} which is displayed in the dialog and
+ *     a default {@link #action} that is executed
+ *     when the entry is selected (via onClick()).
+ *     
+ *     They action can be overridden by using the Builder's
+ *     {@link org.schabi.newpipe.info_list.InfoItemDialog.Builder#setAction(
+ *     StreamDialogDefaultEntry, StreamDialogEntry.StreamDialogEntryAction)}
+ *     method.
+ * 
          *     + - - - - - - - - - - - - - - - - - - - - - -+
          *     | ENQUEUE                                    |
-         *     | ENQUEUE_HERE                               |
+         *     | ENQUEUE_NEXT                               |
          *     | START_ON_BACKGROUND                        |
          *     | START_ON_POPUP                             |
          *     + - - - - - - - - - - - - - - - - - - - - - -+
@@ -118,10 +132,12 @@ public final class InfoItemDialog {
          * @param context
          * @param fragment
          * @param infoItem the item for this dialog; all entries and their actions work with
-         *                this {@link org.schabi.newpipe.extractor.InfoItem}
+         *                this {@link StreamInfoItem}
+         * @throws IllegalArgumentException if activity, context
+         *         or resources is null
          */
-        public Builder(@NonNull final Activity activity,
-                       @NonNull final Context context,
+        public Builder(final Activity activity,
+                       final Context context,
                        @NonNull final Fragment fragment,
                        @NonNull final StreamInfoItem infoItem) {
             this(activity, context, fragment, infoItem, true);
@@ -135,7 +151,7 @@ public final class InfoItemDialog {
          * 
          *     + - - - - - - - - - - - - - - - - - - - - - -+
          *     | ENQUEUE                                    |
-         *     | ENQUEUE_HERE                               |
+         *     | ENQUEUE_NEXT                               |
          *     | START_ON_BACKGROUND                        |
          *     | START_ON_POPUP                             |
          *     + - - - - - - - - - - - - - - - - - - - - - -+
@@ -164,12 +180,21 @@ public final class InfoItemDialog {
          *        
          *        Entries added with {@link #addEntry(StreamDialogDefaultEntry)} and
          *        {@link #addAllEntries(StreamDialogDefaultEntry...)} are added in between.
+         * @throws IllegalArgumentException if activity, context
+         * or resources is null
          */
-        public Builder(@NonNull final Activity activity,
-                       @NonNull final Context context,
+        public Builder(final Activity activity,
+                       final Context context,
                        @NonNull final Fragment fragment,
                        @NonNull final StreamInfoItem infoItem,
                        final boolean addDefaultEntriesAutomatically) {
+            if (activity == null || context == null || context.getResources() == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "activity, context or resources is null: activity = "
+                            + activity + ", context = " + context);
+                }
+                throw new IllegalArgumentException("activity, context or resources is null");
+            }
             this.activity = activity;
             this.context = context;
             this.fragment = fragment;
@@ -180,14 +205,24 @@ public final class InfoItemDialog {
             }
         }
 
-        public void addEntry(@NonNull final StreamDialogDefaultEntry entry) {
+        /**
+         * Adds a new entry and appends it to the current entry list.
+         * @param entry the entry to add
+         * @return the current {@link Builder} instance
+         */
+        public Builder addEntry(@NonNull final StreamDialogDefaultEntry entry) {
             entries.add(entry.toStreamDialogEntry());
+            return this;
         }
 
-        public void addAllEntries(@NonNull final StreamDialogDefaultEntry... newEntries) {
-            for (final StreamDialogDefaultEntry entry: newEntries) {
-                this.entries.add(entry.toStreamDialogEntry());
-            }
+        /**
+         * Adds new entries. These are appended to the current entry list.
+         * @param newEntries the entries to add
+         * @return the current {@link Builder} instance
+         */
+        public Builder addAllEntries(@NonNull final StreamDialogDefaultEntry... newEntries) {
+            Stream.of(newEntries).forEach(this::addEntry);
+            return this;
         }
 
         /**
@@ -197,23 +232,26 @@ public final class InfoItemDialog {
          * does not have an effect.
          * @param entry the entry to change
          * @param action the action to perform when the entry is selected
+         * @return the current {@link Builder} instance
          */
-        public void setAction(@NonNull final StreamDialogDefaultEntry entry,
+        public Builder setAction(@NonNull final StreamDialogDefaultEntry entry,
                               @NonNull final StreamDialogEntry.StreamDialogEntryAction action) {
             for (int i = 0; i < entries.size(); i++) {
                 if (entries.get(i).resource == entry.resource) {
                     entries.set(i, new StreamDialogEntry(entry.resource, action));
-                    return;
+                    return this;
                 }
             }
+            return this;
         }
 
         /**
          * Adds {@link StreamDialogDefaultEntry#ENQUEUE} if the player is open and
          * {@link StreamDialogDefaultEntry#ENQUEUE_NEXT} if there are multiple streams
          * in the play queue.
+         * @return the current {@link Builder} instance
          */
-        public void addEnqueueEntriesIfNeeded() {
+        public Builder addEnqueueEntriesIfNeeded() {
             if (PlayerHolder.getInstance().isPlayerOpen()) {
                 addEntry(StreamDialogDefaultEntry.ENQUEUE);
 
@@ -221,26 +259,30 @@ public final class InfoItemDialog {
                     addEntry(StreamDialogDefaultEntry.ENQUEUE_NEXT);
                 }
             }
+            return this;
         }
 
         /**
          * Adds the {@link StreamDialogDefaultEntry#START_HERE_ON_BACKGROUND}.
          * If the {@link #infoItem} is not a pure audio (live) stream,
          * {@link StreamDialogDefaultEntry#START_HERE_ON_POPUP} is added, too.
+         * @return the current {@link Builder} instance
          */
-        public void addStartHereEntries() {
+        public Builder addStartHereEntries() {
             addEntry(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND);
             if (infoItem.getStreamType() != StreamType.AUDIO_STREAM
                     && infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) {
                 addEntry(StreamDialogDefaultEntry.START_HERE_ON_POPUP);
             }
+            return this;
         }
 
         /**
          * Adds {@link StreamDialogDefaultEntry.MARK_AS_WATCHED} if the watch history is enabled
          * and the stream is not a livestream.
+         * @return the current {@link Builder} instance
          */
-        public void addMarkAsWatchedEntryIfNeeded() {
+        public Builder addMarkAsWatchedEntryIfNeeded() {
             final boolean isWatchHistoryEnabled = PreferenceManager
                     .getDefaultSharedPreferences(context)
                     .getBoolean(context.getString(R.string.enable_watch_history_key), false);
@@ -249,12 +291,18 @@ public final class InfoItemDialog {
                     && infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) {
                 addEntry(StreamDialogDefaultEntry.MARK_AS_WATCHED);
             }
+            return this;
         }
 
-        public void addPlayWithKodiEntryIfNeeded() {
+        /**
+         * Adds the {@link StreamDialogDefaultEntry.PLAY_WITH_KODI} entry if it is needed.
+         * @return the current {@link Builder} instance
+         */
+        public Builder addPlayWithKodiEntryIfNeeded() {
             if (KoreUtils.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
                 addEntry(StreamDialogDefaultEntry.PLAY_WITH_KODI);
             }
+            return this;
         }
 
         /**
@@ -262,16 +310,19 @@ public final class InfoItemDialog {
          * 
          * This method adds the "enqueue" (see {@link #addEnqueueEntriesIfNeeded()})
          * and "start here" (see {@link #addStartHereEntries()} entries.
+         * @return the current {@link Builder} instance
          */
-        public void addDefaultBeginningEntries() {
+        public Builder addDefaultBeginningEntries() {
             addEnqueueEntriesIfNeeded();
             addStartHereEntries();
+            return this;
         }
 
         /**
          * Add the entries which are usually at the bottom of the action list.
+         * @return the current {@link Builder} instance
          */
-        public void addDefaultEndEntries() {
+        public Builder addDefaultEndEntries() {
             addAllEntries(
                     StreamDialogDefaultEntry.APPEND_PLAYLIST,
                     StreamDialogDefaultEntry.SHARE,
@@ -280,6 +331,7 @@ public final class InfoItemDialog {
             addPlayWithKodiEntryIfNeeded();
             addMarkAsWatchedEntryIfNeeded();
             addEntry(StreamDialogDefaultEntry.SHOW_CHANNEL_DETAILS);
+            return this;
         }
 
         /**
@@ -292,5 +344,14 @@ public final class InfoItemDialog {
             }
             return new InfoItemDialog(this.activity, this.fragment, this.infoItem, this.entries);
         }
+
+        public static void reportErrorDuringInitialization(final Throwable throwable,
+                                                           final InfoItem item) {
+            ErrorUtil.showSnackbar(App.getApp().getBaseContext(), new ErrorInfo(
+                    throwable,
+                    UserAction.OPEN_INFO_ITEM_DIALOG,
+                    "none",
+                    item.getServiceId()));
+        }
     }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
index 03c922ba4..832fb580f 100644
--- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
@@ -1,6 +1,5 @@
 package org.schabi.newpipe.local.history;
 
-import android.app.Activity;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -326,26 +325,28 @@ public class StatisticsPlaylistFragment
 
     private void showInfoItemDialog(final StreamStatisticsEntry item) {
         final Context context = getContext();
-        final Activity activity = getActivity();
-        if (context == null || context.getResources() == null || activity == null) {
-            return;
-        }
         final StreamInfoItem infoItem = item.toStreamInfoItem();
 
-        final InfoItemDialog.Builder dialogBuilder =
-                new InfoItemDialog.Builder(activity, context, this, infoItem);
+        try {
+            final InfoItemDialog.Builder dialogBuilder =
+                    new InfoItemDialog.Builder(getActivity(), context, this, infoItem);
 
-        // set entries in the middle; the others are added automatically
-        dialogBuilder.addEntry(StreamDialogDefaultEntry.DELETE);
-
-        // set custom actions
-        dialogBuilder.setAction(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND,
-                (fragment, infoItemDuplicate) -> NavigationHelper
-                        .playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true));
-        dialogBuilder.setAction(StreamDialogDefaultEntry.DELETE, (fragment, infoItemDuplicate) ->
-                deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0)));
-
-        dialogBuilder.create().show();
+            // set entries in the middle; the others are added automatically
+            dialogBuilder
+                    .addEntry(StreamDialogDefaultEntry.DELETE)
+                    .setAction(
+                            StreamDialogDefaultEntry.DELETE,
+                            (f, i) -> deleteEntry(
+                                    Math.max(itemListAdapter.getItemsList().indexOf(item), 0)))
+                    .setAction(
+                            StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND,
+                            (f, i) -> NavigationHelper.playOnBackgroundPlayer(
+                                    context, getPlayQueueStartingAt(item), true))
+                    .create()
+                    .show();
+        } catch (final IllegalArgumentException e) {
+            InfoItemDialog.Builder.reportErrorDuringInitialization(e, infoItem);
+        }
     }
 
     private void deleteEntry(final int index) {
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 2b690ff7b..ed2b109b5 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
@@ -3,7 +3,6 @@ package org.schabi.newpipe.local.playlist;
 import static org.schabi.newpipe.ktx.ViewUtils.animate;
 import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
 
-import android.app.Activity;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Bundle;
@@ -740,33 +739,38 @@ public class LocalPlaylistFragment extends BaseLocalListFragment NavigationHelper.playOnBackgroundPlayer(
-                        context, getPlayQueueStartingAt(item), true));
-        dialogBuilder.setAction(StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL,
-                (fragment, infoItemDuplicate) ->
-                        changeThumbnailUrl(item.getStreamEntity().getThumbnailUrl()));
-        dialogBuilder.setAction(StreamDialogDefaultEntry.DELETE,
-                (fragment, infoItemDuplicate) -> deleteItem(item));
-
-        dialogBuilder.create().show();
+            // set custom actions
+            // all entries modified below have already been added within the builder
+            dialogBuilder
+                    .setAction(
+                            StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND,
+                            (f, i) -> NavigationHelper.playOnBackgroundPlayer(
+                                    context, getPlayQueueStartingAt(item), true))
+                    .setAction(
+                            StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL,
+                            (f, i) ->
+                                    changeThumbnailUrl(item.getStreamEntity().getThumbnailUrl()))
+                    .setAction(
+                            StreamDialogDefaultEntry.DELETE,
+                            (f, i) -> deleteItem(item))
+                    .create()
+                    .show();
+        } catch (final IllegalArgumentException e) {
+            InfoItemDialog.Builder.reportErrorDuringInitialization(e, infoItem);
+        }
     }
 
     private void setInitialData(final long pid, final String title) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogDefaultEntry.java b/app/src/main/java/org/schabi/newpipe/util/StreamDialogDefaultEntry.java
index e3d26c833..b9e1f17c5 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogDefaultEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogDefaultEntry.java
@@ -1,13 +1,14 @@
 package org.schabi.newpipe.util;
 
 import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
+import static org.schabi.newpipe.util.StreamDialogEntry.fetchItemInfoIfSparse;
+import static org.schabi.newpipe.util.StreamDialogEntry.openChannelFragment;
 
 import android.net.Uri;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.StringRes;
-import androidx.fragment.app.Fragment;
 
 import org.schabi.newpipe.NewPipeDatabase;
 import org.schabi.newpipe.R;
@@ -15,11 +16,9 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
 import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.ErrorUtil;
 import org.schabi.newpipe.error.UserAction;
-import org.schabi.newpipe.extractor.stream.StreamInfoItem;
 import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
 import org.schabi.newpipe.local.dialog.PlaylistDialog;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
-import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
 import org.schabi.newpipe.util.external_communication.KoreUtils;
 import org.schabi.newpipe.util.external_communication.ShareUtils;
 
@@ -73,32 +72,45 @@ public enum StreamDialogDefaultEntry {
     }),
 
     /**
-     * Enqueues the stream automatically to the current PlayerType.
-     * 
-     * Info: Add this entry within showStreamDialog.
+     * Enqueues the stream automatically to the current PlayerType.
      */
     ENQUEUE(R.string.enqueue_stream, (fragment, item) ->
-        NavigationHelper.enqueueOnPlayer(fragment.getContext(), new SinglePlayQueue(item))
+            fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
+                NavigationHelper.enqueueOnPlayer(fragment.getContext(), singlePlayQueue))
     ),
 
+    /**
+     * Enqueues the stream automatically to the current PlayerType
+     * after the currently playing stream.
+     */
     ENQUEUE_NEXT(R.string.enqueue_next_stream, (fragment, item) ->
-        NavigationHelper.enqueueNextOnPlayer(fragment.getContext(), new SinglePlayQueue(item))
+            fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
+                NavigationHelper.enqueueNextOnPlayer(fragment.getContext(), singlePlayQueue))
     ),
 
     START_HERE_ON_BACKGROUND(R.string.start_here_on_background, (fragment, item) ->
-            NavigationHelper.playOnBackgroundPlayer(fragment.getContext(),
-                    new SinglePlayQueue(item), true)),
+            fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
+                NavigationHelper.playOnBackgroundPlayer(
+                        fragment.getContext(), singlePlayQueue, true))),
 
     START_HERE_ON_POPUP(R.string.start_here_on_popup, (fragment, item) ->
-            NavigationHelper.playOnPopupPlayer(fragment.getContext(),
-                    new SinglePlayQueue(item), true)),
+            fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
+                NavigationHelper.playOnPopupPlayer(fragment.getContext(), singlePlayQueue, true))),
 
     SET_AS_PLAYLIST_THUMBNAIL(R.string.set_as_playlist_thumbnail, (fragment, item) -> {
-    }), // has to be set manually
+        throw new UnsupportedOperationException("This needs to be implemented manually "
+                + "by using InfoItemDialog.Builder.setAction()");
+    }),
 
     DELETE(R.string.delete, (fragment, item) -> {
-    }), // has to be set manually
+        throw new UnsupportedOperationException("This needs to be implemented manually "
+                + "by using InfoItemDialog.Builder.setAction()");
+    }),
 
+    /**
+     * Opens a {@link PlaylistDialog} to either append the stream to a playlist
+     * or create a new playlist if there are no local playlists.
+     */
     APPEND_PLAYLIST(R.string.add_to_playlist, (fragment, item) ->
         PlaylistDialog.createCorrespondingDialog(
                 fragment.getContext(),
@@ -154,12 +166,4 @@ public enum StreamDialogDefaultEntry {
         return new StreamDialogEntry(resource, action);
     }
 
-    private static void openChannelFragment(@NonNull final Fragment fragment,
-                                            @NonNull final StreamInfoItem item,
-                                            final String uploaderUrl) {
-        // For some reason `getParentFragmentManager()` doesn't work, but this does.
-        NavigationHelper.openChannelFragment(
-                fragment.requireActivity().getSupportFragmentManager(),
-                item.getServiceId(), uploaderUrl, item.getUploaderName());
-    }
 }
diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
index bb59d0f29..24d616819 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
@@ -2,16 +2,22 @@ package org.schabi.newpipe.util;
 
 import android.content.Context;
 
-
 import androidx.annotation.NonNull;
 import androidx.annotation.StringRes;
 import androidx.fragment.app.Fragment;
 
+import org.schabi.newpipe.error.ErrorInfo;
+import org.schabi.newpipe.error.ErrorUtil;
+import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.extractor.stream.StreamInfoItem;
+import org.schabi.newpipe.extractor.stream.StreamType;
+import org.schabi.newpipe.local.history.HistoryRecordManager;
+import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
 
 import java.util.function.Consumer;
 
-import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.schedulers.Schedulers;
 
 public class StreamDialogEntry {
 
@@ -33,4 +39,61 @@ public class StreamDialogEntry {
     public interface StreamDialogEntryAction {
         void onClick(Fragment fragment, StreamInfoItem infoItem);
     }
+
+    public static void openChannelFragment(@NonNull final Fragment fragment,
+                                            @NonNull final StreamInfoItem item,
+                                            final String uploaderUrl) {
+        // For some reason `getParentFragmentManager()` doesn't work, but this does.
+        NavigationHelper.openChannelFragment(
+                fragment.requireActivity().getSupportFragmentManager(),
+                item.getServiceId(), uploaderUrl, item.getUploaderName());
+    }
+
+    /**
+     * Fetches a {@link StreamInfoItem} if it is incomplete and executes the callback.
+     * 
+     * This method is required if the info has been fetched
+     * via a {@link org.schabi.newpipe.extractor.feed.FeedExtractor}.
+     * FeedExtractors provide a fast and lightweight method to fetch info,
+     * but the info might be incomplete
+     * (see {@link org.schabi.newpipe.local.feed.service.FeedLoadService} for more details).
+     * @param context
+     * @param item the item which is checked and eventually loaded completely
+     * @param callback
+     */
+    public static void fetchItemInfoIfSparse(@NonNull final Context context,
+                                             @NonNull final StreamInfoItem item,
+                                             @NonNull final Consumer callback) {
+        if (!(item.getStreamType() == StreamType.LIVE_STREAM
+                || item.getStreamType() == StreamType.AUDIO_LIVE_STREAM)
+                && item.getDuration() < 0) {
+            // Sparse item: fetched by fast fetch
+            ExtractorHelper.getStreamInfo(
+                    item.getServiceId(),
+                    item.getUrl(),
+                    false
+            )
+                    .subscribeOn(Schedulers.io())
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe(result -> {
+                        final HistoryRecordManager recordManager =
+                                new HistoryRecordManager(context);
+                        recordManager.saveStreamState(result, 0)
+                                .subscribeOn(Schedulers.io())
+                                .observeOn(AndroidSchedulers.mainThread())
+                                .doOnError(throwable -> ErrorUtil.showSnackbar(
+                                        context,
+                                        new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
+                                                item.getUrl(), item.getServiceId())))
+                                .subscribe();
+
+                        callback.accept(new SinglePlayQueue(result));
+                    }, throwable -> ErrorUtil.createNotification(context,
+                            new ErrorInfo(throwable, UserAction.REQUESTED_CHANNEL,
+                                    "Could not fetch missing stream info")));
+        } else {
+            callback.accept(new SinglePlayQueue(item));
+        }
+    }
+
 }
From a7d5d9a1d693e7794c8df03c4f0657f33040b6a9 Mon Sep 17 00:00:00 2001
From: TobiGr 
Date: Sat, 19 Feb 2022 00:02:15 +0100
Subject: [PATCH 050/107] Fix rebase
---
 .../newpipe/info_list/InfoItemDialog.java     |  2 +-
 .../util/StreamDialogDefaultEntry.java        | 37 ++-----------------
 2 files changed, 5 insertions(+), 34 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java
index 183b2d8d9..924c03cd8 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java
@@ -252,7 +252,7 @@ public final class InfoItemDialog {
          * @return the current {@link Builder} instance
          */
         public Builder addEnqueueEntriesIfNeeded() {
-            if (PlayerHolder.getInstance().isPlayerOpen()) {
+            if (PlayerHolder.getInstance().isPlayQueueReady()) {
                 addEntry(StreamDialogDefaultEntry.ENQUEUE);
 
                 if (PlayerHolder.getInstance().getQueueSize() > 1) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogDefaultEntry.java b/app/src/main/java/org/schabi/newpipe/util/StreamDialogDefaultEntry.java
index b9e1f17c5..a395d1ec1 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogDefaultEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogDefaultEntry.java
@@ -1,21 +1,15 @@
 package org.schabi.newpipe.util;
 
-import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
 import static org.schabi.newpipe.util.StreamDialogEntry.fetchItemInfoIfSparse;
 import static org.schabi.newpipe.util.StreamDialogEntry.openChannelFragment;
 
 import android.net.Uri;
-import android.widget.Toast;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.StringRes;
 
-import org.schabi.newpipe.NewPipeDatabase;
 import org.schabi.newpipe.R;
 import org.schabi.newpipe.database.stream.model.StreamEntity;
-import org.schabi.newpipe.error.ErrorInfo;
-import org.schabi.newpipe.error.ErrorUtil;
-import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
 import org.schabi.newpipe.local.dialog.PlaylistDialog;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
@@ -25,7 +19,6 @@ import org.schabi.newpipe.util.external_communication.ShareUtils;
 import java.util.Collections;
 
 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.schedulers.Schedulers;
 
 /**
  * 
@@ -44,32 +37,10 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
  * 
  */
 public enum StreamDialogDefaultEntry {
-    SHOW_CHANNEL_DETAILS(R.string.show_channel_details, (fragment, item) -> {
-        if (isNullOrEmpty(item.getUploaderUrl())) {
-            final int serviceId = item.getServiceId();
-            final String url = item.getUrl();
-            Toast.makeText(fragment.getContext(), R.string.loading_channel_details,
-                    Toast.LENGTH_SHORT).show();
-            ExtractorHelper.getStreamInfo(serviceId, url, false)
-                    .subscribeOn(Schedulers.io())
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe(result -> {
-                        NewPipeDatabase.getInstance(fragment.requireContext()).streamDAO()
-                                .setUploaderUrl(serviceId, url, result.getUploaderUrl())
-                                .subscribeOn(Schedulers.io()).subscribe();
-                        openChannelFragment(fragment, item, result.getUploaderUrl());
-                    }, throwable -> ErrorUtil.openActivity(
-                            fragment.requireContext(),
-                            new ErrorInfo(
-                                    throwable,
-                                    UserAction.REQUESTED_CHANNEL,
-                                    url,
-                                    serviceId
-                            )));
-        } else {
-            openChannelFragment(fragment, item, item.getUploaderUrl());
-        }
-    }),
+    SHOW_CHANNEL_DETAILS(R.string.show_channel_details, (fragment, item) ->
+        SaveUploaderUrlHelper.saveUploaderUrlIfNeeded(fragment, item,
+                uploaderUrl -> openChannelFragment(fragment, item, uploaderUrl))
+    ),
 
     /**
      * Enqueues the stream automatically to the current PlayerType.
From 277f21d5b2ccc8e50241d5997a098e9022b3a1f0 Mon Sep 17 00:00:00 2001
From: TobiGr 
Date: Fri, 18 Feb 2022 23:46:23 +0100
Subject: [PATCH 051/107] Move Classes related to InfoItemDIalog into own
 package
---
 .../newpipe/fragments/list/BaseListFragment.java     |  2 +-
 .../fragments/list/playlist/PlaylistFragment.java    |  4 ++--
 .../info_list/{ => dialog}/InfoItemDialog.java       |  4 +---
 .../dialog}/StreamDialogDefaultEntry.java            | 12 +++++++-----
 .../dialog}/StreamDialogEntry.java                   |  4 +++-
 .../org/schabi/newpipe/local/feed/FeedFragment.kt    |  2 +-
 .../local/history/StatisticsPlaylistFragment.java    |  4 ++--
 .../local/playlist/LocalPlaylistFragment.java        |  4 ++--
 8 files changed, 19 insertions(+), 17 deletions(-)
 rename app/src/main/java/org/schabi/newpipe/info_list/{ => dialog}/InfoItemDialog.java (99%)
 rename app/src/main/java/org/schabi/newpipe/{util => info_list/dialog}/StreamDialogDefaultEntry.java (92%)
 rename app/src/main/java/org/schabi/newpipe/{util => info_list/dialog}/StreamDialogEntry.java (96%)
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
index b1fa0059c..db14fead9 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java
@@ -29,8 +29,8 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
 import org.schabi.newpipe.extractor.stream.StreamInfoItem;
 import org.schabi.newpipe.fragments.BaseStateFragment;
 import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
-import org.schabi.newpipe.info_list.InfoListAdapter;
 import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
+import org.schabi.newpipe.info_list.InfoListAdapter;
 import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.OnClickGesture;
 import org.schabi.newpipe.util.StateSaver;
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index 15110183c..50dd73ffc 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -35,7 +35,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
 import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
 import org.schabi.newpipe.extractor.stream.StreamInfoItem;
 import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
-import org.schabi.newpipe.info_list.InfoItemDialog;
+import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
 import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
 import org.schabi.newpipe.player.MainPlayer.PlayerType;
 import org.schabi.newpipe.player.playqueue.PlayQueue;
@@ -44,7 +44,7 @@ import org.schabi.newpipe.util.ExtractorHelper;
 import org.schabi.newpipe.util.Localization;
 import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.PicassoHelper;
-import org.schabi.newpipe.util.StreamDialogDefaultEntry;
+import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
 import org.schabi.newpipe.util.external_communication.ShareUtils;
 
 import java.util.ArrayList;
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/InfoItemDialog.java
similarity index 99%
rename from app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java
rename to app/src/main/java/org/schabi/newpipe/info_list/dialog/InfoItemDialog.java
index 924c03cd8..c573831b4 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/InfoItemDialog.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.info_list;
+package org.schabi.newpipe.info_list.dialog;
 
 import static org.schabi.newpipe.MainActivity.DEBUG;
 
@@ -24,8 +24,6 @@ import org.schabi.newpipe.extractor.InfoItem;
 import org.schabi.newpipe.extractor.stream.StreamInfoItem;
 import org.schabi.newpipe.extractor.stream.StreamType;
 import org.schabi.newpipe.player.helper.PlayerHolder;
-import org.schabi.newpipe.util.StreamDialogDefaultEntry;
-import org.schabi.newpipe.util.StreamDialogEntry;
 import org.schabi.newpipe.util.external_communication.KoreUtils;
 
 import java.util.ArrayList;
diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogDefaultEntry.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java
similarity index 92%
rename from app/src/main/java/org/schabi/newpipe/util/StreamDialogDefaultEntry.java
rename to app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java
index a395d1ec1..66894ff88 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogDefaultEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java
@@ -1,7 +1,7 @@
-package org.schabi.newpipe.util;
+package org.schabi.newpipe.info_list.dialog;
 
-import static org.schabi.newpipe.util.StreamDialogEntry.fetchItemInfoIfSparse;
-import static org.schabi.newpipe.util.StreamDialogEntry.openChannelFragment;
+import static org.schabi.newpipe.info_list.dialog.StreamDialogEntry.fetchItemInfoIfSparse;
+import static org.schabi.newpipe.info_list.dialog.StreamDialogEntry.openChannelFragment;
 
 import android.net.Uri;
 
@@ -13,6 +13,8 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
 import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
 import org.schabi.newpipe.local.dialog.PlaylistDialog;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
+import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.SaveUploaderUrlHelper;
 import org.schabi.newpipe.util.external_communication.KoreUtils;
 import org.schabi.newpipe.util.external_communication.ShareUtils;
 
@@ -23,7 +25,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
 /**
  * 
  *     This enum provides entries that are accepted
- *     by the {@link org.schabi.newpipe.info_list.InfoItemDialog.Builder}.
+ *     by the {@link InfoItemDialog.Builder}.
  * 
  * 
  *     These entries contain a String {@link #resource} which is displayed in the dialog and
@@ -31,7 +33,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
  *     when the entry is selected (via onClick()).
  *     
  *     They action can be overridden by using the Builder's
- *     {@link org.schabi.newpipe.info_list.InfoItemDialog.Builder#setAction(
+ *     {@link InfoItemDialog.Builder#setAction(
  *     StreamDialogDefaultEntry, StreamDialogEntry.StreamDialogEntryAction)}
  *     method.
  * 
diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogEntry.java
similarity index 96%
rename from app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
rename to app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogEntry.java
index 24d616819..98c3d9aa3 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogEntry.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.util;
+package org.schabi.newpipe.info_list.dialog;
 
 import android.content.Context;
 
@@ -13,6 +13,8 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
 import org.schabi.newpipe.extractor.stream.StreamType;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
+import org.schabi.newpipe.util.ExtractorHelper;
+import org.schabi.newpipe.util.NavigationHelper;
 
 import java.util.function.Consumer;
 
diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt
index e229a8789..8285d21e6 100644
--- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt
@@ -70,7 +70,7 @@ import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
 import org.schabi.newpipe.extractor.stream.StreamInfoItem
 import org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty
 import org.schabi.newpipe.fragments.BaseStateFragment
-import org.schabi.newpipe.info_list.InfoItemDialog
+import org.schabi.newpipe.info_list.dialog.InfoItemDialog
 import org.schabi.newpipe.ktx.animate
 import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling
 import org.schabi.newpipe.ktx.slideUp
diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
index 832fb580f..01df34292 100644
--- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
@@ -28,14 +28,14 @@ import org.schabi.newpipe.databinding.StatisticPlaylistControlBinding;
 import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.extractor.stream.StreamInfoItem;
-import org.schabi.newpipe.info_list.InfoItemDialog;
+import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
 import org.schabi.newpipe.local.BaseLocalListFragment;
 import org.schabi.newpipe.player.playqueue.PlayQueue;
 import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
 import org.schabi.newpipe.settings.HistorySettingsFragment;
 import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.OnClickGesture;
-import org.schabi.newpipe.util.StreamDialogDefaultEntry;
+import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
 
 import java.util.ArrayList;
 import java.util.Collections;
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 ed2b109b5..9ea6c020d 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
@@ -40,7 +40,7 @@ import org.schabi.newpipe.databinding.PlaylistControlBinding;
 import org.schabi.newpipe.error.ErrorInfo;
 import org.schabi.newpipe.error.UserAction;
 import org.schabi.newpipe.extractor.stream.StreamInfoItem;
-import org.schabi.newpipe.info_list.InfoItemDialog;
+import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
 import org.schabi.newpipe.local.BaseLocalListFragment;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.player.MainPlayer.PlayerType;
@@ -49,7 +49,7 @@ import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
 import org.schabi.newpipe.util.Localization;
 import org.schabi.newpipe.util.NavigationHelper;
 import org.schabi.newpipe.util.OnClickGesture;
-import org.schabi.newpipe.util.StreamDialogDefaultEntry;
+import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
 
 import java.util.ArrayList;
 import java.util.Collections;
From ee477b25e55f2a90e9a75a897b98f6c78c5ec592 Mon Sep 17 00:00:00 2001
From: TobiGr 
Date: Sun, 20 Feb 2022 20:26:23 +0100
Subject: [PATCH 052/107] Move StreamDialogEntry.openChannelFragment to
 NavigationHelper
---
 .../info_list/dialog/StreamDialogDefaultEntry.java     |  2 +-
 .../newpipe/info_list/dialog/StreamDialogEntry.java    | 10 ----------
 .../java/org/schabi/newpipe/util/NavigationHelper.java | 10 ++++++++++
 3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java
index 66894ff88..eda9e19bc 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java
@@ -1,7 +1,7 @@
 package org.schabi.newpipe.info_list.dialog;
 
 import static org.schabi.newpipe.info_list.dialog.StreamDialogEntry.fetchItemInfoIfSparse;
-import static org.schabi.newpipe.info_list.dialog.StreamDialogEntry.openChannelFragment;
+import static org.schabi.newpipe.util.NavigationHelper.openChannelFragment;
 
 import android.net.Uri;
 
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogEntry.java
index 98c3d9aa3..a8d361447 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogEntry.java
@@ -14,7 +14,6 @@ import org.schabi.newpipe.extractor.stream.StreamType;
 import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
 import org.schabi.newpipe.util.ExtractorHelper;
-import org.schabi.newpipe.util.NavigationHelper;
 
 import java.util.function.Consumer;
 
@@ -42,15 +41,6 @@ public class StreamDialogEntry {
         void onClick(Fragment fragment, StreamInfoItem infoItem);
     }
 
-    public static void openChannelFragment(@NonNull final Fragment fragment,
-                                            @NonNull final StreamInfoItem item,
-                                            final String uploaderUrl) {
-        // For some reason `getParentFragmentManager()` doesn't work, but this does.
-        NavigationHelper.openChannelFragment(
-                fragment.requireActivity().getSupportFragmentManager(),
-                item.getServiceId(), uploaderUrl, item.getUploaderName());
-    }
-
     /**
      * Fetches a {@link StreamInfoItem} if it is incomplete and executes the callback.
      * 
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index 22e0a2dd0..2502bef8a 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -31,6 +31,7 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
 import org.schabi.newpipe.extractor.stream.AudioStream;
 import org.schabi.newpipe.extractor.stream.Stream;
 import org.schabi.newpipe.extractor.stream.StreamInfo;
+import org.schabi.newpipe.extractor.stream.StreamInfoItem;
 import org.schabi.newpipe.extractor.stream.VideoStream;
 import org.schabi.newpipe.fragments.MainFragment;
 import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
@@ -402,6 +403,15 @@ public final class NavigationHelper {
                 .commit();
     }
 
+    public static void openChannelFragment(@NonNull final Fragment fragment,
+                                           @NonNull final StreamInfoItem item,
+                                           final String uploaderUrl) {
+        // For some reason `getParentFragmentManager()` doesn't work, but this does.
+        openChannelFragment(
+                fragment.requireActivity().getSupportFragmentManager(),
+                item.getServiceId(), uploaderUrl, item.getUploaderName());
+    }
+
     public static void openPlaylistFragment(final FragmentManager fragmentManager,
                                             final int serviceId, final String url,
                                             @NonNull final String name) {
From 96b930cd070ef067607788dcd0aade5a488725fc Mon Sep 17 00:00:00 2001
From: litetex <40789489+litetex@users.noreply.github.com>
Date: Mon, 21 Feb 2022 20:30:56 +0100
Subject: [PATCH 053/107] Revert "Respect cutouts when playing in MultiWindow"
This reverts commit c92a90749ef1188f50afe528245a54afd180613c.
---
 .../fragments/detail/VideoDetailFragment.java     | 15 +++------------
 1 file changed, 3 insertions(+), 12 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
index 2d9abc6dc..78f0bfffb 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -1994,9 +1994,7 @@ public final class VideoDetailFragment
         // Prevent jumping of the player on devices with cutout
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
             activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
-                    isMultiWindowOrFullscreen()
-                            ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
-                            : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
         }
         activity.getWindow().getDecorView().setSystemUiVisibility(0);
         activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -2018,9 +2016,7 @@ public final class VideoDetailFragment
         // Prevent jumping of the player on devices with cutout
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
             activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
-                    isMultiWindowOrFullscreen()
-                            ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
-                            : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
         }
         int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -2037,7 +2033,7 @@ public final class VideoDetailFragment
         activity.getWindow().getDecorView().setSystemUiVisibility(visibility);
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
-                && isMultiWindowOrFullscreen()) {
+                && (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen()))) {
             activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
             activity.getWindow().setNavigationBarColor(Color.TRANSPARENT);
         }
@@ -2053,11 +2049,6 @@ public final class VideoDetailFragment
         }
     }
 
-    private boolean isMultiWindowOrFullscreen() {
-        return DeviceUtils.isInMultiWindow(activity)
-                || (isPlayerAvailable() && player.isFullscreen());
-    }
-
     private boolean playerIsNotStopped() {
         return isPlayerAvailable() && !player.isStopped();
     }
From d3bc18497157572e758f73bfdf5a69a447027fe4 Mon Sep 17 00:00:00 2001
From: TobiGr 
Date: Mon, 21 Feb 2022 21:50:30 +0100
Subject: [PATCH 054/107] Clarify that only StramInfoItems are accepted by the
 builder
---
 .../schabi/newpipe/info_list/dialog/InfoItemDialog.java    | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/dialog/InfoItemDialog.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/InfoItemDialog.java
index c573831b4..2264ab370 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/dialog/InfoItemDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/InfoItemDialog.java
@@ -86,7 +86,7 @@ public final class InfoItemDialog {
     }
 
     /**
-     * Builder to generate a {@link InfoItemDialog}.
+     * Builder to generate a {@link InfoItemDialog} for a {@link StreamInfoItem}.
      * Use {@link #addEntry(StreamDialogDefaultEntry)}
      * and {@link #addAllEntries(StreamDialogDefaultEntry...)} to add options to the dialog.
      * 
@@ -102,7 +102,8 @@ public final class InfoItemDialog {
         private final boolean addDefaultEntriesAutomatically;
 
         /**
-         * Create a Builder instance that automatically adds the some default entries
+         * 
Create a {@link Builder builder} instance for a {@link StreamInfoItem}
+         * that automatically adds the some default entries
          * at the top and bottom of the dialog.
          * The dialog has the following structure:
          * 
@@ -142,7 +143,7 @@ public final class InfoItemDialog {
         }
 
         /**
-         * Create an instance of this Builder.
+         * Create an instance of this {@link Builder} for a {@link StreamInfoItem}.
          * If {@code addDefaultEntriesAutomatically} is set to {@code true},
          * some default entries are added to the top and bottom of the dialog.
          * The dialog has the following structure:
From 01e0dd50ad4c5c4f9f8ac7b33e50cf749c93f85a Mon Sep 17 00:00:00 2001
From: Avently <7953703+avently@users.noreply.github.com>
Date: Wed, 23 Feb 2022 00:53:39 +0300
Subject: [PATCH 055/107] Added serviceId check while comparing PlayQueues
---
 .../org/schabi/newpipe/player/playqueue/PlayQueue.java     | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java
index 750564ce2..f46c9d72f 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java
@@ -532,8 +532,11 @@ public abstract class PlayQueue implements Serializable {
             return false;
         }
         for (int i = 0; i < size(); i++) {
-            // Check is based on URL
-            if (!streams.get(i).getUrl().equals(other.streams.get(i).getUrl())) {
+            final PlayQueueItem stream = streams.get(i);
+            final PlayQueueItem otherStream = other.streams.get(i);
+            // Check is based on serviceId and URL
+            if (stream.getServiceId() != otherStream.getServiceId()
+                    || !stream.getUrl().equals(otherStream.getUrl())) {
                 return false;
             }
         }
From 21dc988e4538c8abfc07cb21cd0ac5e4810b518a Mon Sep 17 00:00:00 2001
From: Stypox 
Date: Wed, 23 Feb 2022 09:15:11 +0100
Subject: [PATCH 056/107] Restore focus handling for TVs in player.xml
---
 app/src/main/res/layout/player.xml | 4 ++++
 1 file changed, 4 insertions(+)
diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml
index 180292fb1..954dd86b2 100644
--- a/app/src/main/res/layout/player.xml
+++ b/app/src/main/res/layout/player.xml
@@ -85,6 +85,7 @@
                 android:layout_height="wrap_content"
                 android:layout_alignParentTop="true"
                 android:baselineAligned="false"
+                android:descendantFocusability="afterDescendants"
                 android:gravity="top"
                 android:orientation="vertical"
                 android:paddingStart="@dimen/player_main_controls_padding"
@@ -96,6 +97,7 @@
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:baselineAligned="false"
+                    android:descendantFocusability="afterDescendants"
                     android:gravity="top"
                     android:minHeight="45dp"
                     tools:ignore="RtlHardcoded">
@@ -438,6 +440,7 @@
                     android:layout_gravity="center"
                     android:layout_marginTop="2dp"
                     android:layout_weight="1"
+                    android:nextFocusDown="@id/screenRotationButton"
                     tools:progress="25"
                     tools:secondaryProgress="50" />
 
@@ -473,6 +476,7 @@
                     android:background="?attr/selectableItemBackgroundBorderless"
                     android:clickable="true"
                     android:focusable="true"
+                    android:nextFocusUp="@id/playbackSeekBar"
                     android:padding="@dimen/player_main_buttons_padding"
                     android:scaleType="fitCenter"
                     android:visibility="gone"
From 4871095a3e4aa353a3e030c0a7782c6a075d29bb Mon Sep 17 00:00:00 2001
From: Stypox 
Date: Wed, 23 Feb 2022 09:16:25 +0100
Subject: [PATCH 057/107] Automatically rearrange code in player.xml
---
 app/src/main/res/layout/player.xml | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml
index 954dd86b2..b520c3f6e 100644
--- a/app/src/main/res/layout/player.xml
+++ b/app/src/main/res/layout/player.xml
@@ -58,9 +58,9 @@
         android:id="@+id/playbackControlsShadow"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:visibility="gone"
         android:layout_alignBottom="@+id/playbackControlRoot"
         android:background="@color/video_overlay_color"
+        android:visibility="gone"
         tools:visibility="visible" />
 
     
@@ -480,8 +480,8 @@
                     android:padding="@dimen/player_main_buttons_padding"
                     android:scaleType="fitCenter"
                     android:visibility="gone"
-                    app:tint="@color/white"
                     app:srcCompat="@drawable/ic_fullscreen"
+                    app:tint="@color/white"
                     tools:ignore="ContentDescription,RtlHardcoded"
                     tools:visibility="visible" />
             
@@ -504,8 +504,8 @@
                 android:clickable="true"
                 android:focusable="true"
                 android:scaleType="fitCenter"
-                app:tint="@color/white"
                 app:srcCompat="@drawable/ic_previous"
+                app:tint="@color/white"
                 tools:ignore="ContentDescription" />
 
 
@@ -516,8 +516,8 @@
                 android:layout_weight="1"
                 android:background="?attr/selectableItemBackgroundBorderless"
                 android:scaleType="fitCenter"
-                app:tint="@color/white"
                 app:srcCompat="@drawable/ic_pause"
+                app:tint="@color/white"
                 tools:ignore="ContentDescription" />
 
             
Date: Wed, 23 Feb 2022 10:13:03 +0100
Subject: [PATCH 058/107] Remove useless title&channel text view focusability
---
 app/src/main/res/layout/player.xml | 4 ----
 1 file changed, 4 deletions(-)
diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml
index b520c3f6e..1fc769c18 100644
--- a/app/src/main/res/layout/player.xml
+++ b/app/src/main/res/layout/player.xml
@@ -132,10 +132,8 @@
                             android:id="@+id/titleTextView"
                             android:layout_width="match_parent"
                             android:layout_height="wrap_content"
-                            android:clickable="true"
                             android:ellipsize="marquee"
                             android:fadingEdge="horizontal"
-                            android:focusable="true"
                             android:marqueeRepeatLimit="marquee_forever"
                             android:scrollHorizontally="true"
                             android:singleLine="true"
@@ -149,10 +147,8 @@
                             android:id="@+id/channelTextView"
                             android:layout_width="match_parent"
                             android:layout_height="wrap_content"
-                            android:clickable="true"
                             android:ellipsize="marquee"
                             android:fadingEdge="horizontal"
-                            android:focusable="true"
                             android:marqueeRepeatLimit="marquee_forever"
                             android:scrollHorizontally="true"
                             android:singleLine="true"
From 443ebc46d6b35552e1b8a1c8accee30ecb0bd6b2 Mon Sep 17 00:00:00 2001
From: Stypox 
Date: Wed, 23 Feb 2022 15:16:37 +0100
Subject: [PATCH 059/107] Release 0.22.1 (984)
---
 app/build.gradle | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index a441a27eb..633da37bd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,8 +16,8 @@ android {
         resValue "string", "app_name", "NewPipe"
         minSdk 19
         targetSdk 29
-        versionCode 983
-        versionName "0.22.0"
+        versionCode 984
+        versionName "0.22.1"
 
         multiDexEnabled true
 
From 3d47d73ba9b39bdc0b5415734d4308f3bd1917f6 Mon Sep 17 00:00:00 2001
From: Stypox 
Date: Wed, 23 Feb 2022 15:11:17 +0100
Subject: [PATCH 060/107] Add changelog for NewPipe 0.22.1 (984)
---
 fastlane/metadata/android/en-US/changelogs/984.txt | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 fastlane/metadata/android/en-US/changelogs/984.txt
diff --git a/fastlane/metadata/android/en-US/changelogs/984.txt b/fastlane/metadata/android/en-US/changelogs/984.txt
new file mode 100644
index 000000000..3b18b4665
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/984.txt
@@ -0,0 +1,7 @@
+Load enough initial items in lists to fill the whole screen and to fix scrolling on tablets and TVs
+Fix random crashes while scrolling through lists
+Have the player fast seek overlay arc go under the system UI
+Revert changes to cutouts when playing in multi window, causing the misplaced player regression on some phones
+Increase compileSdk from 30 to 31
+Update error reporting library
+Refactor some code in the player
From 1a000fecd530c19087b1d7444d97b6684f945cdc Mon Sep 17 00:00:00 2001
From: TacoTheDank 
Date: Wed, 23 Feb 2022 15:11:25 -0500
Subject: [PATCH 061/107] Replace CircleImageView with ShapeableImageView
---
 app/build.gradle                                 |  2 --
 .../org/schabi/newpipe/about/AboutActivity.kt    |  4 ----
 .../list/playlist/PlaylistFragment.java          | 16 +++++++++++++---
 .../holder/ChannelMiniInfoItemHolder.java        |  5 ++---
 .../holder/CommentsMiniInfoItemHolder.java       |  5 ++---
 .../newpipe/settings/SelectChannelFragment.java  |  4 ++--
 .../layout-large-land/fragment_video_detail.xml  |  8 +++++---
 app/src/main/res/layout/channel_header.xml       | 16 ++++++++++------
 .../main/res/layout/fragment_video_detail.xml    |  8 +++++---
 .../main/res/layout/list_channel_grid_item.xml   |  4 +++-
 app/src/main/res/layout/list_channel_item.xml    | 10 ++++++----
 .../main/res/layout/list_channel_mini_item.xml   |  9 ++++++---
 app/src/main/res/layout/list_comments_item.xml   |  8 +++++---
 .../main/res/layout/list_comments_mini_item.xml  |  8 +++++---
 .../main/res/layout/picker_subscription_item.xml |  3 ++-
 app/src/main/res/layout/playlist_header.xml      |  8 +++++---
 app/src/main/res/layout/select_channel_item.xml  |  9 ++++++---
 app/src/main/res/values-land/dimens.xml          |  6 ++++++
 app/src/main/res/values-sw600dp-land/dimens.xml  |  3 ++-
 app/src/main/res/values/dimens.xml               |  5 +++++
 app/src/main/res/values/styles_misc.xml          |  5 +++++
 21 files changed, 95 insertions(+), 51 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index a441a27eb..6335869bf 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -246,8 +246,6 @@ dependencies {
     implementation "com.github.lisawray.groupie:groupie:${groupieVersion}"
     implementation "com.github.lisawray.groupie:groupie-viewbinding:${groupieVersion}"
 
-    // Circular ImageView
-    implementation "de.hdodenhof:circleimageview:3.1.0"
     // Image loading
     //noinspection GradleDependency --> 2.8 is the last version, not 2.71828!
     implementation "com.squareup.picasso:picasso:2.8"
diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
index 1e5bd8799..32c460d0a 100644
--- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
+++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
@@ -117,10 +117,6 @@ class AboutActivity : AppCompatActivity() {
                 "AndroidX", "2005 - 2011", "The Android Open Source Project",
                 "https://developer.android.com/jetpack", StandardLicenses.APACHE2
             ),
-            SoftwareComponent(
-                "CircleImageView", "2014 - 2020", "Henning Dodenhof",
-                "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2
-            ),
             SoftwareComponent(
                 "ExoPlayer", "2014 - 2020", "Google, Inc.",
                 "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index 84dcb4fd9..b73842966 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -6,6 +6,7 @@ import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingSc
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -19,6 +20,10 @@ import android.view.ViewGroup;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.appcompat.content.res.AppCompatResources;
+import androidx.core.content.ContextCompat;
+
+import com.google.android.material.shape.CornerFamily;
+import com.google.android.material.shape.ShapeAppearanceModel;
 
 import org.reactivestreams.Subscriber;
 import org.reactivestreams.Subscription;
@@ -328,9 +333,14 @@ public class PlaylistFragment extends BaseListInfoFragment {
                 && (YoutubeParsingHelper.isYoutubeMixId(result.getId())
                 || YoutubeParsingHelper.isYoutubeMusicMixId(result.getId()))) {
             // this is an auto-generated playlist (e.g. Youtube mix), so a radio is shown
-            headerBinding.uploaderAvatarView.setDisableCircularTransformation(true);
-            headerBinding.uploaderAvatarView.setBorderColor(
-                    getResources().getColor(R.color.transparent_background_color));
+            final ShapeAppearanceModel model = ShapeAppearanceModel.builder()
+                    .setAllCorners(CornerFamily.ROUNDED, 0f)
+                    .build(); // this turns the image back into a square
+            headerBinding.uploaderAvatarView.setShapeAppearanceModel(model);
+            headerBinding.uploaderAvatarView.setStrokeColor(
+                    ColorStateList.valueOf(ContextCompat.getColor(
+                            requireContext(), R.color.transparent_background_color))
+            );
             headerBinding.uploaderAvatarView.setImageDrawable(
                     AppCompatResources.getDrawable(requireContext(),
                     R.drawable.ic_radio)
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java
index 78acb752b..aa4f4c9f0 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java
@@ -1,6 +1,7 @@
 package org.schabi.newpipe.info_list.holder;
 
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.schabi.newpipe.R;
@@ -11,10 +12,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
 import org.schabi.newpipe.util.PicassoHelper;
 import org.schabi.newpipe.util.Localization;
 
-import de.hdodenhof.circleimageview.CircleImageView;
-
 public class ChannelMiniInfoItemHolder extends InfoItemHolder {
-    public final CircleImageView itemThumbnailView;
+    public final ImageView itemThumbnailView;
     public final TextView itemTitleView;
     private final TextView itemAdditionalDetailView;
 
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
index cb47efa92..6e4773c09 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
@@ -7,6 +7,7 @@ import android.text.util.Linkify;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
@@ -28,8 +29,6 @@ import org.schabi.newpipe.util.PicassoHelper;
 
 import java.util.regex.Matcher;
 
-import de.hdodenhof.circleimageview.CircleImageView;
-
 public class CommentsMiniInfoItemHolder extends InfoItemHolder {
     private static final String TAG = "CommentsMiniIIHolder";
 
@@ -40,7 +39,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
     private final int commentVerticalPadding;
 
     private final RelativeLayout itemRoot;
-    public final CircleImageView itemThumbnailView;
+    public final ImageView itemThumbnailView;
     private final TextView itemContentView;
     private final TextView itemLikesCountView;
     private final TextView itemPublishedTime;
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
index 116807cbc..0f25be630 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
@@ -5,6 +5,7 @@ import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
@@ -24,7 +25,6 @@ import org.schabi.newpipe.util.ThemeHelper;
 import java.util.List;
 import java.util.Vector;
 
-import de.hdodenhof.circleimageview.CircleImageView;
 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
 import io.reactivex.rxjava3.core.Observer;
 import io.reactivex.rxjava3.disposables.Disposable;
@@ -200,7 +200,7 @@ public class SelectChannelFragment extends DialogFragment {
 
         public class SelectChannelItemHolder extends RecyclerView.ViewHolder {
             public final View view;
-            final CircleImageView thumbnailView;
+            final ImageView thumbnailView;
             final TextView titleView;
             SelectChannelItemHolder(final View v) {
                 super(v);
diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml
index 1ee11c49b..851085b5b 100644
--- a/app/src/main/res/layout-large-land/fragment_video_detail.xml
+++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml
@@ -266,14 +266,15 @@
                                     android:layout_width="wrap_content"
                                     android:layout_height="wrap_content">
 
-                                    
 
-    
 
-    
 
-    
 
-    
 
-            
 
 
-    
     142dp 
     80dp 
+    80dp 
+    80dp 
+    31dp 
+    41dp 
     
     106dp 
     
@@ -50,4 +54,6 @@
     16sp 
 
     14sp 
+
+    13dp 
 
diff --git a/app/src/main/res/values-sw600dp-land/dimens.xml b/app/src/main/res/values-sw600dp-land/dimens.xml
index a9be78c92..eeb412a29 100644
--- a/app/src/main/res/values-sw600dp-land/dimens.xml
+++ b/app/src/main/res/values-sw600dp-land/dimens.xml
@@ -10,8 +10,9 @@
     12sp 
     
     6dp 
+    33dp 
     1sp 
     5sp 
     
-
+    9dp 
 
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index f3d29b605..022dc5179 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -23,6 +23,10 @@
     
     124dp 
     70dp 
+    70dp 
+    70dp 
+    27dp 
+    33dp 
     164dp 
     92dp 
 
@@ -52,6 +56,7 @@
     
     180dp 
     150dp 
+    9dp 
 
     32dp 
     42dp 
diff --git a/app/src/main/res/values/styles_misc.xml b/app/src/main/res/values/styles_misc.xml
index 5e50b88ff..33f3a48ee 100644
--- a/app/src/main/res/values/styles_misc.xml
+++ b/app/src/main/res/values/styles_misc.xml
@@ -102,4 +102,9 @@
         - ?attr/toolbarSearchColor+    
+
 
From 8291098b6dfc96642f4f2429d84291022f5394e7 Mon Sep 17 00:00:00 2001
From: TacoTheDank
Date: Fri, 25 Feb 2022 19:36:06 -0500
Subject: [PATCH 062/107] Update AGP and Gradle
---
 app/build.gradle                         |   2 +-
 build.gradle                             |   2 +-
 gradle/wrapper/gradle-wrapper.jar        | Bin 59536 -> 59821 bytes
 gradle/wrapper/gradle-wrapper.properties |   4 ++--
 gradlew                                  |  10 +++++-----
 5 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index a441a27eb..f9e5ca326 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@ plugins {
 
 android {
     compileSdk 31
-    buildToolsVersion '30.0.3'
+    buildToolsVersion '31.0.0'
 
     defaultConfig {
         applicationId "org.schabi.newpipe"
diff --git a/build.gradle b/build.gradle
index f7717a4ff..904376ea0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:7.0.4'
+        classpath 'com.android.tools.build:gradle:7.1.2'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 
         // NOTE: Do not place your application dependencies here; they belong
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644
GIT binary patch
delta 8958
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