Apply feedback

Return this in InfoIrtemDialog.Builder methoods.
Move null checks for InfoIrtemDialog.Builder into constructor.
Fix and add some more docs.
This commit is contained in:
TobiGr 2022-02-18 23:44:14 +01:00
parent 646d8f431c
commit fd0d76e866
8 changed files with 240 additions and 111 deletions

View File

@ -28,8 +28,8 @@ public enum UserAction {
DOWNLOAD_FAILED("download failed"), DOWNLOAD_FAILED("download failed"),
PREFERENCES_MIGRATION("migration of preferences"), PREFERENCES_MIGRATION("migration of preferences"),
SHARE_TO_NEWPIPE("share to newpipe"), 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; private final String message;

View File

@ -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.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration; 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.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; 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.InfoListAdapter;
import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.StateSaver;
@ -403,13 +402,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
} }
protected void showInfoItemDialog(final StreamInfoItem item) { protected void showInfoItemDialog(final StreamInfoItem item) {
final Context context = getContext(); try {
final Activity activity = getActivity(); new InfoItemDialog.Builder(getActivity(), getContext(), this, item).create().show();
if (context == null || context.getResources() == null || activity == null) { } catch (final IllegalArgumentException e) {
return; InfoItemDialog.Builder.reportErrorDuringInitialization(e, item);
} }
new InfoItemDialog.Builder(activity, context, this, item).create().show();
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////

View File

@ -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.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
@ -137,20 +136,20 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
@Override @Override
protected void showInfoItemDialog(final StreamInfoItem item) { protected void showInfoItemDialog(final StreamInfoItem item) {
final Context context = getContext(); final Context context = getContext();
final Activity activity = getActivity(); try {
if (context == null || context.getResources() == null || activity == null) { final InfoItemDialog.Builder dialogBuilder =
return; 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 @Override

View File

@ -1,8 +1,12 @@
package org.schabi.newpipe.info_list; package org.schabi.newpipe.info_list;
import static org.schabi.newpipe.MainActivity.DEBUG;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Build;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
@ -11,7 +15,12 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R; 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.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.player.helper.PlayerHolder; 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
/** /**
* Dialog for a {@link StreamInfoItem}. * 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. * This dialog is mostly used for longpress context menus.
*/ */
public final class InfoItemDialog { public final class InfoItemDialog {
private static final String TAG = Build.class.getSimpleName();
/** /**
* Ideally, {@link InfoItemDialog} would extend {@link AlertDialog}. * Ideally, {@link InfoItemDialog} would extend {@link AlertDialog}.
* However, extending {@link AlertDialog} requires many additional lines * However, extending {@link AlertDialog} requires many additional lines
@ -42,6 +53,7 @@ public final class InfoItemDialog {
@NonNull final StreamInfoItem info, @NonNull final StreamInfoItem info,
@NonNull final List<StreamDialogEntry> entries) { @NonNull final List<StreamDialogEntry> entries) {
// Create the dialog's title
final View bannerView = View.inflate(activity, R.layout.dialog_title, null); final View bannerView = View.inflate(activity, R.layout.dialog_title, null);
bannerView.setSelected(true); bannerView.setSelected(true);
@ -56,9 +68,11 @@ public final class InfoItemDialog {
detailsView.setVisibility(View.GONE); detailsView.setVisibility(View.GONE);
} }
// Get the entry's descriptions which are displayed in the dialog
final String[] items = entries.stream() final String[] items = entries.stream()
.map(entry -> entry.getString(activity)).toArray(String[]::new); .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) -> final DialogInterface.OnClickListener action = (d, index) ->
entries.get(index).action.onClick(fragment, info); entries.get(index).action.onClick(fragment, info);
@ -96,7 +110,7 @@ public final class InfoItemDialog {
* <pre> * <pre>
* + - - - - - - - - - - - - - - - - - - - - - -+ * + - - - - - - - - - - - - - - - - - - - - - -+
* | ENQUEUE | * | ENQUEUE |
* | ENQUEUE_HERE | * | ENQUEUE_NEXT |
* | START_ON_BACKGROUND | * | START_ON_BACKGROUND |
* | START_ON_POPUP | * | START_ON_POPUP |
* + - - - - - - - - - - - - - - - - - - - - - -+ * + - - - - - - - - - - - - - - - - - - - - - -+
@ -118,10 +132,12 @@ public final class InfoItemDialog {
* @param context * @param context
* @param fragment * @param fragment
* @param infoItem the item for this dialog; all entries and their actions work with * @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 <code>activity, context</code>
* or resources is <code>null</code>
*/ */
public Builder(@NonNull final Activity activity, public Builder(final Activity activity,
@NonNull final Context context, final Context context,
@NonNull final Fragment fragment, @NonNull final Fragment fragment,
@NonNull final StreamInfoItem infoItem) { @NonNull final StreamInfoItem infoItem) {
this(activity, context, fragment, infoItem, true); this(activity, context, fragment, infoItem, true);
@ -135,7 +151,7 @@ public final class InfoItemDialog {
* <pre> * <pre>
* + - - - - - - - - - - - - - - - - - - - - - -+ * + - - - - - - - - - - - - - - - - - - - - - -+
* | ENQUEUE | * | ENQUEUE |
* | ENQUEUE_HERE | * | ENQUEUE_NEXT |
* | START_ON_BACKGROUND | * | START_ON_BACKGROUND |
* | START_ON_POPUP | * | START_ON_POPUP |
* + - - - - - - - - - - - - - - - - - - - - - -+ * + - - - - - - - - - - - - - - - - - - - - - -+
@ -164,12 +180,21 @@ public final class InfoItemDialog {
* <br/> * <br/>
* Entries added with {@link #addEntry(StreamDialogDefaultEntry)} and * Entries added with {@link #addEntry(StreamDialogDefaultEntry)} and
* {@link #addAllEntries(StreamDialogDefaultEntry...)} are added in between. * {@link #addAllEntries(StreamDialogDefaultEntry...)} are added in between.
* @throws IllegalArgumentException if <code>activity, context</code>
* or resources is <code>null</code>
*/ */
public Builder(@NonNull final Activity activity, public Builder(final Activity activity,
@NonNull final Context context, final Context context,
@NonNull final Fragment fragment, @NonNull final Fragment fragment,
@NonNull final StreamInfoItem infoItem, @NonNull final StreamInfoItem infoItem,
final boolean addDefaultEntriesAutomatically) { 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.activity = activity;
this.context = context; this.context = context;
this.fragment = fragment; 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()); entries.add(entry.toStreamDialogEntry());
return this;
} }
public void addAllEntries(@NonNull final StreamDialogDefaultEntry... newEntries) { /**
for (final StreamDialogDefaultEntry entry: newEntries) { * Adds new entries. These are appended to the current entry list.
this.entries.add(entry.toStreamDialogEntry()); * @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.</p> * does not have an effect.</p>
* @param entry the entry to change * @param entry the entry to change
* @param action the action to perform when the entry is selected * @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) { @NonNull final StreamDialogEntry.StreamDialogEntryAction action) {
for (int i = 0; i < entries.size(); i++) { for (int i = 0; i < entries.size(); i++) {
if (entries.get(i).resource == entry.resource) { if (entries.get(i).resource == entry.resource) {
entries.set(i, new StreamDialogEntry(entry.resource, action)); entries.set(i, new StreamDialogEntry(entry.resource, action));
return; return this;
} }
} }
return this;
} }
/** /**
* Adds {@link StreamDialogDefaultEntry#ENQUEUE} if the player is open and * Adds {@link StreamDialogDefaultEntry#ENQUEUE} if the player is open and
* {@link StreamDialogDefaultEntry#ENQUEUE_NEXT} if there are multiple streams * {@link StreamDialogDefaultEntry#ENQUEUE_NEXT} if there are multiple streams
* in the play queue. * in the play queue.
* @return the current {@link Builder} instance
*/ */
public void addEnqueueEntriesIfNeeded() { public Builder addEnqueueEntriesIfNeeded() {
if (PlayerHolder.getInstance().isPlayerOpen()) { if (PlayerHolder.getInstance().isPlayerOpen()) {
addEntry(StreamDialogDefaultEntry.ENQUEUE); addEntry(StreamDialogDefaultEntry.ENQUEUE);
@ -221,26 +259,30 @@ public final class InfoItemDialog {
addEntry(StreamDialogDefaultEntry.ENQUEUE_NEXT); addEntry(StreamDialogDefaultEntry.ENQUEUE_NEXT);
} }
} }
return this;
} }
/** /**
* Adds the {@link StreamDialogDefaultEntry#START_HERE_ON_BACKGROUND}. * Adds the {@link StreamDialogDefaultEntry#START_HERE_ON_BACKGROUND}.
* If the {@link #infoItem} is not a pure audio (live) stream, * If the {@link #infoItem} is not a pure audio (live) stream,
* {@link StreamDialogDefaultEntry#START_HERE_ON_POPUP} is added, too. * {@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); addEntry(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND);
if (infoItem.getStreamType() != StreamType.AUDIO_STREAM if (infoItem.getStreamType() != StreamType.AUDIO_STREAM
&& infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) { && infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) {
addEntry(StreamDialogDefaultEntry.START_HERE_ON_POPUP); addEntry(StreamDialogDefaultEntry.START_HERE_ON_POPUP);
} }
return this;
} }
/** /**
* Adds {@link StreamDialogDefaultEntry.MARK_AS_WATCHED} if the watch history is enabled * Adds {@link StreamDialogDefaultEntry.MARK_AS_WATCHED} if the watch history is enabled
* and the stream is not a livestream. * and the stream is not a livestream.
* @return the current {@link Builder} instance
*/ */
public void addMarkAsWatchedEntryIfNeeded() { public Builder addMarkAsWatchedEntryIfNeeded() {
final boolean isWatchHistoryEnabled = PreferenceManager final boolean isWatchHistoryEnabled = PreferenceManager
.getDefaultSharedPreferences(context) .getDefaultSharedPreferences(context)
.getBoolean(context.getString(R.string.enable_watch_history_key), false); .getBoolean(context.getString(R.string.enable_watch_history_key), false);
@ -249,12 +291,18 @@ public final class InfoItemDialog {
&& infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) { && infoItem.getStreamType() != StreamType.AUDIO_LIVE_STREAM) {
addEntry(StreamDialogDefaultEntry.MARK_AS_WATCHED); 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())) { if (KoreUtils.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
addEntry(StreamDialogDefaultEntry.PLAY_WITH_KODI); addEntry(StreamDialogDefaultEntry.PLAY_WITH_KODI);
} }
return this;
} }
/** /**
@ -262,16 +310,19 @@ public final class InfoItemDialog {
* <br/> * <br/>
* This method adds the "enqueue" (see {@link #addEnqueueEntriesIfNeeded()}) * This method adds the "enqueue" (see {@link #addEnqueueEntriesIfNeeded()})
* and "start here" (see {@link #addStartHereEntries()} entries. * and "start here" (see {@link #addStartHereEntries()} entries.
* @return the current {@link Builder} instance
*/ */
public void addDefaultBeginningEntries() { public Builder addDefaultBeginningEntries() {
addEnqueueEntriesIfNeeded(); addEnqueueEntriesIfNeeded();
addStartHereEntries(); addStartHereEntries();
return this;
} }
/** /**
* Add the entries which are usually at the bottom of the action list. * 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( addAllEntries(
StreamDialogDefaultEntry.APPEND_PLAYLIST, StreamDialogDefaultEntry.APPEND_PLAYLIST,
StreamDialogDefaultEntry.SHARE, StreamDialogDefaultEntry.SHARE,
@ -280,6 +331,7 @@ public final class InfoItemDialog {
addPlayWithKodiEntryIfNeeded(); addPlayWithKodiEntryIfNeeded();
addMarkAsWatchedEntryIfNeeded(); addMarkAsWatchedEntryIfNeeded();
addEntry(StreamDialogDefaultEntry.SHOW_CHANNEL_DETAILS); 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); 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()));
}
} }
} }

View File

@ -1,6 +1,5 @@
package org.schabi.newpipe.local.history; package org.schabi.newpipe.local.history;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
@ -326,26 +325,28 @@ public class StatisticsPlaylistFragment
private void showInfoItemDialog(final StreamStatisticsEntry item) { private void showInfoItemDialog(final StreamStatisticsEntry item) {
final Context context = getContext(); final Context context = getContext();
final Activity activity = getActivity();
if (context == null || context.getResources() == null || activity == null) {
return;
}
final StreamInfoItem infoItem = item.toStreamInfoItem(); final StreamInfoItem infoItem = item.toStreamInfoItem();
final InfoItemDialog.Builder dialogBuilder = try {
new InfoItemDialog.Builder(activity, context, this, infoItem); final InfoItemDialog.Builder dialogBuilder =
new InfoItemDialog.Builder(getActivity(), context, this, infoItem);
// set entries in the middle; the others are added automatically // set entries in the middle; the others are added automatically
dialogBuilder.addEntry(StreamDialogDefaultEntry.DELETE); dialogBuilder
.addEntry(StreamDialogDefaultEntry.DELETE)
// set custom actions .setAction(
dialogBuilder.setAction(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND, StreamDialogDefaultEntry.DELETE,
(fragment, infoItemDuplicate) -> NavigationHelper (f, i) -> deleteEntry(
.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true)); Math.max(itemListAdapter.getItemsList().indexOf(item), 0)))
dialogBuilder.setAction(StreamDialogDefaultEntry.DELETE, (fragment, infoItemDuplicate) -> .setAction(
deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0))); StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND,
(f, i) -> NavigationHelper.playOnBackgroundPlayer(
dialogBuilder.create().show(); context, getPlayQueueStartingAt(item), true))
.create()
.show();
} catch (final IllegalArgumentException e) {
InfoItemDialog.Builder.reportErrorDuringInitialization(e, infoItem);
}
} }
private void deleteEntry(final int index) { private void deleteEntry(final int index) {

View File

@ -3,7 +3,6 @@ package org.schabi.newpipe.local.playlist;
import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout; import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
@ -740,33 +739,38 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
} }
protected void showInfoItemDialog(final PlaylistStreamEntry item) { protected void showInfoItemDialog(final PlaylistStreamEntry item) {
final Context context = getContext();
final Activity activity = getActivity();
if (context == null || context.getResources() == null || activity == null) {
return;
}
final StreamInfoItem infoItem = item.toStreamInfoItem(); final StreamInfoItem infoItem = item.toStreamInfoItem();
final InfoItemDialog.Builder dialogBuilder = try {
new InfoItemDialog.Builder(activity, context, this, infoItem); final Context context = getContext();
final InfoItemDialog.Builder dialogBuilder =
new InfoItemDialog.Builder(getActivity(), context, this, infoItem);
// add entries in the middle // add entries in the middle
dialogBuilder.addAllEntries( dialogBuilder.addAllEntries(
StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL, StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL,
StreamDialogDefaultEntry.DELETE StreamDialogDefaultEntry.DELETE
); );
// set custom actions; all entries modified here have already been added within the builder // set custom actions
dialogBuilder.setAction(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND, // all entries modified below have already been added within the builder
(fragment, infoItemDuplicate) -> NavigationHelper.playOnBackgroundPlayer( dialogBuilder
context, getPlayQueueStartingAt(item), true)); .setAction(
dialogBuilder.setAction(StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL, StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND,
(fragment, infoItemDuplicate) -> (f, i) -> NavigationHelper.playOnBackgroundPlayer(
changeThumbnailUrl(item.getStreamEntity().getThumbnailUrl())); context, getPlayQueueStartingAt(item), true))
dialogBuilder.setAction(StreamDialogDefaultEntry.DELETE, .setAction(
(fragment, infoItemDuplicate) -> deleteItem(item)); StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL,
(f, i) ->
dialogBuilder.create().show(); 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) { private void setInitialData(final long pid, final String title) {

View File

@ -1,13 +1,14 @@
package org.schabi.newpipe.util; package org.schabi.newpipe.util;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; 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.net.Uri;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R; 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.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction; 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.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager; 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.KoreUtils;
import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.ShareUtils;
@ -73,32 +72,45 @@ public enum StreamDialogDefaultEntry {
}), }),
/** /**
* Enqueues the stream automatically to the current PlayerType.<br> * Enqueues the stream automatically to the current PlayerType.
* <br>
* Info: Add this entry within showStreamDialog.
*/ */
ENQUEUE(R.string.enqueue_stream, (fragment, item) -> 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) -> 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) -> START_HERE_ON_BACKGROUND(R.string.start_here_on_background, (fragment, item) ->
NavigationHelper.playOnBackgroundPlayer(fragment.getContext(), fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
new SinglePlayQueue(item), true)), NavigationHelper.playOnBackgroundPlayer(
fragment.getContext(), singlePlayQueue, true))),
START_HERE_ON_POPUP(R.string.start_here_on_popup, (fragment, item) -> START_HERE_ON_POPUP(R.string.start_here_on_popup, (fragment, item) ->
NavigationHelper.playOnPopupPlayer(fragment.getContext(), fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
new SinglePlayQueue(item), true)), NavigationHelper.playOnPopupPlayer(fragment.getContext(), singlePlayQueue, true))),
SET_AS_PLAYLIST_THUMBNAIL(R.string.set_as_playlist_thumbnail, (fragment, item) -> { 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) -> { 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) -> APPEND_PLAYLIST(R.string.add_to_playlist, (fragment, item) ->
PlaylistDialog.createCorrespondingDialog( PlaylistDialog.createCorrespondingDialog(
fragment.getContext(), fragment.getContext(),
@ -154,12 +166,4 @@ public enum StreamDialogDefaultEntry {
return new StreamDialogEntry(resource, action); 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());
}
} }

View File

@ -2,16 +2,22 @@ package org.schabi.newpipe.util;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment; 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.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 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 { public class StreamDialogEntry {
@ -33,4 +39,61 @@ public class StreamDialogEntry {
public interface StreamDialogEntryAction { public interface StreamDialogEntryAction {
void onClick(Fragment fragment, StreamInfoItem infoItem); 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.
* <br />
* 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<SinglePlayQueue> 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));
}
}
} }