From 985872f2c11219c7c1f0a350d7fa2ebf3b234290 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 12 Feb 2025 01:03:34 +0100 Subject: [PATCH] Replace InfoItemDialog with LongPressMenu --- .../fragments/list/BaseListFragment.java | 14 +- .../list/playlist/PlaylistFragment.java | 38 +---- .../schabi/newpipe/local/feed/FeedFragment.kt | 20 +-- .../history/StatisticsPlaylistFragment.java | 59 ++------ .../local/playlist/LocalPlaylistFragment.java | 48 ++---- .../newpipe/ui/components/items/ItemList.kt | 27 +--- .../components/items/stream/StreamListItem.kt | 31 +++- .../ui/components/items/stream/StreamMenu.kt | 142 ------------------ .../ui/components/menu/LongPressAction.kt | 61 +++++++- .../ui/components/menu/LongPressMenu.kt | 20 ++- .../ui/components/menu/LongPressable.kt | 37 +++-- 11 files changed, 166 insertions(+), 331 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamMenu.kt 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 8a117a47a..7a1ee3095 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 @@ -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 extends BaseStateFragment } 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) + ); } /*////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 269d85c7a..c3755ab65 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -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 NavigationHelper.playOnBackgroundPlayer( - context, getPlayQueueStartingAt(infoItem), true)) - .create() - .show(); - } catch (final IllegalArgumentException e) { - InfoItemDialog.Builder.reportErrorDuringInitialization(e, item); - } } @Override diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 560850294..b20fa330b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -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() { 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() { 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 diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 43b7f1c0d..d31e6bf57 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -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, 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 diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index cb38d9bae..5ae7ce158 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -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 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) { diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt b/app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt index ba45c503d..520587589 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt @@ -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(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) } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamListItem.kt b/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamListItem.kt index 84fff3e74..f4fbfb716 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamListItem.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamListItem.kt @@ -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) } } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamMenu.kt b/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamMenu.kt deleted file mode 100644 index 099a93005..000000000 --- a/app/src/main/java/org/schabi/newpipe/ui/components/items/stream/StreamMenu.kt +++ /dev/null @@ -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() - - 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) - } - } - ) - } -} 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 b77880075..cc025ee10 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 @@ -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 { 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 { + // 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 { + 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 { + return fromStreamEntity(item.streamEntity) + + listOf( + Type.Delete.buildAction { onDelete.run() }, + Type.SetAsPlaylistThumbnail.buildAction { onSetAsPlaylistThumbnail.run() } + ) } } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressMenu.kt b/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressMenu.kt index 66c7a2629..c57e271ea 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressMenu.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressMenu.kt @@ -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, +) { + 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, - onEditActions: () -> Unit, + onDismissRequest: () -> Unit, + onEditActions: () -> Unit = {}, // TODO handle this menu sheetState: SheetState = rememberModalBottomSheetState(), ) { ModalBottomSheet( 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 ada15e6cd..06e5146fd 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 @@ -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), ) } }