From c29ae0e885a9cb4057c52716731fc43260d54d43 Mon Sep 17 00:00:00 2001
From: aryan
Date: Sat, 27 Dec 2025 11:20:46 +0530
Subject: [PATCH 1/5] Improve accessibility for video lists and player details
---
.../holder/StreamMiniInfoItemHolder.java | 215 +++++++++++++++++-
.../layout/activity_player_queue_control.xml | 16 +-
.../res/layout/dialog_feed_group_create.xml | 4 +-
.../main/res/layout/fragment_video_detail.xml | 6 +-
app/src/main/res/layout/list_stream_item.xml | 1 +
.../main/res/layout/list_stream_mini_item.xml | 1 +
app/src/main/res/values/ids.xml | 15 ++
app/src/main/res/values/strings.xml | 1 +
8 files changed, 245 insertions(+), 14 deletions(-)
create mode 100644 app/src/main/res/values/ids.xml
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
index 642738630..6d3c99307 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
@@ -1,27 +1,51 @@
package org.schabi.newpipe.info_list.holder;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
+import androidx.core.view.AccessibilityDelegateCompat;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.fragment.app.FragmentActivity;
+import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
+import org.schabi.newpipe.download.DownloadDialog;
+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.info_list.InfoItemBuilder;
import org.schabi.newpipe.ktx.ViewUtils;
+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.helper.PlayerHolder;
import org.schabi.newpipe.util.DependentPreferenceHelper;
import org.schabi.newpipe.util.Localization;
+import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.util.SparseItemUtil;
import org.schabi.newpipe.util.StreamTypeUtil;
+import org.schabi.newpipe.util.external_communication.KoreUtils;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.image.CoilHelper;
import org.schabi.newpipe.views.AnimatedProgressBar;
+import java.util.List;
import java.util.concurrent.TimeUnit;
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+
public class StreamMiniInfoItemHolder extends InfoItemHolder {
public final ImageView itemThumbnailView;
public final TextView itemVideoTitleView;
@@ -57,7 +81,8 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
if (item.getDuration() > 0) {
itemDurationView.setText(Localization.getDurationString(item.getDuration()));
- itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
+ itemDurationView.setBackgroundColor(ContextCompat.getColor(
+ itemBuilder.getContext(),
R.color.duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
@@ -76,7 +101,8 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
}
} else if (StreamTypeUtil.isLiveStream(item.getStreamType())) {
itemDurationView.setText(R.string.duration_live);
- itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(),
+ itemDurationView.setBackgroundColor(ContextCompat.getColor(
+ itemBuilder.getContext(),
R.color.live_duration_background_color));
itemDurationView.setVisibility(View.VISIBLE);
itemProgressView.setVisibility(View.GONE);
@@ -145,10 +171,195 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
}
return true;
});
+
+ updateAccessibilityActions(item);
+ }
+
+ private void updateAccessibilityActions(final StreamInfoItem item) {
+ ViewCompat.setAccessibilityDelegate(itemView, new AccessibilityDelegateCompat() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(final View host,
+ final AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ final Context context = itemBuilder.getContext();
+ if (context == null) {
+ return;
+ }
+
+ final PlayerHolder holder = PlayerHolder.INSTANCE;
+ if (holder.isPlayQueueReady()) {
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_enqueue,
+ context.getString(R.string.enqueue_stream)));
+
+ if (holder.getQueuePosition() < holder.getQueueSize() - 1) {
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_enqueue_next,
+ context.getString(R.string.enqueue_next_stream)));
+ }
+ }
+
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_background,
+ context.getString(R.string.start_here_on_background)));
+
+ if (!StreamTypeUtil.isAudio(item.getStreamType())) {
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_popup,
+ context.getString(R.string.start_here_on_popup)));
+ }
+
+ if (context instanceof FragmentActivity) {
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_download,
+ context.getString(R.string.download)));
+
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_playlist,
+ context.getString(R.string.add_to_playlist)));
+ }
+
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_share,
+ context.getString(R.string.share)));
+
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_browser,
+ context.getString(R.string.open_in_browser)));
+
+ if (KoreUtils.shouldShowPlayWithKodi(context, item.getServiceId())) {
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_kodi,
+ context.getString(R.string.play_with_kodi_title)));
+ }
+
+ final boolean isWatchHistoryEnabled = PreferenceManager
+ .getDefaultSharedPreferences(context)
+ .getBoolean(context.getString(R.string.enable_watch_history_key), false);
+ if (isWatchHistoryEnabled && !StreamTypeUtil.isLiveStream(item.getStreamType())) {
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_mark_watched,
+ context.getString(R.string.mark_as_watched)));
+ }
+
+ if (context instanceof AppCompatActivity) {
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_channel_details,
+ context.getString(R.string.show_channel_details)));
+ }
+
+ info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.accessibility_action_show_options,
+ context.getString(R.string.more_options)));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(final View host, final int action,
+ final Bundle args) {
+ final Context context = itemBuilder.getContext();
+ if (context == null) {
+ return super.performAccessibilityAction(host, action, args);
+ }
+
+ if (action == R.id.accessibility_action_show_options) {
+ if (itemBuilder.getOnStreamSelectedListener() != null) {
+ itemBuilder.getOnStreamSelectedListener().held(item);
+ }
+ return true;
+ } else if (action == R.id.accessibility_action_enqueue) {
+ SparseItemUtil.fetchItemInfoIfSparse(context, item,
+ singlePlayQueue -> NavigationHelper.enqueueOnPlayer(
+ context, singlePlayQueue));
+ return true;
+ } else if (action == R.id.accessibility_action_enqueue_next) {
+ SparseItemUtil.fetchItemInfoIfSparse(context, item,
+ singlePlayQueue -> NavigationHelper.enqueueNextOnPlayer(
+ context, singlePlayQueue));
+ return true;
+ } else if (action == R.id.accessibility_action_background) {
+ SparseItemUtil.fetchItemInfoIfSparse(context, item, singlePlayQueue ->
+ NavigationHelper.playOnBackgroundPlayer(
+ context, singlePlayQueue, true));
+ return true;
+ } else if (action == R.id.accessibility_action_popup) {
+ SparseItemUtil.fetchItemInfoIfSparse(context, item, singlePlayQueue ->
+ NavigationHelper.playOnPopupPlayer(
+ context, singlePlayQueue, true));
+ return true;
+ } else if (action == R.id.accessibility_action_download) {
+ SparseItemUtil.fetchStreamInfoAndSaveToDatabase(context,
+ item.getServiceId(),
+ item.getUrl(), info -> {
+ final FragmentActivity activity = (FragmentActivity) context;
+ if (!activity.isFinishing() && !activity.isDestroyed()) {
+ final DownloadDialog downloadDialog =
+ new DownloadDialog(context, info);
+ downloadDialog.show(activity.getSupportFragmentManager(),
+ "downloadDialog");
+ }
+ });
+ return true;
+ } else if (action == R.id.accessibility_action_playlist) {
+ final FragmentActivity activity = (FragmentActivity) context;
+ PlaylistDialog.createCorrespondingDialog(
+ context,
+ List.of(new StreamEntity(item)),
+ dialog -> dialog.show(
+ activity.getSupportFragmentManager(),
+ "StreamDialogEntry@"
+ + (dialog instanceof PlaylistAppendDialog
+ ? "append" : "create")
+ + "_playlist"
+ )
+ );
+ return true;
+ } else if (action == R.id.accessibility_action_share) {
+ ShareUtils.shareText(context, item.getName(),
+ item.getUrl(), item.getThumbnails());
+ return true;
+ } else if (action == R.id.accessibility_action_browser) {
+ ShareUtils.openUrlInBrowser(context, item.getUrl());
+ return true;
+ } else if (action == R.id.accessibility_action_kodi) {
+ KoreUtils.playWithKore(context, Uri.parse(item.getUrl()));
+ return true;
+ } else if (action == R.id.accessibility_action_mark_watched) {
+ new HistoryRecordManager(context)
+ .markAsWatched(item)
+ .doOnError(error -> {
+ ErrorUtil.showSnackbar(
+ context,
+ new ErrorInfo(
+ error,
+ UserAction.OPEN_INFO_ITEM_DIALOG,
+ "Got an error when trying to mark as watched"
+ )
+ );
+ })
+ .onErrorComplete()
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe();
+ return true;
+ } else if (action == R.id.accessibility_action_channel_details) {
+ SparseItemUtil.fetchUploaderUrlIfSparse((AppCompatActivity) context,
+ item.getServiceId(), item.getUrl(),
+ item.getUploaderUrl(),
+ url -> NavigationHelper.openChannelFragment(
+ ((AppCompatActivity) context).getSupportFragmentManager(),
+ item.getServiceId(), url, item.getUploaderName()));
+ return true;
+ }
+
+ return super.performAccessibilityAction(host, action, args);
+ }
+ });
}
private void disableLongClick() {
itemView.setLongClickable(false);
itemView.setOnLongClickListener(null);
+ ViewCompat.setAccessibilityDelegate(itemView, null);
}
}
+
diff --git a/app/src/main/res/layout/activity_player_queue_control.xml b/app/src/main/res/layout/activity_player_queue_control.xml
index 29efa36f9..d2961bf5f 100644
--- a/app/src/main/res/layout/activity_player_queue_control.xml
+++ b/app/src/main/res/layout/activity_player_queue_control.xml
@@ -176,7 +176,7 @@
android:scaleType="fitXY"
android:src="@drawable/ic_repeat"
android:tint="?attr/colorAccent"
- tools:ignore="ContentDescription" />
+ android:contentDescription="@string/notification_action_repeat" />
+ android:contentDescription="@string/previous_stream" />
+ android:tint="?attr/colorAccent"
+ android:contentDescription="@string/rewind" />
+ android:contentDescription="@string/play" />
+ android:tint="?attr/colorAccent"
+ android:contentDescription="@string/forward" />
+ android:contentDescription="@string/next_stream" />
+ android:contentDescription="@string/notification_action_shuffle" />
diff --git a/app/src/main/res/layout/dialog_feed_group_create.xml b/app/src/main/res/layout/dialog_feed_group_create.xml
index 464940238..ca4f34f6a 100644
--- a/app/src/main/res/layout/dialog_feed_group_create.xml
+++ b/app/src/main/res/layout/dialog_feed_group_create.xml
@@ -28,7 +28,7 @@
android:scaleType="centerInside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- tools:ignore="ContentDescription"
+ android:contentDescription="@string/select_icon"
tools:src="@drawable/ic_asterisk" />
+ android:contentDescription="@string/show_more" />
@@ -614,7 +614,7 @@
android:paddingLeft="@dimen/video_item_search_padding"
android:paddingRight="@dimen/video_item_search_padding"
android:scaleType="fitCenter"
- tools:ignore="ContentDescription" />
+ android:contentDescription="@string/switch_to_main" />
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
new file mode 100644
index 000000000..227133feb
--- /dev/null
+++ b/app/src/main/res/values/ids.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d9520a056..6434f8405 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -576,6 +576,7 @@
None
Minimize to background player
Minimize to popup player
+ Select icon
Start playback automatically — %s
Only on Wi-Fi
From df3996c246140164b585bb4c35080a2fbbc05454 Mon Sep 17 00:00:00 2001
From: aryan
Date: Sat, 27 Dec 2025 11:35:23 +0530
Subject: [PATCH 2/5] Include channel name in channel details accessibility
action
---
.../newpipe/info_list/holder/StreamMiniInfoItemHolder.java | 3 ++-
app/src/main/res/values/strings.xml | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
index 6d3c99307..a8afb0fc7 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
@@ -246,7 +246,8 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
if (context instanceof AppCompatActivity) {
info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
R.id.accessibility_action_channel_details,
- context.getString(R.string.show_channel_details)));
+ context.getString(R.string.accessibility_show_channel_details,
+ item.getUploaderName())));
}
info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6434f8405..dff4d7732 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -436,6 +436,7 @@
Audio track
Hold to enqueue
Show channel details
+ Show channel details for %1$s
Enqueue
Enqueued
Enqueue next
From a0616c74ce50c4c967efab4ae46419e3eb3f001d Mon Sep 17 00:00:00 2001
From: tobigr
Date: Sat, 27 Dec 2025 13:21:44 +0100
Subject: [PATCH 3/5] Pass FragementActivity instead of Fragment to
StreamDialogEntryAction
This is necessary if it is not clear which fragment is available.
---
.../info_list/dialog/InfoItemDialog.java | 3 +-
.../dialog/StreamDialogDefaultEntry.java | 85 ++++++++++---------
.../info_list/dialog/StreamDialogEntry.java | 4 +-
3 files changed, 47 insertions(+), 45 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 cbaae2834..a8e7401e7 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
@@ -13,6 +13,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.App;
@@ -73,7 +74,7 @@ public final class InfoItemDialog {
// 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);
+ entries.get(index).action.onClick((FragmentActivity) activity, info);
dialog = new AlertDialog.Builder(activity)
.setCustomTitle(bannerView)
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 5676fee95..017dd2cb1 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
@@ -9,6 +9,7 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
+import androidx.fragment.app.FragmentManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamEntity;
@@ -44,44 +45,43 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
*
*/
public enum StreamDialogDefaultEntry {
- SHOW_CHANNEL_DETAILS(R.string.show_channel_details, (fragment, item) -> {
- final var activity = fragment.requireActivity();
- fetchUploaderUrlIfSparse(activity, item.getServiceId(), item.getUrl(),
- item.getUploaderUrl(), url -> openChannelFragment(activity, item, url));
- }),
+ SHOW_CHANNEL_DETAILS(R.string.show_channel_details, (fragmentActivity, item) ->
+ fetchUploaderUrlIfSparse(fragmentActivity, item.getServiceId(), item.getUrl(),
+ item.getUploaderUrl(), url -> openChannelFragment(fragmentActivity, item, url))
+ ),
/**
* Enqueues the stream automatically to the current PlayerType.
*/
- ENQUEUE(R.string.enqueue_stream, (fragment, item) ->
- fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
- NavigationHelper.enqueueOnPlayer(fragment.getContext(), singlePlayQueue))
+ ENQUEUE(R.string.enqueue_stream, (fragmentActivity, item) ->
+ fetchItemInfoIfSparse(fragmentActivity, item, singlePlayQueue ->
+ NavigationHelper.enqueueOnPlayer(fragmentActivity, singlePlayQueue))
),
/**
* Enqueues the stream automatically to the current PlayerType
* after the currently playing stream.
*/
- ENQUEUE_NEXT(R.string.enqueue_next_stream, (fragment, item) ->
- fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
- NavigationHelper.enqueueNextOnPlayer(fragment.getContext(), singlePlayQueue))
+ ENQUEUE_NEXT(R.string.enqueue_next_stream, (fragmentActivity, item) ->
+ fetchItemInfoIfSparse(fragmentActivity, item, singlePlayQueue ->
+ NavigationHelper.enqueueNextOnPlayer(fragmentActivity, singlePlayQueue))
),
- START_HERE_ON_BACKGROUND(R.string.start_here_on_background, (fragment, item) ->
- fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
+ START_HERE_ON_BACKGROUND(R.string.start_here_on_background, (fragmentActivity, item) ->
+ fetchItemInfoIfSparse(fragmentActivity, item, singlePlayQueue ->
NavigationHelper.playOnBackgroundPlayer(
- fragment.getContext(), singlePlayQueue, true))),
+ fragmentActivity, singlePlayQueue, true))),
- START_HERE_ON_POPUP(R.string.start_here_on_popup, (fragment, item) ->
- fetchItemInfoIfSparse(fragment.requireContext(), item, singlePlayQueue ->
- NavigationHelper.playOnPopupPlayer(fragment.getContext(), singlePlayQueue, true))),
+ START_HERE_ON_POPUP(R.string.start_here_on_popup, (fragmentActivity, item) ->
+ fetchItemInfoIfSparse(fragmentActivity, item, singlePlayQueue ->
+ NavigationHelper.playOnPopupPlayer(fragmentActivity, singlePlayQueue, true))),
- SET_AS_PLAYLIST_THUMBNAIL(R.string.set_as_playlist_thumbnail, (fragment, item) -> {
+ SET_AS_PLAYLIST_THUMBNAIL(R.string.set_as_playlist_thumbnail, (fragmentActivity, item) -> {
throw new UnsupportedOperationException("This needs to be implemented manually "
+ "by using InfoItemDialog.Builder.setAction()");
}),
- DELETE(R.string.delete, (fragment, item) -> {
+ DELETE(R.string.delete, (fragmentActivity, item) -> {
throw new UnsupportedOperationException("This needs to be implemented manually "
+ "by using InfoItemDialog.Builder.setAction()");
}),
@@ -90,12 +90,12 @@ public enum StreamDialogDefaultEntry {
* 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, (fragmentActivity, item) ->
PlaylistDialog.createCorrespondingDialog(
- fragment.getContext(),
+ fragmentActivity,
List.of(new StreamEntity(item)),
dialog -> dialog.show(
- fragment.getParentFragmentManager(),
+ fragmentActivity.getSupportFragmentManager(),
"StreamDialogEntry@"
+ (dialog instanceof PlaylistAppendDialog ? "append" : "create")
+ "_playlist"
@@ -103,49 +103,50 @@ public enum StreamDialogDefaultEntry {
)
),
- PLAY_WITH_KODI(R.string.play_with_kodi_title, (fragment, item) ->
- KoreUtils.playWithKore(fragment.requireContext(), Uri.parse(item.getUrl()))),
+ PLAY_WITH_KODI(R.string.play_with_kodi_title, (fragmentActivity, item) ->
+ KoreUtils.playWithKore(fragmentActivity, Uri.parse(item.getUrl()))),
- SHARE(R.string.share, (fragment, item) ->
- ShareUtils.shareText(fragment.requireContext(), item.getName(), item.getUrl(),
+ SHARE(R.string.share, (fragmentActivity, item) ->
+ ShareUtils.shareText(fragmentActivity, item.getName(), item.getUrl(),
item.getThumbnails())),
/**
* Opens a {@link DownloadDialog} after fetching some stream info.
- * If the user quits the current fragment, it will not open a DownloadDialog.
+ * If the user quits the current fragmentActivity, it will not open a DownloadDialog.
*/
- DOWNLOAD(R.string.download, (fragment, item) ->
- fetchStreamInfoAndSaveToDatabase(fragment.requireContext(), item.getServiceId(),
+ DOWNLOAD(R.string.download, (fragmentActivity, item) ->
+ fetchStreamInfoAndSaveToDatabase(fragmentActivity, item.getServiceId(),
item.getUrl(), info -> {
- // Ensure the fragment is attached and its state hasn't been saved to avoid
+ // Ensure the fragment in the activity is attached
+ // and its state hasn't been saved to avoid
// showing dialog during lifecycle changes or when the activity is paused,
// e.g. by selecting the download option and opening a different fragment.
- if (fragment.isAdded() && !fragment.isStateSaved()) {
+ final FragmentManager fm = fragmentActivity.getSupportFragmentManager();
+ if (!fm.isStateSaved()) {
final DownloadDialog downloadDialog =
- new DownloadDialog(fragment.requireContext(), info);
- downloadDialog.show(fragment.getChildFragmentManager(),
- "downloadDialog");
+ new DownloadDialog(fragmentActivity, info);
+ downloadDialog.show(fm, "downloadDialog");
}
})
),
- OPEN_IN_BROWSER(R.string.open_in_browser, (fragment, item) ->
- ShareUtils.openUrlInBrowser(fragment.requireContext(), item.getUrl())),
+ OPEN_IN_BROWSER(R.string.open_in_browser, (fragmentActivity, item) ->
+ ShareUtils.openUrlInBrowser(fragmentActivity, item.getUrl())),
- MARK_AS_WATCHED(R.string.mark_as_watched, (fragment, item) ->
- new HistoryRecordManager(fragment.getContext())
+ MARK_AS_WATCHED(R.string.mark_as_watched, (fragmentActivity, item) ->
+ new HistoryRecordManager(fragmentActivity)
.markAsWatched(item)
- .doOnError(error -> {
+ .doOnError(error ->
ErrorUtil.showSnackbar(
- fragment.requireContext(),
+ fragmentActivity,
new ErrorInfo(
error,
UserAction.OPEN_INFO_ITEM_DIALOG,
"Got an error when trying to mark as watched"
)
- );
- })
+ )
+ )
.onErrorComplete()
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
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 9d82e3b58..8cba26bb4 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
@@ -4,7 +4,7 @@ import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
-import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
@@ -26,6 +26,6 @@ public class StreamDialogEntry {
}
public interface StreamDialogEntryAction {
- void onClick(Fragment fragment, StreamInfoItem infoItem);
+ void onClick(FragmentActivity fragmentActivity, StreamInfoItem infoItem);
}
}
From 973c74b751c9b24e4a4098100e23f06d574ad839 Mon Sep 17 00:00:00 2001
From: tobigr
Date: Sat, 27 Dec 2025 13:22:05 +0100
Subject: [PATCH 4/5] Deduplicate actions
The code for the matching StreamDialogEntries was copy-pasted. It is now possible to use the entries directly.
---
.../holder/StreamMiniInfoItemHolder.java | 98 ++++---------------
1 file changed, 21 insertions(+), 77 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
index a8afb0fc7..f110243d2 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
@@ -1,13 +1,13 @@
package org.schabi.newpipe.info_list.holder;
import android.content.Context;
-import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.view.AccessibilityDelegateCompat;
@@ -17,35 +17,23 @@ import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
-import org.schabi.newpipe.download.DownloadDialog;
-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.info_list.InfoItemBuilder;
+import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
import org.schabi.newpipe.ktx.ViewUtils;
-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.helper.PlayerHolder;
import org.schabi.newpipe.util.DependentPreferenceHelper;
import org.schabi.newpipe.util.Localization;
-import org.schabi.newpipe.util.NavigationHelper;
-import org.schabi.newpipe.util.SparseItemUtil;
import org.schabi.newpipe.util.StreamTypeUtil;
import org.schabi.newpipe.util.external_communication.KoreUtils;
-import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.image.CoilHelper;
import org.schabi.newpipe.views.AnimatedProgressBar;
-import java.util.List;
import java.util.concurrent.TimeUnit;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-
public class StreamMiniInfoItemHolder extends InfoItemHolder {
public final ImageView itemThumbnailView;
public final TextView itemVideoTitleView;
@@ -178,8 +166,9 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
private void updateAccessibilityActions(final StreamInfoItem item) {
ViewCompat.setAccessibilityDelegate(itemView, new AccessibilityDelegateCompat() {
@Override
- public void onInitializeAccessibilityNodeInfo(final View host,
- final AccessibilityNodeInfoCompat info) {
+ public void onInitializeAccessibilityNodeInfo(
+ @NonNull final View host,
+ @NonNull final AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
final Context context = itemBuilder.getContext();
@@ -256,99 +245,54 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
}
@Override
- public boolean performAccessibilityAction(final View host, final int action,
+ public boolean performAccessibilityAction(@NonNull final View host, final int action,
final Bundle args) {
final Context context = itemBuilder.getContext();
+ final FragmentActivity fActivity = ((FragmentActivity) context);
+
if (context == null) {
return super.performAccessibilityAction(host, action, args);
}
if (action == R.id.accessibility_action_show_options) {
+ // display stream dialog
if (itemBuilder.getOnStreamSelectedListener() != null) {
itemBuilder.getOnStreamSelectedListener().held(item);
}
return true;
} else if (action == R.id.accessibility_action_enqueue) {
- SparseItemUtil.fetchItemInfoIfSparse(context, item,
- singlePlayQueue -> NavigationHelper.enqueueOnPlayer(
- context, singlePlayQueue));
+ StreamDialogDefaultEntry.ENQUEUE.action.onClick(fActivity, item);
return true;
} else if (action == R.id.accessibility_action_enqueue_next) {
- SparseItemUtil.fetchItemInfoIfSparse(context, item,
- singlePlayQueue -> NavigationHelper.enqueueNextOnPlayer(
- context, singlePlayQueue));
+ StreamDialogDefaultEntry.ENQUEUE_NEXT.action.onClick(fActivity, item);
return true;
} else if (action == R.id.accessibility_action_background) {
- SparseItemUtil.fetchItemInfoIfSparse(context, item, singlePlayQueue ->
- NavigationHelper.playOnBackgroundPlayer(
- context, singlePlayQueue, true));
+ StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND.action.onClick(
+ fActivity, item);
return true;
} else if (action == R.id.accessibility_action_popup) {
- SparseItemUtil.fetchItemInfoIfSparse(context, item, singlePlayQueue ->
- NavigationHelper.playOnPopupPlayer(
- context, singlePlayQueue, true));
+ StreamDialogDefaultEntry.START_HERE_ON_POPUP.action.onClick(fActivity, item);
return true;
} else if (action == R.id.accessibility_action_download) {
- SparseItemUtil.fetchStreamInfoAndSaveToDatabase(context,
- item.getServiceId(),
- item.getUrl(), info -> {
- final FragmentActivity activity = (FragmentActivity) context;
- if (!activity.isFinishing() && !activity.isDestroyed()) {
- final DownloadDialog downloadDialog =
- new DownloadDialog(context, info);
- downloadDialog.show(activity.getSupportFragmentManager(),
- "downloadDialog");
- }
- });
+ StreamDialogDefaultEntry.DOWNLOAD.action.onClick(fActivity, item);
return true;
} else if (action == R.id.accessibility_action_playlist) {
- final FragmentActivity activity = (FragmentActivity) context;
- PlaylistDialog.createCorrespondingDialog(
- context,
- List.of(new StreamEntity(item)),
- dialog -> dialog.show(
- activity.getSupportFragmentManager(),
- "StreamDialogEntry@"
- + (dialog instanceof PlaylistAppendDialog
- ? "append" : "create")
- + "_playlist"
- )
- );
+ StreamDialogDefaultEntry.APPEND_PLAYLIST.action.onClick(fActivity, item);
return true;
} else if (action == R.id.accessibility_action_share) {
- ShareUtils.shareText(context, item.getName(),
- item.getUrl(), item.getThumbnails());
+ StreamDialogDefaultEntry.SHARE.action.onClick(fActivity, item);
return true;
} else if (action == R.id.accessibility_action_browser) {
- ShareUtils.openUrlInBrowser(context, item.getUrl());
+ StreamDialogDefaultEntry.OPEN_IN_BROWSER.action.onClick(fActivity, item);
return true;
} else if (action == R.id.accessibility_action_kodi) {
- KoreUtils.playWithKore(context, Uri.parse(item.getUrl()));
+ StreamDialogDefaultEntry.PLAY_WITH_KODI.action.onClick(fActivity, item);
return true;
} else if (action == R.id.accessibility_action_mark_watched) {
- new HistoryRecordManager(context)
- .markAsWatched(item)
- .doOnError(error -> {
- ErrorUtil.showSnackbar(
- context,
- new ErrorInfo(
- error,
- UserAction.OPEN_INFO_ITEM_DIALOG,
- "Got an error when trying to mark as watched"
- )
- );
- })
- .onErrorComplete()
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe();
+ StreamDialogDefaultEntry.MARK_AS_WATCHED.action.onClick(fActivity, item);
return true;
} else if (action == R.id.accessibility_action_channel_details) {
- SparseItemUtil.fetchUploaderUrlIfSparse((AppCompatActivity) context,
- item.getServiceId(), item.getUrl(),
- item.getUploaderUrl(),
- url -> NavigationHelper.openChannelFragment(
- ((AppCompatActivity) context).getSupportFragmentManager(),
- item.getServiceId(), url, item.getUploaderName()));
+ StreamDialogDefaultEntry.SHOW_CHANNEL_DETAILS.action.onClick(fActivity, item);
return true;
}
From ab114d2bdbdd733a7fb98dbc0cd2cb45fadd1cd2 Mon Sep 17 00:00:00 2001
From: TobiGr
Date: Sat, 27 Dec 2025 13:59:17 +0100
Subject: [PATCH 5/5] Move return true to end
---
.../holder/StreamMiniInfoItemHolder.java | 18 ++++--------------
1 file changed, 4 insertions(+), 14 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
index f110243d2..838736797 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java
@@ -259,44 +259,34 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
if (itemBuilder.getOnStreamSelectedListener() != null) {
itemBuilder.getOnStreamSelectedListener().held(item);
}
- return true;
} else if (action == R.id.accessibility_action_enqueue) {
StreamDialogDefaultEntry.ENQUEUE.action.onClick(fActivity, item);
- return true;
} else if (action == R.id.accessibility_action_enqueue_next) {
StreamDialogDefaultEntry.ENQUEUE_NEXT.action.onClick(fActivity, item);
- return true;
} else if (action == R.id.accessibility_action_background) {
StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND.action.onClick(
fActivity, item);
- return true;
} else if (action == R.id.accessibility_action_popup) {
StreamDialogDefaultEntry.START_HERE_ON_POPUP.action.onClick(fActivity, item);
- return true;
} else if (action == R.id.accessibility_action_download) {
StreamDialogDefaultEntry.DOWNLOAD.action.onClick(fActivity, item);
- return true;
} else if (action == R.id.accessibility_action_playlist) {
StreamDialogDefaultEntry.APPEND_PLAYLIST.action.onClick(fActivity, item);
- return true;
} else if (action == R.id.accessibility_action_share) {
StreamDialogDefaultEntry.SHARE.action.onClick(fActivity, item);
- return true;
} else if (action == R.id.accessibility_action_browser) {
StreamDialogDefaultEntry.OPEN_IN_BROWSER.action.onClick(fActivity, item);
- return true;
} else if (action == R.id.accessibility_action_kodi) {
StreamDialogDefaultEntry.PLAY_WITH_KODI.action.onClick(fActivity, item);
- return true;
} else if (action == R.id.accessibility_action_mark_watched) {
StreamDialogDefaultEntry.MARK_AS_WATCHED.action.onClick(fActivity, item);
- return true;
} else if (action == R.id.accessibility_action_channel_details) {
StreamDialogDefaultEntry.SHOW_CHANNEL_DETAILS.action.onClick(fActivity, item);
- return true;
+ } else {
+ return super.performAccessibilityAction(host, action, args);
}
-
- return super.performAccessibilityAction(host, action, args);
+ // return true if the action was handled
+ return true;
}
});
}