diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 7a1ee3095..bafe1d554 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -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 extends BaseStateFragment 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 extends BaseStateFragment } }); - 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 extends BaseStateFragment 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 extends BaseStateFragment } } - 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 //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/ChannelTabPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/ChannelTabPlayQueue.java index a9eb2a19c..77b253283 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/ChannelTabPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/ChannelTabPlayQueue.java @@ -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 { - final ListLinkHandler linkHandler; + @Nullable + ListLinkHandler linkHandler; public ChannelTabPlayQueue(final int serviceId, final ListLinkHandler linkHandler, @@ -31,6 +36,13 @@ public final class ChannelTabPlayQueue extends AbstractInfoPlayQueue { + 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()) diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java index 32316f393..ee87a64f3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlaylistPlayQueue.java @@ -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 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()); diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressAction.kt b/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressAction.kt index 52882a972..b051f9168 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressAction.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressAction.kt @@ -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 { - 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 { + 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 { + return buildPlayerActionList { PlaylistPlayQueue(item.serviceId, item.url) } + + buildShareActionList(item) + } } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressable.kt b/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressable.kt index 644783d78..a29e39a99 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressable.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressable.kt @@ -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), + ) } }