diff --git a/app/src/main/java/org/schabi/newpipe/error/UserAction.java b/app/src/main/java/org/schabi/newpipe/error/UserAction.java index e8dec9556..d291f0491 100644 --- a/app/src/main/java/org/schabi/newpipe/error/UserAction.java +++ b/app/src/main/java/org/schabi/newpipe/error/UserAction.java @@ -28,8 +28,8 @@ public enum UserAction { DOWNLOAD_FAILED("download failed"), PREFERENCES_MIGRATION("migration of preferences"), SHARE_TO_NEWPIPE("share to newpipe"), - CHECK_FOR_NEW_APP_VERSION("check for new app version"); - + CHECK_FOR_NEW_APP_VERSION("check for new app version"), + OPEN_INFO_ITEM_DIALOG("open info item dialog"); private final String message; 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 63adcb64b..b1fa0059c 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 @@ -3,7 +3,6 @@ package org.schabi.newpipe.fragments.list; import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; -import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; @@ -30,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.InfoItemDialog; import org.schabi.newpipe.info_list.InfoListAdapter; +import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.StateSaver; @@ -403,13 +402,11 @@ public abstract class BaseListFragment extends BaseStateFragment } protected void showInfoItemDialog(final StreamInfoItem item) { - final Context context = getContext(); - final Activity activity = getActivity(); - if (context == null || context.getResources() == null || activity == null) { - return; + try { + new InfoItemDialog.Builder(getActivity(), getContext(), this, item).create().show(); + } catch (final IllegalArgumentException e) { + InfoItemDialog.Builder.reportErrorDuringInitialization(e, item); } - - new InfoItemDialog.Builder(activity, context, this, item).create().show(); } /*////////////////////////////////////////////////////////////////////////// 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 b12cc59fb..15110183c 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 @@ -3,7 +3,6 @@ package org.schabi.newpipe.fragments.list.playlist; import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; -import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.text.TextUtils; @@ -137,20 +136,20 @@ public class PlaylistFragment extends BaseListInfoFragment { @Override protected void showInfoItemDialog(final StreamInfoItem item) { final Context context = getContext(); - final Activity activity = getActivity(); - if (context == null || context.getResources() == null || activity == null) { - return; + try { + final InfoItemDialog.Builder dialogBuilder = + new InfoItemDialog.Builder(getActivity(), context, this, item); + + dialogBuilder + .setAction( + StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND, + (f, infoItem) -> NavigationHelper.playOnBackgroundPlayer( + context, getPlayQueueStartingAt(infoItem), true)) + .create() + .show(); + } catch (final IllegalArgumentException e) { + InfoItemDialog.Builder.reportErrorDuringInitialization(e, item); } - - final InfoItemDialog.Builder dialogBuilder = - new InfoItemDialog.Builder(activity, context, this, item); - - dialogBuilder.setAction(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND, - (fragment, infoItem) -> NavigationHelper.playOnBackgroundPlayer( - context, getPlayQueueStartingAt(infoItem), true)); - - dialogBuilder.create().show(); - } @Override 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 6db66ef14..183b2d8d9 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 @@ -1,8 +1,12 @@ package org.schabi.newpipe.info_list; +import static org.schabi.newpipe.MainActivity.DEBUG; + import android.app.Activity; import android.content.Context; import android.content.DialogInterface; +import android.os.Build; +import android.util.Log; import android.view.View; import android.widget.TextView; @@ -11,7 +15,12 @@ import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceManager; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; +import org.schabi.newpipe.error.UserAction; +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; @@ -21,13 +30,15 @@ import org.schabi.newpipe.util.external_communication.KoreUtils; import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; /** * Dialog for a {@link StreamInfoItem}. - * The dialog'S content are actions that can be performed on the {@link StreamInfoItem}. + * The dialog's content are actions that can be performed on the {@link StreamInfoItem}. * This dialog is mostly used for longpress context menus. */ public final class InfoItemDialog { + private static final String TAG = Build.class.getSimpleName(); /** * Ideally, {@link InfoItemDialog} would extend {@link AlertDialog}. * However, extending {@link AlertDialog} requires many additional lines @@ -42,6 +53,7 @@ public final class InfoItemDialog { @NonNull final StreamInfoItem info, @NonNull final List entries) { + // Create the dialog's title final View bannerView = View.inflate(activity, R.layout.dialog_title, null); bannerView.setSelected(true); @@ -56,9 +68,11 @@ public final class InfoItemDialog { detailsView.setVisibility(View.GONE); } + // Get the entry's descriptions which are displayed in the dialog final String[] items = entries.stream() .map(entry -> entry.getString(activity)).toArray(String[]::new); + // Call an entry's action / onClick method when the entry is selected. final DialogInterface.OnClickListener action = (d, index) -> entries.get(index).action.onClick(fragment, info); @@ -96,7 +110,7 @@ public final class InfoItemDialog { *
          *     + - - - - - - - - - - - - - - - - - - - - - -+
          *     | 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)); + } + } + }