Replace InfoItemDialog with LongPressMenu

This commit is contained in:
Stypox 2025-02-12 01:03:34 +01:00
parent eca3486e09
commit 985872f2c1
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
11 changed files with 166 additions and 331 deletions

View File

@ -2,6 +2,7 @@ package org.schabi.newpipe.fragments.list;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
import static org.schabi.newpipe.ui.components.menu.LongPressMenuKt.openLongPressMenuInActivity;
import android.content.Context;
import android.content.SharedPreferences;
@ -27,7 +28,8 @@ import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.info_list.ItemViewMode;
import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
import org.schabi.newpipe.ui.components.menu.LongPressAction;
import org.schabi.newpipe.ui.components.menu.LongPressable;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.StateSaver;
@ -387,11 +389,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
}
protected void showInfoItemDialog(final StreamInfoItem item) {
try {
new InfoItemDialog.Builder(getActivity(), getContext(), this, item).create().show();
} catch (final IllegalArgumentException e) {
InfoItemDialog.Builder.reportErrorDuringInitialization(e, item);
}
openLongPressMenuInActivity(
requireActivity(),
LongPressable.fromStreamInfoItem(item),
LongPressAction.fromStreamInfoItem(item)
);
}
/*//////////////////////////////////////////////////////////////////////////

View File

@ -3,10 +3,9 @@ package org.schabi.newpipe.fragments.list.playlist;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
import static org.schabi.newpipe.ui.components.menu.LongPressMenuKt.getLongPressMenuView;
import static org.schabi.newpipe.ui.components.menu.LongPressMenuKt.openLongPressMenuInActivity;
import static org.schabi.newpipe.util.ServiceHelper.getServiceById;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
@ -43,8 +42,6 @@ import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
import org.schabi.newpipe.extractor.stream.Description;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.player.playqueue.PlayQueue;
@ -153,35 +150,12 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
@Override
protected void showInfoItemDialog(final StreamInfoItem item) {
activity.addContentView(
getLongPressMenuView(
requireContext(),
LongPressable.from(item),
LongPressAction.buildActionList(item, false)
),
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
openLongPressMenuInActivity(
activity,
LongPressable.fromStreamInfoItem(item),
// TODO handle play queue starting at
LongPressAction.fromStreamInfoItem(item)
);
if (Context.class.getSimpleName().startsWith("C")) {
return;
}
final Context context = getContext();
try {
final InfoItemDialog.Builder dialogBuilder =
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);
}
}
@Override

View File

@ -20,7 +20,6 @@
package org.schabi.newpipe.local.feed
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
@ -65,17 +64,18 @@ import org.schabi.newpipe.error.ErrorUtil
import org.schabi.newpipe.error.UserAction
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty
import org.schabi.newpipe.fragments.BaseStateFragment
import org.schabi.newpipe.info_list.ItemViewMode
import org.schabi.newpipe.info_list.dialog.InfoItemDialog
import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.ktx.animateHideRecyclerViewAllowingScrolling
import org.schabi.newpipe.ktx.slideUp
import org.schabi.newpipe.local.feed.item.StreamItem
import org.schabi.newpipe.local.feed.service.FeedLoadService
import org.schabi.newpipe.local.subscription.SubscriptionManager
import org.schabi.newpipe.ui.components.menu.LongPressAction
import org.schabi.newpipe.ui.components.menu.LongPressable
import org.schabi.newpipe.ui.components.menu.openLongPressMenuInActivity
import org.schabi.newpipe.ui.emptystate.setEmptyStateComposable
import org.schabi.newpipe.util.DeviceUtils
import org.schabi.newpipe.util.Localization
@ -381,14 +381,6 @@ class FeedFragment : BaseStateFragment<FeedState>() {
feedBinding.loadingProgressBar.max = progressState.maxProgress
}
private fun showInfoItemDialog(item: StreamInfoItem) {
val context = context
val activity: Activity? = getActivity()
if (context == null || context.resources == null || activity == null) return
InfoItemDialog.Builder(activity, context, this, item).create().show()
}
private val listenerStreamItem = object : OnItemClickListener, OnItemLongClickListener {
override fun onItemClick(item: Item<*>, view: View) {
if (item is StreamItem && !isRefreshing) {
@ -407,7 +399,11 @@ class FeedFragment : BaseStateFragment<FeedState>() {
override fun onItemLongClick(item: Item<*>, view: View): Boolean {
if (item is StreamItem && !isRefreshing) {
showInfoItemDialog(item.streamWithState.stream.toStreamInfoItem())
openLongPressMenuInActivity(
requireActivity(),
LongPressable.fromStreamEntity(item.streamWithState.stream),
LongPressAction.fromStreamEntity(item.streamWithState.stream),
)
return true
}
return false

View File

@ -1,6 +1,7 @@
package org.schabi.newpipe.local.history;
import android.content.Context;
import static org.schabi.newpipe.ui.components.menu.LongPressMenuKt.openLongPressMenuInActivity;
import android.os.Bundle;
import android.os.Parcelable;
import android.view.LayoutInflater;
@ -9,13 +10,11 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.evernote.android.state.State;
import com.google.android.material.snackbar.Snackbar;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
@ -29,12 +28,12 @@ import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder;
import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.settings.HistorySettingsFragment;
import org.schabi.newpipe.ui.components.menu.LongPressAction;
import org.schabi.newpipe.ui.components.menu.LongPressable;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.PlayButtonHelper;
@ -48,7 +47,6 @@ import java.util.function.Supplier;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
public class StatisticsPlaylistFragment
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void>
@ -318,50 +316,11 @@ public class StatisticsPlaylistFragment
}
private void showInfoItemDialog(final StreamStatisticsEntry item) {
final Context context = getContext();
final StreamInfoItem infoItem = item.toStreamInfoItem();
try {
final InfoItemDialog.Builder dialogBuilder =
new InfoItemDialog.Builder(getActivity(), context, this, infoItem);
// set entries in the middle; the others are added automatically
dialogBuilder
.addEntry(StreamDialogDefaultEntry.DELETE)
.setAction(
StreamDialogDefaultEntry.DELETE,
(f, i) -> deleteEntry(
Math.max(itemListAdapter.getItemsList().indexOf(item), 0)))
.create()
.show();
} catch (final IllegalArgumentException e) {
InfoItemDialog.Builder.reportErrorDuringInitialization(e, infoItem);
}
}
private void deleteEntry(final int index) {
final LocalItem infoItem = itemListAdapter.getItemsList().get(index);
if (infoItem instanceof StreamStatisticsEntry) {
final StreamStatisticsEntry entry = (StreamStatisticsEntry) infoItem;
final Disposable onDelete = recordManager
.deleteStreamHistoryAndState(entry.getStreamId())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
() -> {
if (getView() != null) {
Snackbar.make(getView(), R.string.one_item_deleted,
Snackbar.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(),
R.string.one_item_deleted,
Toast.LENGTH_SHORT).show();
}
},
throwable -> showSnackBarError(new ErrorInfo(throwable,
UserAction.DELETE_FROM_HISTORY, "Deleting item")));
disposables.add(onDelete);
}
openLongPressMenuInActivity(
requireActivity(),
LongPressable.fromStreamEntity(item.getStreamEntity()),
LongPressAction.fromStreamStatisticsEntry(item)
);
}
@Override

View File

@ -8,6 +8,7 @@ import static org.schabi.newpipe.local.playlist.ExportPlaylistKt.export;
import static org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS;
import static org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES;
import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST;
import static org.schabi.newpipe.ui.components.menu.LongPressMenuKt.openLongPressMenuInActivity;
import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
@ -52,13 +53,13 @@ import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder;
import org.schabi.newpipe.info_list.dialog.InfoItemDialog;
import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry;
import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.ui.components.menu.LongPressAction;
import org.schabi.newpipe.ui.components.menu.LongPressable;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
@ -789,39 +790,16 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
}
protected void showInfoItemDialog(final PlaylistStreamEntry item) {
final StreamInfoItem infoItem = item.toStreamInfoItem();
try {
final Context context = getContext();
final InfoItemDialog.Builder dialogBuilder =
new InfoItemDialog.Builder(getActivity(), context, this, infoItem);
// add entries in the middle
dialogBuilder.addAllEntries(
StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL,
StreamDialogDefaultEntry.DELETE
);
// set custom actions
// all entries modified below have already been added within the builder
dialogBuilder
.setAction(
StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND,
(f, i) -> NavigationHelper.playOnBackgroundPlayer(
context, getPlayQueueStartingAt(item), true))
.setAction(
StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL,
(f, i) ->
changeThumbnailStreamId(item.getStreamEntity().getUid(),
true))
.setAction(
StreamDialogDefaultEntry.DELETE,
(f, i) -> deleteItem(item))
.create()
.show();
} catch (final IllegalArgumentException e) {
InfoItemDialog.Builder.reportErrorDuringInitialization(e, infoItem);
}
openLongPressMenuInActivity(
requireActivity(),
LongPressable.fromStreamEntity(item.getStreamEntity()),
// TODO getPlayQueueStartingAt(), resumePlayback=true
LongPressAction.fromPlaylistStreamEntry(
item,
() -> deleteItem(item),
() -> changeThumbnailStreamId(item.getStreamEntity().getUid(), true)
)
);
}
private void setInitialData(final long pid, final String title) {

View File

@ -5,10 +5,7 @@ import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
@ -59,20 +56,6 @@ fun ItemList(
}
}
// Handle long clicks for stream items
// TODO: Adjust the menu display depending on where it was triggered
var selectedStream by remember { mutableStateOf<StreamInfoItem?>(null) }
val onLongClick = remember {
{ stream: StreamInfoItem ->
selectedStream = stream
}
}
val onDismissPopup = remember {
{
selectedStream = null
}
}
val showProgress = DependentPreferenceHelper.getPositionsInListsEnabled(context)
val nestedScrollModifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection())
@ -89,15 +72,7 @@ fun ItemList(
val item = items[it]
if (item is StreamInfoItem) {
val isSelected = selectedStream == item
StreamListItem(
item,
showProgress,
isSelected,
onClick,
onLongClick,
onDismissPopup
)
StreamListItem(item, showProgress, onClick)
} else if (item is PlaylistInfoItem) {
PlaylistListItem(item, onClick)
}

View File

@ -10,10 +10,15 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
@ -21,22 +26,26 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.ui.components.menu.LongPressAction
import org.schabi.newpipe.ui.components.menu.LongPressMenu
import org.schabi.newpipe.ui.components.menu.LongPressable
import org.schabi.newpipe.ui.theme.AppTheme
@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun StreamListItem(
stream: StreamInfoItem,
showProgress: Boolean,
isSelected: Boolean,
onClick: (StreamInfoItem) -> Unit = {},
onLongClick: (StreamInfoItem) -> Unit = {},
onDismissPopup: () -> Unit = {}
) {
// Box serves as an anchor for the dropdown menu
var showLongPressMenu by rememberSaveable { mutableStateOf(false) }
Box(
modifier = Modifier
.combinedClickable(onLongClick = { onLongClick(stream) }, onClick = { onClick(stream) })
.combinedClickable(
onLongClick = { showLongPressMenu = true },
onClick = { onClick(stream) }
)
.fillMaxWidth()
.padding(12.dp)
) {
@ -67,7 +76,13 @@ fun StreamListItem(
}
}
StreamMenu(stream, isSelected, onDismissPopup)
if (showLongPressMenu) {
LongPressMenu(
longPressable = LongPressable.fromStreamInfoItem(stream),
longPressActions = LongPressAction.fromStreamInfoItem(stream),
onDismissRequest = { showLongPressMenu = false },
)
}
}
}
@ -79,7 +94,7 @@ private fun StreamListItemPreview(
) {
AppTheme {
Surface {
StreamListItem(stream, showProgress = false, isSelected = false)
StreamListItem(stream, showProgress = false)
}
}
}

View File

@ -1,142 +0,0 @@
package org.schabi.newpipe.ui.components.items.stream
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
import org.schabi.newpipe.R
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.download.DownloadDialog
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.player.helper.PlayerHolder
import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.SparseItemUtil
import org.schabi.newpipe.util.external_communication.ShareUtils
import org.schabi.newpipe.viewmodels.StreamViewModel
@Composable
fun StreamMenu(
stream: StreamInfoItem,
expanded: Boolean,
onDismissRequest: () -> Unit
) {
val context = LocalContext.current
val streamViewModel = viewModel<StreamViewModel>()
DropdownMenu(expanded = expanded, onDismissRequest = onDismissRequest) {
if (PlayerHolder.isPlayQueueReady) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.enqueue_stream)) },
onClick = {
onDismissRequest()
SparseItemUtil.fetchItemInfoIfSparse(context, stream) {
NavigationHelper.enqueueOnPlayer(context, it)
}
}
)
if (PlayerHolder.queuePosition < PlayerHolder.queueSize - 1) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.enqueue_next_stream)) },
onClick = {
onDismissRequest()
SparseItemUtil.fetchItemInfoIfSparse(context, stream) {
NavigationHelper.enqueueNextOnPlayer(context, it)
}
}
)
}
}
DropdownMenuItem(
text = { Text(text = stringResource(R.string.start_here_on_background)) },
onClick = {
onDismissRequest()
SparseItemUtil.fetchItemInfoIfSparse(context, stream) {
NavigationHelper.playOnBackgroundPlayer(context, it, true)
}
}
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.start_here_on_popup)) },
onClick = {
onDismissRequest()
SparseItemUtil.fetchItemInfoIfSparse(context, stream) {
NavigationHelper.playOnPopupPlayer(context, it, true)
}
}
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.download)) },
onClick = {
onDismissRequest()
SparseItemUtil.fetchStreamInfoAndSaveToDatabase(
context,
stream.serviceId,
stream.url
) { info ->
// TODO: Use an AlertDialog composable instead.
val downloadDialog = DownloadDialog(context, info)
val fragmentManager = context.findFragmentActivity().supportFragmentManager
downloadDialog.show(fragmentManager, "downloadDialog")
}
}
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.add_to_playlist)) },
onClick = {
onDismissRequest()
val list = listOf(StreamEntity(stream))
PlaylistDialog.createCorrespondingDialog(context, list) { dialog ->
val tag = if (dialog is PlaylistAppendDialog) "append" else "create"
dialog.show(
context.findFragmentActivity().supportFragmentManager,
"StreamDialogEntry@${tag}_playlist"
)
}
}
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.share)) },
onClick = {
onDismissRequest()
ShareUtils.shareText(context, stream.name, stream.url, stream.thumbnails)
}
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.open_in_browser)) },
onClick = {
onDismissRequest()
ShareUtils.openUrlInBrowser(context, stream.url)
}
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.mark_as_watched)) },
onClick = {
onDismissRequest()
streamViewModel.markAsWatched(stream)
}
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.show_channel_details)) },
onClick = {
onDismissRequest()
SparseItemUtil.fetchUploaderUrlIfSparse(
context,
stream.serviceId,
stream.url,
stream.uploaderUrl
) { url ->
val activity = context.findFragmentActivity()
NavigationHelper.openChannelFragment(activity, stream, url)
}
}
)
}
}

View File

@ -1,7 +1,7 @@
package org.schabi.newpipe.ui.components.menu
import android.content.Context
import android.net.Uri
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
@ -12,6 +12,7 @@ import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Headset
import androidx.compose.material.icons.filled.OpenInBrowser
import androidx.compose.material.icons.filled.Panorama
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.PictureInPicture
import androidx.compose.material.icons.filled.PlayArrow
@ -20,6 +21,8 @@ import androidx.compose.material.icons.filled.Share
import androidx.compose.ui.graphics.vector.ImageVector
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import org.schabi.newpipe.R
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry
import org.schabi.newpipe.database.stream.StreamStatisticsEntry
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.download.DownloadDialog
import org.schabi.newpipe.error.ErrorInfo
@ -35,7 +38,6 @@ import org.schabi.newpipe.player.playqueue.PlayQueue
import org.schabi.newpipe.player.playqueue.SinglePlayQueue
import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.SparseItemUtil
import org.schabi.newpipe.util.external_communication.KoreUtils
import org.schabi.newpipe.util.external_communication.ShareUtils
data class LongPressAction(
@ -60,6 +62,7 @@ data class LongPressAction(
ShowChannelDetails(R.string.show_channel_details, Icons.Default.Person),
MarkAsWatched(R.string.mark_as_watched, Icons.Default.Done),
Delete(R.string.delete, Icons.Default.Delete),
SetAsPlaylistThumbnail(R.string.set_as_playlist_thumbnail, Icons.Default.Panorama),
;
// TODO allow actions to return disposables
@ -104,9 +107,9 @@ data class LongPressAction(
}
@JvmStatic
fun buildActionList(
fun fromStreamInfoItem(
item: StreamInfoItem,
isKodiEnabled: Boolean,
/* TODO isKodiEnabled: Boolean, */
/* TODO wholeListQueue: (() -> PlayQueue)? */
): List<LongPressAction> {
return buildPlayerActionList { SinglePlayQueue(item) } +
@ -163,11 +166,57 @@ data class LongPressAction(
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
},
) + if (isKodiEnabled) listOf(
)
/* TODO handle kodi
+ if (isKodiEnabled) listOf(
Type.PlayWithKodi.buildAction { context ->
KoreUtils.playWithKore(context, Uri.parse(item.url))
},
) else listOf()
) else listOf()*/
}
@JvmStatic
fun fromStreamEntity(
item: StreamEntity,
): List<LongPressAction> {
// TODO decide if it's fine to just convert to StreamInfoItem here (it poses an
// unnecessary dependency on the extractor, when we want to just look at data; maybe
// using something like LongPressable would work)
return fromStreamInfoItem(item.toStreamInfoItem())
}
@JvmStatic
fun fromStreamStatisticsEntry(
item: StreamStatisticsEntry,
): List<LongPressAction> {
return fromStreamEntity(item.streamEntity) +
listOf(
Type.Delete.buildAction { context ->
HistoryRecordManager(context)
.deleteStreamHistoryAndState(item.streamId)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Toast.makeText(
context,
R.string.one_item_deleted,
Toast.LENGTH_SHORT
).show()
}
}
)
}
@JvmStatic
fun fromPlaylistStreamEntry(
item: PlaylistStreamEntry,
onDelete: Runnable,
onSetAsPlaylistThumbnail: Runnable,
): List<LongPressAction> {
return fromStreamEntity(item.streamEntity) +
listOf(
Type.Delete.buildAction { onDelete.run() },
Type.SetAsPlaylistThumbnail.buildAction { onSetAsPlaylistThumbnail.run() }
)
}
}
}

