diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index def1774b7..686e102f1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -112,6 +112,7 @@ import org.schabi.newpipe.util.StreamTypeUtil; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.PlayButtonHelper; import java.util.ArrayList; import java.util.Iterator; @@ -535,9 +536,11 @@ public final class VideoDetailFragment })); binding.detailControlsBackground.setOnLongClickListener(makeOnLongClickListener(info -> - openBackgroundPlayer(true))); + openBackgroundPlayer(true) + )); binding.detailControlsPopup.setOnLongClickListener(makeOnLongClickListener(info -> - openPopupPlayer(true))); + openPopupPlayer(true) + )); binding.detailControlsDownload.setOnLongClickListener(makeOnLongClickListener(info -> NavigationHelper.openDownloads(activity))); @@ -620,8 +623,7 @@ public final class VideoDetailFragment final View.OnTouchListener controlsTouchListener = (view, motionEvent) -> { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN - && PreferenceManager.getDefaultSharedPreferences(activity) - .getBoolean(getString(R.string.show_hold_to_append_key), true)) { + && PlayButtonHelper.shouldShowHoldToAppendTip(activity)) { animate(binding.touchAppendDetail, true, 250, AnimationType.ALPHA, 0, () -> animate(binding.touchAppendDetail, false, 1500, AnimationType.ALPHA, 1000)); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelTabFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelTabFragment.java index 6b2dd20bf..27315a991 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelTabFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelTabFragment.java @@ -17,12 +17,12 @@ import org.schabi.newpipe.extractor.channel.tabs.ChannelTabInfo; import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; -import org.schabi.newpipe.player.PlayerType; +import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder; import org.schabi.newpipe.player.playqueue.ChannelTabPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.ChannelTabHelper; import org.schabi.newpipe.util.ExtractorHelper; -import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.PlayButtonHelper; import java.util.List; import java.util.function.Supplier; @@ -31,7 +31,8 @@ import java.util.stream.Collectors; import icepick.State; import io.reactivex.rxjava3.core.Single; -public class ChannelTabFragment extends BaseListInfoFragment { +public class ChannelTabFragment extends BaseListInfoFragment + implements PlaylistControlViewHolder { @State protected ListLinkHandler tabHandler; @@ -39,7 +40,6 @@ public class ChannelTabFragment extends BaseListInfoFragment NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); - playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener( - view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); - playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener( - view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), - false)); - - playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> { - NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.POPUP); - return true; - }); - - playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> { - NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.AUDIO); - return true; - }); + PlayButtonHelper.initPlaylistControlClickListener( + activity, playlistControlBinding, this); } } - private PlayQueue getPlayQueue() { + public PlayQueue getPlayQueue() { final List streamItems = infoListAdapter.getItemsList().stream() .filter(StreamInfoItem.class::isInstance) .map(StreamInfoItem.class::cast) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistControlViewHolder.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistControlViewHolder.java new file mode 100644 index 000000000..2a785146c --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistControlViewHolder.java @@ -0,0 +1,13 @@ +package org.schabi.newpipe.fragments.list.playlist; + +import org.schabi.newpipe.player.playqueue.PlayQueue; + +/** + * Interface for {@code R.layout.playlist_control} view holders + * to give access to the play queue. + */ +public interface PlaylistControlViewHolder { + + PlayQueue getPlayQueue(); + +} 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 8dd77bed6..2b7cf9446 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 @@ -43,7 +43,6 @@ 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.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.util.ExtractorHelper; @@ -51,6 +50,7 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.PlayButtonHelper; import java.util.ArrayList; import java.util.List; @@ -64,7 +64,8 @@ import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; -public class PlaylistFragment extends BaseListInfoFragment { +public class PlaylistFragment extends BaseListInfoFragment + implements PlaylistControlViewHolder { private static final String PICASSO_PLAYLIST_TAG = "PICASSO_PLAYLIST_TAG"; @@ -332,25 +333,10 @@ public class PlaylistFragment extends BaseListInfoFragment - NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); - playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(view -> - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); - playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(view -> - NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); - - playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> { - NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.POPUP); - return true; - }); - - playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> { - NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.AUDIO); - return true; - }); + PlayButtonHelper.initPlaylistControlClickListener(activity, playlistControlBinding, this); } - private PlayQueue getPlayQueue() { + public PlayQueue getPlayQueue() { return getPlayQueue(0); } 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 a20a80ae9..1fea7e155 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 @@ -28,14 +28,16 @@ import org.schabi.newpipe.databinding.StatisticPlaylistControlBinding; 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.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; -import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry; +import org.schabi.newpipe.util.PlayButtonHelper; import java.util.ArrayList; import java.util.Collections; @@ -49,7 +51,8 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; public class StatisticsPlaylistFragment - extends BaseLocalListFragment, Void> { + extends BaseLocalListFragment, Void> + implements PlaylistControlViewHolder { private final CompositeDisposable disposables = new CompositeDisposable(); @State Parcelable itemsListState; @@ -195,14 +198,9 @@ public class StatisticsPlaylistFragment if (itemListAdapter != null) { itemListAdapter.unsetSelectedListener(); } - if (playlistControlBinding != null) { - playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(null); - playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(null); - playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(null); - headerBinding = null; - playlistControlBinding = null; - } + headerBinding = null; + playlistControlBinding = null; if (databaseSubscription != null) { databaseSubscription.cancel(); @@ -276,12 +274,8 @@ public class StatisticsPlaylistFragment itemsListState = null; } - playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(view -> - NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); - playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(view -> - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); - playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(view -> - NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); + PlayButtonHelper.initPlaylistControlClickListener(activity, playlistControlBinding, this); + headerBinding.sortButton.setOnClickListener(view -> toggleSortMode()); hideLoading(); @@ -374,7 +368,7 @@ public class StatisticsPlaylistFragment } } - private PlayQueue getPlayQueue() { + public PlayQueue getPlayQueue() { return getPlayQueue(0); } 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 2a639a69f..0d8f81334 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 @@ -22,7 +22,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; import androidx.viewbinding.ViewBinding; @@ -42,17 +41,18 @@ import org.schabi.newpipe.databinding.PlaylistControlBinding; 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.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.PlayButtonHelper; import java.util.ArrayList; import java.util.Collections; @@ -69,7 +69,8 @@ import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.subjects.PublishSubject; -public class LocalPlaylistFragment extends BaseLocalListFragment, Void> { +public class LocalPlaylistFragment extends BaseLocalListFragment, Void> + implements PlaylistControlViewHolder { // Save the list 10 seconds after the last change occurred private static final long SAVE_DEBOUNCE_MILLIS = 10000; private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12; @@ -265,14 +266,10 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { - NavigationHelper.playOnMainPlayer(activity, getPlayQueue()); - showHoldToAppendTipIfNeeded(); - }); - playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(view -> { - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false); - showHoldToAppendTipIfNeeded(); - }); - playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(view -> { - NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false); - showHoldToAppendTipIfNeeded(); - }); - playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> { - NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.POPUP); - return true; - }); - - playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> { - NavigationHelper.enqueueOnPlayer(activity, getPlayQueue(), PlayerType.AUDIO); - return true; - }); + PlayButtonHelper.initPlaylistControlClickListener(activity, playlistControlBinding, this); hideLoading(); } - private void showHoldToAppendTipIfNeeded() { - if (PreferenceManager.getDefaultSharedPreferences(activity) - .getBoolean(getString(R.string.show_hold_to_append_key), true)) { - Toast.makeText(activity, R.string.hold_to_append, Toast.LENGTH_SHORT).show(); - } - } - /////////////////////////////////////////////////////////////////////////// // Fragment Error Handling /////////////////////////////////////////////////////////////////////////// @@ -853,7 +823,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment prefsKeys = PreferenceManager.getDefaultSharedPreferences(context) - .getAll().keySet(); - for (final String key: prefsKeys) { - // ACRA stores some info in the prefs during app initialization - // which happens before this method is called. Therefore ignore ACRA-related keys. - if (!key.toLowerCase().startsWith("acra")) { - isFirstRun = false; - break; - } - } - if (isFirstRun == null) { - isFirstRun = true; - } + // check if the last used preference version is set + // to determine whether this is the first app run + final int lastUsedPrefVersion = PreferenceManager.getDefaultSharedPreferences(context) + .getInt(context.getString(R.string.last_used_preferences_version), -1); + final boolean isFirstRun = lastUsedPrefVersion == -1; // first run migrations, then setDefaultValues, since the latter requires the correct types SettingMigrations.runMigrationsIfNeeded(context, isFirstRun); diff --git a/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.java b/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.java new file mode 100644 index 000000000..a0707c656 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/PlayButtonHelper.java @@ -0,0 +1,90 @@ +package org.schabi.newpipe.util; + +import android.content.Context; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.PreferenceManager; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.PlaylistControlBinding; +import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder; +import org.schabi.newpipe.player.PlayerType; + +/** + * Utility class for play buttons and their respective click listeners. + */ +public final class PlayButtonHelper { + + private PlayButtonHelper() { + // utility class + } + + /** + *

Initialize {@link android.view.View.OnClickListener OnClickListener} + * and {@link android.view.View.OnLongClickListener OnLongClickListener} for playlist control + * buttons defined in {@code R.layout.playlist_control}.

+ * + * @param activity The activity to use for the {@link android.widget.Toast Toast}. + * @param playlistControlBinding The binding of the + * {@link R.layout.playlist_control playlist control layout}. + * @param fragment The fragment to get the play queue from. + */ + public static void initPlaylistControlClickListener( + @NonNull final AppCompatActivity activity, + @NonNull final PlaylistControlBinding playlistControlBinding, + @NonNull final PlaylistControlViewHolder fragment) { + // click listener + playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(view -> { + NavigationHelper.playOnMainPlayer(activity, fragment.getPlayQueue()); + showHoldToAppendToastIfNeeded(activity); + }); + playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(view -> { + NavigationHelper.playOnPopupPlayer(activity, fragment.getPlayQueue(), false); + showHoldToAppendToastIfNeeded(activity); + }); + playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(view -> { + NavigationHelper.playOnBackgroundPlayer(activity, fragment.getPlayQueue(), false); + showHoldToAppendToastIfNeeded(activity); + }); + + // long click listener + playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> { + NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.POPUP); + return true; + }); + playlistControlBinding.playlistCtrlPlayBgButton.setOnLongClickListener(view -> { + NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.AUDIO); + return true; + }); + } + + /** + *

Show the "hold to append" toast if the corresponding preference is enabled.

+ * + * @param context The context to show the toast. + */ + private static void showHoldToAppendToastIfNeeded(@NonNull final Context context) { + if (shouldShowHoldToAppendTip(context)) { + Toast.makeText(context, R.string.hold_to_append, Toast.LENGTH_SHORT).show(); + } + + } + + /** + *

Check if the "hold to append" toast should be shown.

+ * + *

+ * The tip is shown if the corresponding preference is enabled. + * This is the default behaviour. + *

+ * + * @param context The context to get the preference. + * @return {@code true} if the tip should be shown, {@code false} otherwise. + */ + public static boolean shouldShowHoldToAppendTip(@NonNull final Context context) { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.show_hold_to_append_key), true); + } +}