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 ifactivity, context
+ * or resources isnull
*/ - 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 ifactivity, context
+ * or resources isnull
*/ - 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 BaseLocalListFragmentNavigationHelper.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 Consumercallback) { + 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: TobiGrDate: 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
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: TobiGronClick()
). *
* 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. *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: TobiGrDate: 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: StypoxDate: 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" /> @@ -582,8 +582,8 @@ android:focusable="true" android:padding="10dp" android:scaleType="fitXY" - app:tint="@color/white" - app:srcCompat="@drawable/ic_close" /> + app:srcCompat="@drawable/ic_close" + app:tint="@color/white" /> From 99379ede8a5ab0ebd476a3b1776828d595272a68 Mon Sep 17 00:00:00 2001 From: Stypox 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"> - + android:src="@drawable/buddy" + app:shapeAppearance="@style/CircularImageView" /> - diff --git a/app/src/main/res/layout/channel_header.xml b/app/src/main/res/layout/channel_header.xml index 9366faf2c..86a308b9f 100644 --- a/app/src/main/res/layout/channel_header.xml +++ b/app/src/main/res/layout/channel_header.xml @@ -28,23 +28,27 @@ android:layout_marginLeft="8dp" android:layout_marginTop="50dp"> - + app:shapeAppearance="@style/CircularImageView" + app:strokeColor="#ffffff" + app:strokeWidth="2dp" /> - diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 23c9a166a..08a9bcf0a 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -253,14 +253,15 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> - + android:src="@drawable/buddy" + app:shapeAppearance="@style/CircularImageView" /> - diff --git a/app/src/main/res/layout/list_channel_grid_item.xml b/app/src/main/res/layout/list_channel_grid_item.xml index 423bfeb9e..d9084bbe9 100644 --- a/app/src/main/res/layout/list_channel_grid_item.xml +++ b/app/src/main/res/layout/list_channel_grid_item.xml @@ -1,5 +1,6 @@ - - diff --git a/app/src/main/res/layout/list_channel_mini_item.xml b/app/src/main/res/layout/list_channel_mini_item.xml index 650685d50..b66e07a12 100644 --- a/app/src/main/res/layout/list_channel_mini_item.xml +++ b/app/src/main/res/layout/list_channel_mini_item.xml @@ -1,5 +1,6 @@ - - - diff --git a/app/src/main/res/layout/picker_subscription_item.xml b/app/src/main/res/layout/picker_subscription_item.xml index 5f0344057..1aaa1e7d8 100644 --- a/app/src/main/res/layout/picker_subscription_item.xml +++ b/app/src/main/res/layout/picker_subscription_item.xml @@ -17,10 +17,11 @@ android:orientation="vertical" android:padding="4dp"> - - + app:shapeAppearance="@style/CircularImageView" + app:strokeColor="#ffffff" + app:strokeWidth="1dp" /> - 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: TacoTheDankDate: 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 zcmY+KWl$VIlZIh&f(Hri?gR<$?iyT!TL`X;1^2~W7YVSq1qtqM!JWlDxLm%}UESUM zndj}Uny%^UnjhVhFb!8V3s(a#fIy>`VW15{5nuy;_V&a5O#0S&!a4dSkUMz_VHu3S zGA@p9Q$T|Sj}tYGWdjH;Mpp8m&yu&YURcrt{K;R|kM~(*{v%QwrBJIUF+K1kX5ZmF zty3i{d`y0;DgE+d e>vN@yYqFPe1Ud{!&G*Q?iUc^V=|H%4~2|N zW+DM)W!`b&V2mQ0Y4u _)uB=P@-2`v|Wm{>CxER1P^ z>c}ZPZ)xxdOCDu59{X^~2id7+6l6x)U}C4Em?H~F`uOxS1?}xMxTV|5@}PlN%Cg$( zwY6c}r60=z5ZA1L zTMe;84rLtYvcm?M(H~ZqU;6F7Evo{P7! LGcdwO|qf1w+)MsnvK5^c@Uzj<{ zUoej1>95tuSvDJ|5K6k%&UF*uE6kBn47QJw^yEG;u^Z9oYWrK(+oL97hBsUMc_^ z;-lmxebwlB`Er_kXp2$`&o+rPJAN<`WX3ws2K{q@qUp}XTfV{t%KrsZ5vM!Q#4{V& zq>iO$MCiLq#%wXj%`W$_%FRg_WR*quv65TdHhdpV&jlq<=K^K`&!Kl5mA6p4n~p3u zWE{20^hYpn1M}}VmSHBXl1*-)2MP=0_k)EPr#>EoZukiXFDz?Di1I>2@Z^P$pvaF+ zN+qUy63jek2m59;YG)`r^F3-O)0RDIXPhf)XOOdkmu`3SMMSW(g+`Ajt{=h1dt~ks ztrhhP|L4G%5x79N#kwAHh5N){@{fzE7n&%dnisCm65Za<8r_hKvfx4Bg*`%-*-Mvn zFvn~)VP@}1sAyD+B{{8l{EjD10Av&Mz9^Xff*t`lU=q=S#(|>ls520;n3<}X#pyh& z*{CJf7$*&~!9jMnw_D~ikUKJ2+UnXmN6qak{xx%W;BKuXt7@ky!LPI1qk?gDwG@@o zkY+BkIie>{{q==5)kXw(*t#I?__Kwi>`=+s?Gq6X+vtSsaAO&Tf+Bl$vKnzc&%BHM z=loWOQq~n}>l=EL(5&6((ESsQC3^@4jlO5Od{qN#sWV)vqXw}aA>*uvwZopNN(|-T zRTF%5Y_k1R$;(d-)n;hWex{;7b6KgdAVE@&0pd(*qDzBO#YZV%kh%pYt1`hnQ(Fa& zYiDrOTDqk5M7hzp9kI2h!PxNnuJ&xl*zF8sx6!67bA49R1bmUF5bpK&&{eI0U~cH}PM z3aW1$lRb|ItkG 5~_eBNu$|I|vYIdAA9a!pVq<+UTx*M}f G`23zxXp&E=FfnY- zEzKj;Cu_s4v>leO7M2-mE(UzKHL4c$c`3dS*19OpLV^4NI*hWWnJQ9lvzP4c;c?do zqrcsKT*i~eIHl0D3r4N{)+RsB6XhrC^;sp2cf_Eq#6*CV;t8v=V!ISe>>9kPgh}NI z=1UZutslxcT$Ad;_P^;Oouoa(cs!Ctpvi>%aQ+Zp=1d|h{W9Wmf7JWxa(~<# tSZ?C%wu4_5F!fc!<@PIBeJ)Nr^$bB6!_Gic_7}c3J{ QI~Gg5g5jTp9}V6KYgrgaX>pJt}7$!wOht&KO|+z{Iw@YL|@~D zMww}+lG}rm2^peNx>58ME||ZQxFQeVSX8iogHLq_vXb`>RnoEKaTWBF-$JD#Q4BMv zt2(2Qb*x-?ur1Y(NsW8AdtX0#rDB?O(Vs4_xA(u-o!-tBG03OI!pQD+2UytbL5>lG z*(F)KacHqMa4?dxa(Vcrw>IIAeB$3cx#;;5r2X;HE8|}eYdAgCw#tpXNy7C3w1q`9 zGxZ6;@1G%8shz9e+!K2MO*{_RjO}Jo6eL3{TSZ>nY7)Qs`Dhi5><@oh0r)gT7H-?3 zLDsd^@m %JvrS8sta5`QiZNs^*GT}Hiy^zjK2^Ni%`Z|ma)D2 zuyumbvw$M8$haCTI~6M%d4+P)uX%u{Sfg4Al+F7c6;O-*)DKI7E8izSOKB#FcV{M+ zEvY0FBkq!$J0EW$Cxl}3{JwV^ki-T?q6C30Y5e&p@8Rd?$ST-Ghn*-`tB{k54W<>F z5I)TFpUC!E9298=sk>m#FI4sUDy_!8?51F qqW!9LN1(zuDnB3$! pEUjL>N>RNgAG~-9Xm|1lqHseW(%v&6K(DZ3Pano(1-Qe?3%J&>0`~w^Q-p&@ zg@HjvhJk?*hpF7$9P|gkzz`zBz_5Z!C4_-%fCcAgiSilzFQef!@amHDrW!YZS@?7C zs2Y9~>yqO +rkih?kXztzvnB^6W=f52*iyuZPv$ c42$WK7>PHb z6%MYIr5D32KPdwL1hJf{_#jn?`k(taW?mwmZVvrr=y~fNcV$`}v(8};o9AjOJumS4 z`889O91^pkF+|@$d9wVoZ3 ;^j;^sUs&Ubo_qD&MTL%O z&*SE0ujG~ zm;? x)8TLC&ft))nyI zcg44@*Q{cYT+qGrA=In_X{NNCD+B0w#;@g)jvBU;_8od6U>;7HIo@F*=g8CQUo(u^ z3r4FJ7#<@)MXO&5+DgKE&^>^`r!loe7CWE*1k0*0wLFzSOV8jvlX~ WOQ?$1v zk$Or}!;ix0g78^6W;+<=J>z@CBs!<<)HvF(Ls-&`mat pesJ5kkjC)6nGB@b{ii6-Uoho$BT%iJgugTOeZ$5Xo4D7Pd< zC*LJh5V@2#5%aBZCgzlQi3@<_!VfiL0 7ywc)ZbwKPfcR|ElQoS(8x|a7#IR}7#Io= zwg4$8S{egr-NffD)Fg&X9bJSoM25pF&%hf>(T&9bI}=#dPQyNYz;ZZ7EZ =u1n701 SWKkZ9n(-qU ztN`sdWL1uxQ1mKS@x11;O|@^AD9!NeoPx}?EKIr!2>1Qq4gjfGU)tr6?Z5l7JAS3j zZeq{vG{rb%DFE4%$szK}d2UzB{4>L?Tv+NAlE*&Nq6g+XauaSI+N2Y8PJLw+aNg1p zbxr|hI8wcMP&&+(Cu|%+Jq|r>+BHk@{AvfBXKiVldN)@}TBS0LdIpnANCVE26WL-} zV}HJ^?m&$Rkq;Zf*i-hoasnpJVyTH__dbGWrB_R55d*>pTyl6(?$EO@>RCmTX1Hzr zT2)rOng?D4FfZ_C49hjMV*UonG2DlG$^+k=Y%|?Dqae4}JOU=8=fgY4Uh!pa9eEqf zFX&WLPu!jArN*^(>|H>dj~g`ONZhaaD%h_HHrHkk%d~TR_RrX{&eM#P@3x=S^%_6h zh=A)A{id16$zEFq@-D7La;kTuE!oopx^9{uA3y<} 9 z^bQ@U<&pJV6kq7LRF47&!UAvgkBx=)KS_ X!NY28^gQr27P=gKh0+E>$aCx&^vj2uc}ycsfSEP zedhTgUwPx%?;+dESs!g1z}5q9EC+fol}tAH9#fhZQ?q1GjyIaR@}lGCSpM-014T~l zEwriqt~ftwz=@2tn$xP&-rJt?nn5sy8sJ5Roy;pavj@O+tm}d_qmAlvhG(&k>(arz z;e|SiTr+0<&6(-An0*4{7akwUk~Yf4 M!!YKj^swp9WOa%al`%R>V7mi z+5+UodFAaPdi4(8_FO&O!Ymb#@yxk uVMrog(7gkj$G@FLA#ENMxG)4f<}S%Fn?Up$+C%{02AgMKa^ z4SFGWp6U>{Q6VRJV}yjxXT*e`1XaX}(dW1F&RNhpTzvC tzuu;LMhMfJ2LBEy?{^GHG!OF!! zDvs64TG)?MX&9NCE#H3(M0K>O>`ca0WT2YR>PTe&tn?~0FV!MRtdb@v?MAUG&Ef7v zW%7>H (;Mm)RJkt18GXv!&np z?RUxOrCfs;m{fBz5MVlq59idhov21di5>WXWD-594L-X5;|@kyWi@N+(jLuh=o+5l zGGTi~)nflP_G}Yg5Pi%pl88U4+^*ihDoMP&zA*^xJE_X*Ah!jODrijCqQ^{=&hD7& z^)qv3;cu?olaT3pc{)Kcy9jA2E8I)#Kn8qO>70SQ5P8YSCN =_+_&)qg)OYBg|-k^d3*@jRAeB?;yd-O1A0wJ z?K*RDm|wE<(PBz~+C%2CTtzCTUohxP2*1kE8Of~{KRAvMrO_}NN&@P7SUO{;zx0iK z@or9 R8ydYOFZ f(cHASCAatL%;62IL27~SmASr(7F&NMr+#gNw@z1VM z_ALFwo3)SoANEwRerBdRV`>y`t72#aF2ConmWQp(Xy|msN9$yxhZ1jAQ67lq{vbC5 zujj|MlGo`6Bfn0TfKgi(k=gq0`K~W+X(@GzYlPI4g0M;owH3yG14rhK>lG8lS{`!K z+Nc@glT-DGz?Ym?v#Hq|_mEdPAlHH5jZuh*6glq!+>Lk$S%ED2@+ea6CE@&1-9a?s znglt|fmIK}fg<9@XgHe4*q!aO<-;Xj$T?IzB-{&2` #eA6rdtCi80mpP&vw(Uytxu$#YzNI_ cB>LS z mim>ys;ir;*Dzbr22ZDxO2s;671&J0U<9(n1yj)J zHFNz=ufPcQVEG+ePjB<5C;=H0{>Mi*xD>hQq8`Vi7TjJ$V04$`h3EZGL|}a07oQdR z?{cR(z+d>arn^AUug&voOzzi$ZqaS)blz-z3zr;10x;oP2)|Cyb^WtN2*wNn`YX!Y z+$Pji<7|!XyMCEw4so}xXLU)p)BA~2fl>y2Tt}o9*BPm?AXA8UE8a;>rOgyCwZBFa zyl42y`bc3}+hiZL_|L_LY29vVerM+BVE@YxK>TGm@dHi@Uw*7AIq?QA9?THL603J% zIBJ4y3n8OFzsOI;NH%DZ!MDwMl<#$)d9eVVeqVl(5ZX$PPbt*p_(_9VSXhaUPa9Qu z7)q4vqYKX7ieVSjOmVEbLj4VYtnDpe*0Y&+>0dS^bJ<8s*eHq3tjRAw^+Mu4W^-E= z4;&namG4G;3pVDyPkUw#0kWEO1;HI6M51(1<0|*pa(I!sj}F^)avrE`ShVMKBz}nE zzKgOPMSEp6M>h JzyTHHcjV%W*;Tdb}1xJjCP#=iQuBk_Eho6yCRVp &e!}4IBJ&?ksVc&u#g3+G$oNlJ?mWfADjeBS-Ph3`DKk-~Z70XugH8sq2eba@4 zIC1H_J$`9b$K`J)sGX3d!&>OmC@@rx1TL~NinQOYy72Q_+^&Mg>Ku(fTgaXdr$p_V z#gav1o{k~c>#)u3r@~6v^o)Lf=C{rAlL@!s457pq)pO;Cojx7U{urO4cvXP|E>+dV zmr2?!-5)tk-&*ap^D^2x7NG6nOop2zNFQ9v8-EZ{WCz-h36C)<^|f{V#R_WE^@(T0+d-at5hXX{U?zak*ac-XnyINo+yBD~~3O1I=a z99|CI>502&s-Qi5bv>^2#cQ%ut<4d7KgQ^kE|=%6#VlGiY8$rdJUH{sra;P~cyb_i zeX(kS%w0C?mjhJl9TZp8RS;N~y3(EXEz13oPhOSE4WaTljGkVXWd~|#) vsG6_76I)Kb z8ro?;{j^lxNsaxE-cfP;g(e;mhh3)&ba}li?woV2#7ByioiD>s%L_D;?#;C#z;a(N z-_WY<=SH42m9bFQ>Nb