Improve accessibility for video lists and player details
This commit is contained in:
parent
d859a5edc8
commit
c29ae0e885
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/control_backward"
|
||||
@ -191,7 +191,7 @@
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_previous"
|
||||
android:tint="?attr/colorAccent"
|
||||
tools:ignore="ContentDescription" />
|
||||
android:contentDescription="@string/previous_stream" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/control_fast_rewind"
|
||||
@ -205,7 +205,8 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/exo_controls_rewind"
|
||||
android:tint="?attr/colorAccent" />
|
||||
android:tint="?attr/colorAccent"
|
||||
android:contentDescription="@string/rewind" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/control_play_pause"
|
||||
@ -221,7 +222,7 @@
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_pause"
|
||||
android:tint="?attr/colorAccent"
|
||||
tools:ignore="ContentDescription" />
|
||||
android:contentDescription="@string/play" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/control_progress_bar"
|
||||
@ -255,7 +256,8 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/exo_controls_fastforward"
|
||||
android:tint="?attr/colorAccent" />
|
||||
android:tint="?attr/colorAccent"
|
||||
android:contentDescription="@string/forward" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/control_forward"
|
||||
@ -270,7 +272,7 @@
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_next"
|
||||
android:tint="?attr/colorAccent"
|
||||
tools:ignore="ContentDescription" />
|
||||
android:contentDescription="@string/next_stream" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/control_shuffle"
|
||||
@ -286,7 +288,7 @@
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_shuffle"
|
||||
android:tint="?attr/colorAccent"
|
||||
tools:ignore="ContentDescription" />
|
||||
android:contentDescription="@string/notification_action_shuffle" />
|
||||
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
@ -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" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
@ -195,7 +195,7 @@
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_delete"
|
||||
android:visibility="gone"
|
||||
tools:ignore="ContentDescription"
|
||||
android:contentDescription="@string/delete"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
|
||||
@ -56,8 +56,8 @@
|
||||
android:layout_gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@drawable/ic_play_arrow_shadow"
|
||||
android:contentDescription="@string/play"
|
||||
android:visibility="invisible"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
@ -188,7 +188,7 @@
|
||||
android:layout_marginTop="11dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:src="@drawable/ic_expand_more"
|
||||
tools:ignore="ContentDescription" />
|
||||
android:contentDescription="@string/show_more" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@ -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" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/overlay_metadata_layout"
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
android:layout_width="@dimen/video_item_search_thumbnail_image_width"
|
||||
android:layout_height="@dimen/video_item_search_thumbnail_image_height"
|
||||
android:scaleType="fitCenter"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/placeholder_thumbnail_video"
|
||||
app:layout_constraintBottom_toTopOf="@+id/itemProgressView"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
|
||||
android:scaleType="fitCenter"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/placeholder_thumbnail_video"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
|
||||
15
app/src/main/res/values/ids.xml
Normal file
15
app/src/main/res/values/ids.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="accessibility_action_show_options" type="id"/>
|
||||
<item name="accessibility_action_enqueue" type="id"/>
|
||||
<item name="accessibility_action_enqueue_next" type="id"/>
|
||||
<item name="accessibility_action_background" type="id"/>
|
||||
<item name="accessibility_action_popup" type="id"/>
|
||||
<item name="accessibility_action_download" type="id"/>
|
||||
<item name="accessibility_action_playlist" type="id"/>
|
||||
<item name="accessibility_action_share" type="id"/>
|
||||
<item name="accessibility_action_browser" type="id"/>
|
||||
<item name="accessibility_action_kodi" type="id"/>
|
||||
<item name="accessibility_action_mark_watched" type="id"/>
|
||||
<item name="accessibility_action_channel_details" type="id"/>
|
||||
</resources>
|
||||
@ -576,6 +576,7 @@
|
||||
<string name="minimize_on_exit_none_description">None</string>
|
||||
<string name="minimize_on_exit_background_description">Minimize to background player</string>
|
||||
<string name="minimize_on_exit_popup_description">Minimize to popup player</string>
|
||||
<string name="select_icon">Select icon</string>
|
||||
<!-- Autoplay behavior -->
|
||||
<string name="autoplay_summary">Start playback automatically — %s</string>
|
||||
<string name="wifi_only">Only on Wi-Fi</string>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user