Add long press actions to channels and playlists info items

This commit is contained in:
Stypox 2025-03-14 12:57:26 +01:00
parent a18933792b
commit 1d1688529d
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
5 changed files with 155 additions and 40 deletions

View File

@ -23,6 +23,8 @@ import androidx.recyclerview.widget.RecyclerView;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
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;
@ -258,7 +260,10 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
infoListAdapter.setOnStreamSelectedListener(new OnClickGesture<>() {
@Override
public void selected(final StreamInfoItem selectedItem) {
onStreamSelected(selectedItem);
onItemSelected(selectedItem);
NavigationHelper.openVideoDetailFragment(requireContext(), getFM(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName(),
null, false);
}
@Override
@ -267,23 +272,50 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
}
});
infoListAdapter.setOnChannelSelectedListener(selectedItem -> {
try {
onItemSelected(selectedItem);
NavigationHelper.openChannelFragment(getFM(), selectedItem.getServiceId(),
selectedItem.getUrl(), selectedItem.getName());
} catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e);
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<>() {
@Override
public void selected(final ChannelInfoItem selectedItem) {
try {
onItemSelected(selectedItem);
NavigationHelper.openChannelFragment(getFM(), selectedItem.getServiceId(),
selectedItem.getUrl(), selectedItem.getName());
} catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(BaseListFragment.this, "Opening channel fragment",
e);
}
}
@Override
public void held(final ChannelInfoItem selectedItem) {
openLongPressMenuInActivity(
requireActivity(),
LongPressable.fromChannelInfoItem(selectedItem),
LongPressAction.fromChannelInfoItem(selectedItem)
);
}
});
infoListAdapter.setOnPlaylistSelectedListener(selectedItem -> {
try {
onItemSelected(selectedItem);
NavigationHelper.openPlaylistFragment(getFM(), selectedItem.getServiceId(),
selectedItem.getUrl(), selectedItem.getName());
} catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(this, "Opening playlist fragment", e);
infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<>() {
@Override
public void selected(final PlaylistInfoItem selectedItem) {
try {
BaseListFragment.this.onItemSelected(selectedItem);
NavigationHelper.openPlaylistFragment(BaseListFragment.this.getFM(),
selectedItem.getServiceId(),
selectedItem.getUrl(), selectedItem.getName());
} catch (final Exception e) {
ErrorUtil.showUiErrorSnackbar(BaseListFragment.this,
"Opening playlist fragment", e);
}
}
@Override
public void held(final PlaylistInfoItem selectedItem) {
openLongPressMenuInActivity(
requireActivity(),
LongPressable.fromPlaylistInfoItem(selectedItem),
LongPressAction.fromPlaylistInfoItem(selectedItem)
);
}
});
@ -293,6 +325,14 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
useNormalItemListScrollListener();
}
protected void showInfoItemDialog(final StreamInfoItem item) {
openLongPressMenuInActivity(
requireActivity(),
LongPressable.fromStreamInfoItem(item),
LongPressAction.fromStreamInfoItem(item)
);
}
/**
* Removes all listeners and adds the normal scroll listener to the {@link #itemsList}.
*/
@ -375,27 +415,12 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
}
}
private void onStreamSelected(final StreamInfoItem selectedItem) {
onItemSelected(selectedItem);
NavigationHelper.openVideoDetailFragment(requireContext(), getFM(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName(),
null, false);
}
protected void onScrollToBottom() {
if (hasMoreItems() && !isLoading.get()) {
loadMoreItems();
}
}
protected void showInfoItemDialog(final StreamInfoItem item) {
openLongPressMenuInActivity(
requireActivity(),
LongPressable.fromStreamInfoItem(item),
LongPressAction.fromStreamInfoItem(item)
);
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/

View File

@ -1,10 +1,14 @@
package org.schabi.newpipe.player.playqueue;
import androidx.annotation.Nullable;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabInfo;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.util.ChannelTabHelper;
import org.schabi.newpipe.util.ExtractorHelper;
import java.util.Collections;
@ -15,7 +19,8 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
public final class ChannelTabPlayQueue extends AbstractInfoPlayQueue<ChannelTabInfo> {
final ListLinkHandler linkHandler;
@Nullable
ListLinkHandler linkHandler;
public ChannelTabPlayQueue(final int serviceId,
final ListLinkHandler linkHandler,
@ -31,6 +36,13 @@ public final class ChannelTabPlayQueue extends AbstractInfoPlayQueue<ChannelTabI
this(serviceId, linkHandler, null, Collections.emptyList(), 0);
}
// Plays the first
public ChannelTabPlayQueue(final int serviceId,
final String channelUrl) {
super(serviceId, channelUrl, null, Collections.emptyList(), 0);
linkHandler = null;
}
@Override
protected String getTag() {
return "ChannelTabPlayQueue@" + Integer.toHexString(hashCode());
@ -39,10 +51,29 @@ public final class ChannelTabPlayQueue extends AbstractInfoPlayQueue<ChannelTabI
@Override
public void fetch() {
if (isInitial) {
ExtractorHelper.getChannelTab(this.serviceId, this.linkHandler, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getHeadListObserver());
if (linkHandler == null) {
ExtractorHelper.getChannelInfo(this.serviceId, this.baseUrl, false)
.flatMap(channelInfo -> {
linkHandler = channelInfo.getTabs()
.stream()
.filter(ChannelTabHelper::isStreamsTab)
.findFirst()
.orElseThrow(() -> new ExtractionException(
"No playable channel tab found"));
return ExtractorHelper
.getChannelTab(this.serviceId, this.linkHandler, false);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getHeadListObserver());
} else {
ExtractorHelper.getChannelTab(this.serviceId, this.linkHandler, false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getHeadListObserver());
}
} else {
ExtractorHelper.getMoreChannelTabItems(this.serviceId, this.linkHandler, this.nextPage)
.subscribeOn(Schedulers.io())

View File

@ -5,6 +5,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.util.ExtractorHelper;
import java.util.Collections;
import java.util.List;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
@ -28,6 +29,11 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo>
super(serviceId, url, nextPage, streams, index);
}
public PlaylistPlayQueue(final int serviceId,
final String url) {
this(serviceId, url, null, Collections.emptyList(), 0);
}
@Override
protected String getTag() {
return "PlaylistPlayQueue@" + Integer.toHexString(hashCode());

View File

@ -33,12 +33,16 @@ 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.channel.ChannelInfoItem
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.ktx.findFragmentActivity
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.ChannelTabPlayQueue
import org.schabi.newpipe.player.playqueue.PlayQueue
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue
import org.schabi.newpipe.player.playqueue.SinglePlayQueue
import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.SparseItemUtil
@ -258,14 +262,37 @@ data class LongPressAction(
item: PlaylistRemoteEntity,
onDelete: Runnable,
): List<LongPressAction> {
return buildShareActionList(
item.orderingName ?: "",
item.url ?: "",
item.thumbnailUrl
) +
return buildPlayerActionList { PlaylistPlayQueue(item.serviceId, item.url) } +
buildShareActionList(
item.orderingName ?: "",
item.orderingName ?: "",
item.thumbnailUrl
) +
listOf(
Type.Delete.buildAction { onDelete.run() },
)
}
@JvmStatic
fun fromChannelInfoItem(item: ChannelInfoItem): List<LongPressAction> {
return buildPlayerActionList { ChannelTabPlayQueue(item.serviceId, item.url) } +
buildShareActionList(item) +
listOf(
Type.ShowChannelDetails.buildAction { context ->
NavigationHelper.openChannelFragment(
context.findFragmentActivity().supportFragmentManager,
item.serviceId,
item.url,
item.name,
)
},
)
}
@JvmStatic
fun fromPlaylistInfoItem(item: PlaylistInfoItem): List<LongPressAction> {
return buildPlayerActionList { PlaylistPlayQueue(item.serviceId, item.url) } +
buildShareActionList(item)
}
}
}

View File

@ -5,6 +5,8 @@ import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.ListExtractor
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
@ -92,5 +94,29 @@ data class LongPressable(
item.streamCount ?: ListExtractor.ITEM_COUNT_UNKNOWN
),
)
@JvmStatic
fun fromChannelInfoItem(item: ChannelInfoItem) = LongPressable(
title = item.name,
url = item.url?.takeIf { it.isNotBlank() },
thumbnailUrl = ImageStrategy.choosePreferredImage(item.thumbnails),
uploader = null,
uploaderUrl = item.url?.takeIf { it.isNotBlank() },
viewCount = null,
uploadDate = null,
decoration = null,
)
@JvmStatic
fun fromPlaylistInfoItem(item: PlaylistInfoItem) = LongPressable(
title = item.name,
url = item.url?.takeIf { it.isNotBlank() },
thumbnailUrl = ImageStrategy.choosePreferredImage(item.thumbnails),
uploader = item.uploaderName.takeIf { it.isNotBlank() },
uploaderUrl = item.uploaderUrl?.takeIf { it.isNotBlank() },
viewCount = null,
uploadDate = null,
decoration = Decoration.Playlist(item.streamCount),
)
}
}