View File

@ -2,9 +2,11 @@
package org.schabi.newpipe.ui.components.menu
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
@ -73,6 +75,17 @@ import org.schabi.newpipe.util.Either
import org.schabi.newpipe.util.Localization
import java.time.OffsetDateTime
fun openLongPressMenuInActivity(
activity: Activity,
longPressable: LongPressable,
longPressActions: List<LongPressAction>,
) {
activity.addContentView(
getLongPressMenuView(activity, longPressable, longPressActions),
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
)
}
fun getLongPressMenuView(
context: Context,
longPressable: LongPressable,
@ -83,9 +96,8 @@ fun getLongPressMenuView(
AppTheme {
LongPressMenu(
longPressable = longPressable,
onDismissRequest = { (this.parent as ViewGroup).removeView(this) },
longPressActions = longPressActions,
onEditActions = {},
onDismissRequest = { (this.parent as ViewGroup).removeView(this) },
)
}
}
@ -95,9 +107,9 @@ fun getLongPressMenuView(
@Composable
fun LongPressMenu(
longPressable: LongPressable,
onDismissRequest: () -> Unit,
longPressActions: List<LongPressAction>,
onEditActions: () -> Unit,
onDismissRequest: () -> Unit,
onEditActions: () -> Unit = {}, // TODO handle this menu
sheetState: SheetState = rememberModalBottomSheetState(),
) {
ModalBottomSheet(

View File

@ -1,8 +1,11 @@
package org.schabi.newpipe.ui.components.menu
import androidx.compose.runtime.Stable
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
import org.schabi.newpipe.util.Either
import org.schabi.newpipe.util.image.ImageStrategy
import java.time.OffsetDateTime
@ -22,11 +25,20 @@ data class LongPressable(
data class Duration(val duration: Long) : Decoration
data object Live : Decoration
data class Playlist(val itemCount: Long) : Decoration
companion object {
internal fun from(streamType: StreamType, duration: Long) =
if (streamType == LIVE_STREAM || streamType == AUDIO_LIVE_STREAM) {
Live
} else {
duration.takeIf { it >= 0 }?.let { Duration(it) }
}
}
}
companion object {
@JvmStatic
fun from(item: StreamInfoItem) = LongPressable(
fun fromStreamInfoItem(item: StreamInfoItem) = LongPressable(
title = item.name,
url = item.url?.takeIf { it.isNotBlank() },
thumbnailUrl = ImageStrategy.choosePreferredImage(item.thumbnails),
@ -35,15 +47,20 @@ data class LongPressable(
viewCount = item.viewCount.takeIf { it >= 0 },
uploadDate = item.uploadDate?.let { Either.right(it.offsetDateTime()) }
?: item.textualUploadDate?.let { Either.left(it) },
decoration = if (item.streamType == StreamType.LIVE_STREAM ||
item.streamType == StreamType.AUDIO_LIVE_STREAM
) {
LongPressable.Decoration.Live
} else {
item.duration.takeIf { it >= 0 }?.let {
LongPressable.Decoration.Duration(it)
}
},
decoration = Decoration.from(item.streamType, item.duration),
)
@JvmStatic
fun fromStreamEntity(item: StreamEntity) = LongPressable(
title = item.title,
url = item.url.takeIf { it.isNotBlank() },
thumbnailUrl = item.thumbnailUrl,
uploader = item.uploader.takeIf { it.isNotBlank() },
uploaderUrl = item.uploaderUrl?.takeIf { it.isNotBlank() },
viewCount = item.viewCount?.takeIf { it >= 0 },
uploadDate = item.uploadDate?.let { Either.right(it) }
?: item.textualUploadDate?.let { Either.left(it) },
decoration = Decoration.from(item.streamType, item.duration),
)
}
}