NewPipe/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment....

433 lines
16 KiB
Java
Raw Normal View History

package org.schabi.newpipe.local.history;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.view.LayoutInflater;
2018-12-29 19:01:45 +01:00
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
2018-04-29 13:15:52 +02:00
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewbinding.ViewBinding;
import com.google.android.material.snackbar.Snackbar;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.databinding.PlaylistControlBinding;
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.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemDialog;
2018-12-29 19:01:45 +01:00
import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.player.helper.PlayerHolder;
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.external_communication.KoreUtils;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.StreamDialogEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import icepick.State;
2020-10-31 21:55:45 +01:00
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
2018-04-28 15:37:27 +02:00
public class StatisticsPlaylistFragment
extends BaseLocalListFragment<List<StreamStatisticsEntry>, Void> {
private final CompositeDisposable disposables = new CompositeDisposable();
@State
Parcelable itemsListState;
private StatisticSortMode sortMode = StatisticSortMode.LAST_PLAYED;
private StatisticPlaylistControlBinding headerBinding;
private PlaylistControlBinding playlistControlBinding;
/* Used for independent events */
private Subscription databaseSubscription;
private HistoryRecordManager recordManager;
private List<StreamStatisticsEntry> processResult(final List<StreamStatisticsEntry> results) {
final Comparator<StreamStatisticsEntry> comparator;
2018-04-28 15:37:27 +02:00
switch (sortMode) {
case LAST_PLAYED:
comparator = Comparator.comparing(StreamStatisticsEntry::getLatestAccessDate);
break;
2018-04-28 15:37:27 +02:00
case MOST_PLAYED:
comparator = Comparator.comparingLong(StreamStatisticsEntry::getWatchCount);
break;
default:
return null;
2018-04-28 15:37:27 +02:00
}
Collections.sort(results, comparator.reversed());
return results;
2018-04-28 15:37:27 +02:00
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Creation
///////////////////////////////////////////////////////////////////////////
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
recordManager = new HistoryRecordManager(getContext());
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_playlist, container, false);
}
2018-06-10 21:57:35 +02:00
@Override
2021-10-16 21:33:45 +02:00
public void onResume() {
super.onResume();
if (activity != null) {
2018-06-10 21:57:35 +02:00
setTitle(activity.getString(R.string.title_activity_history));
}
}
2018-12-29 19:01:45 +01:00
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu,
@NonNull final MenuInflater inflater) {
2018-12-29 19:01:45 +01:00
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_history, menu);
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Views
///////////////////////////////////////////////////////////////////////////
@Override
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
if (!useAsFrontPage) {
2018-06-11 11:01:18 +02:00
setTitle(getString(R.string.title_last_played));
}
}
@Override
protected ViewBinding getListHeader() {
headerBinding = StatisticPlaylistControlBinding.inflate(activity.getLayoutInflater(),
itemsList, false);
playlistControlBinding = headerBinding.playlistControl;
return headerBinding;
}
@Override
protected void initListeners() {
super.initListeners();
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
@Override
public void selected(final LocalItem selectedItem) {
if (selectedItem instanceof StreamStatisticsEntry) {
final StreamEntity item =
((StreamStatisticsEntry) selectedItem).getStreamEntity();
NavigationHelper.openVideoDetailFragment(requireContext(), getFM(),
item.getServiceId(), item.getUrl(), item.getTitle(), null, false);
}
}
@Override
public void held(final LocalItem selectedItem) {
if (selectedItem instanceof StreamStatisticsEntry) {
showStreamDialog((StreamStatisticsEntry) selectedItem);
}
}
});
}
2018-12-29 19:01:45 +01:00
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if (item.getItemId() == R.id.action_history_clear) {
HistorySettingsFragment
.openDeleteWatchHistoryDialog(requireContext(), recordManager, disposables);
} else {
return super.onOptionsItemSelected(item);
2018-12-29 19:01:45 +01:00
}
return true;
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Loading
///////////////////////////////////////////////////////////////////////////
@Override
public void startLoading(final boolean forceLoad) {
super.startLoading(forceLoad);
recordManager.getStreamStatistics()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getHistoryObserver());
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Destruction
///////////////////////////////////////////////////////////////////////////
@Override
public void onPause() {
super.onPause();
itemsListState = Objects.requireNonNull(itemsList.getLayoutManager()).onSaveInstanceState();
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (itemListAdapter != null) {
itemListAdapter.unsetSelectedListener();
}
if (playlistControlBinding != null) {
playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(null);
playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(null);
playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(null);
headerBinding = null;
playlistControlBinding = null;
}
if (databaseSubscription != null) {
databaseSubscription.cancel();
}
databaseSubscription = null;
}
@Override
public void onDestroy() {
super.onDestroy();
recordManager = null;
itemsListState = null;
}
///////////////////////////////////////////////////////////////////////////
// Statistics Loader
///////////////////////////////////////////////////////////////////////////
private Subscriber<List<StreamStatisticsEntry>> getHistoryObserver() {
return new Subscriber<List<StreamStatisticsEntry>>() {
@Override
public void onSubscribe(final Subscription s) {
showLoading();
if (databaseSubscription != null) {
databaseSubscription.cancel();
}
databaseSubscription = s;
databaseSubscription.request(1);
}
@Override
public void onNext(final List<StreamStatisticsEntry> streams) {
handleResult(streams);
if (databaseSubscription != null) {
databaseSubscription.request(1);
}
}
@Override
public void onError(final Throwable exception) {
showError(
new ErrorInfo(exception, UserAction.SOMETHING_ELSE, "History Statistics"));
}
@Override
public void onComplete() {
}
};
}
@Override
public void handleResult(@NonNull final List<StreamStatisticsEntry> result) {
super.handleResult(result);
if (itemListAdapter == null) {
return;
}
playlistControlBinding.getRoot().setVisibility(View.VISIBLE);
2018-04-28 15:37:27 +02:00
itemListAdapter.clearStreamItemList();
if (result.isEmpty()) {
showEmptyState();
return;
}
itemListAdapter.addItems(processResult(result));
if (itemsListState != null && itemsList.getLayoutManager() != null) {
itemsList.getLayoutManager().onRestoreInstanceState(itemsListState);
itemsListState = null;
}
playlistControlBinding.playlistCtrlPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
playlistControlBinding.playlistCtrlPlayPopupButton.setOnClickListener(view ->
2019-04-13 09:31:32 +02:00
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
playlistControlBinding.playlistCtrlPlayBgButton.setOnClickListener(view ->
2019-04-13 09:31:32 +02:00
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false));
headerBinding.sortButton.setOnClickListener(view -> toggleSortMode());
hideLoading();
}
///////////////////////////////////////////////////////////////////////////
// Fragment Error Handling
///////////////////////////////////////////////////////////////////////////
@Override
protected void resetFragment() {
super.resetFragment();
if (databaseSubscription != null) {
databaseSubscription.cancel();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
2018-04-29 13:15:52 +02:00
private void toggleSortMode() {
if (sortMode == StatisticSortMode.LAST_PLAYED) {
2018-04-28 15:37:27 +02:00
sortMode = StatisticSortMode.MOST_PLAYED;
setTitle(getString(R.string.title_most_played));
2021-03-27 15:45:49 +01:00
headerBinding.sortButtonIcon.setImageResource(R.drawable.ic_history);
headerBinding.sortButtonText.setText(R.string.title_last_played);
2018-04-28 15:37:27 +02:00
} else {
sortMode = StatisticSortMode.LAST_PLAYED;
setTitle(getString(R.string.title_last_played));
headerBinding.sortButtonIcon.setImageResource(
2021-03-27 15:45:49 +01:00
R.drawable.ic_filter_list);
headerBinding.sortButtonText.setText(R.string.title_most_played);
2018-04-28 15:37:27 +02:00
}
startLoading(true);
}
private PlayQueue getPlayQueueStartingAt(final StreamStatisticsEntry infoItem) {
return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0));
}
private void showStreamDialog(final StreamStatisticsEntry item) {
final Context context = getContext();
final Activity activity = getActivity();
if (context == null || context.getResources() == null || activity == null) {
return;
}
final StreamInfoItem infoItem = item.toStreamInfoItem();
final ArrayList<StreamDialogEntry> entries = new ArrayList<>();
Add play next to long press menu & refactor enqueue methods (#6872) * added mvp play next button in long press menu; new intent handling, new long press dialog entry, new dialog functions, new strings * changed line length for checkstyle pass * cleaned comments, moved strings * Update app/src/main/res/values/strings.xml to make long press entry more descriptive Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> * Update app/src/main/res/values/strings.xml Co-authored-by: Stypox <stypox@pm.me> * replace redundant nextOnVideoPlayer methods Co-authored-by: Stypox <stypox@pm.me> * add enqueueNextOnPlayer and enqueueOnPlayer without selectOnAppend and RESUME_PLAYBACK/ deprecate enqueueNextOn*Player and enqueueOn*Player methods add getPlayerIntent, getPlayerEnqueueIntent and getPlayerEnqueueNextIntent without selectOnAppend and RESUME_PLAYBACK/ deprecate those with add section comments * removed deprecated methods removed redundant methods * removed deprecated methods removed redundant methods * replaced APPEND_ONLY, removed SELECT_ON_APPEND / replaced remaining enqueueOn*Player methods * now works with playlists * renamed dialog entry * checking for >1 items in the queue using the PlayerHolder * making enqueue*OnPlayer safe to call when no video is playing (defaulting to audio) * corrected strings * improve getQueueSize in PlayerHolder * long press to enqueue only if queue isnt empty * add Whitespace Co-authored-by: Stypox <stypox@pm.me> * clarify comments / add spaces * PlayerType as parameter of the enqueueOnPlayer method add Helper method * using the helper function everywhere (except for the background and popup long-press actions (also on playlists, history, ...)), so basically nowhere / passing checkstyle * assimilated the enqueue*OnPlayer methods * removed redundant comment, variable * simplify code line Co-authored-by: Stypox <stypox@pm.me> * move if * replace workaround for isPlayerOpen() Co-authored-by: Stypox <stypox@pm.me> * replaced workarounds (getType), corrected static access with getInstance * remove unused imports * changed method call to original, new method doesnt exist yet. * Use getter method instead of property access syntax. * improve conditional for play next entry Co-authored-by: Stypox <stypox@pm.me> * show play next btn in feed fragment Co-authored-by: Stypox <stypox@pm.me> * add play next to local playlist and statistics fragment Co-authored-by: Stypox <stypox@pm.me> * formating Co-authored-by: Stypox <stypox@pm.me> * correcting logic Co-authored-by: Stypox <stypox@pm.me> * remove 2 year old unused string, formating Co-authored-by: Stypox <stypox@pm.me> * correct enqueue (next) conditionals, default to background if no player is open. Dont generally default to background play. * remove player open checks from button long press enqueue actions * improve log msg * Rename next to enqueue_next * Refactor kotlin Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Co-authored-by: Stypox <stypox@pm.me>
2021-09-18 11:22:49 +02:00
if (PlayerHolder.getInstance().isPlayerOpen()) {
entries.add(StreamDialogEntry.enqueue);
Add play next to long press menu & refactor enqueue methods (#6872) * added mvp play next button in long press menu; new intent handling, new long press dialog entry, new dialog functions, new strings * changed line length for checkstyle pass * cleaned comments, moved strings * Update app/src/main/res/values/strings.xml to make long press entry more descriptive Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> * Update app/src/main/res/values/strings.xml Co-authored-by: Stypox <stypox@pm.me> * replace redundant nextOnVideoPlayer methods Co-authored-by: Stypox <stypox@pm.me> * add enqueueNextOnPlayer and enqueueOnPlayer without selectOnAppend and RESUME_PLAYBACK/ deprecate enqueueNextOn*Player and enqueueOn*Player methods add getPlayerIntent, getPlayerEnqueueIntent and getPlayerEnqueueNextIntent without selectOnAppend and RESUME_PLAYBACK/ deprecate those with add section comments * removed deprecated methods removed redundant methods * removed deprecated methods removed redundant methods * replaced APPEND_ONLY, removed SELECT_ON_APPEND / replaced remaining enqueueOn*Player methods * now works with playlists * renamed dialog entry * checking for >1 items in the queue using the PlayerHolder * making enqueue*OnPlayer safe to call when no video is playing (defaulting to audio) * corrected strings * improve getQueueSize in PlayerHolder * long press to enqueue only if queue isnt empty * add Whitespace Co-authored-by: Stypox <stypox@pm.me> * clarify comments / add spaces * PlayerType as parameter of the enqueueOnPlayer method add Helper method * using the helper function everywhere (except for the background and popup long-press actions (also on playlists, history, ...)), so basically nowhere / passing checkstyle * assimilated the enqueue*OnPlayer methods * removed redundant comment, variable * simplify code line Co-authored-by: Stypox <stypox@pm.me> * move if * replace workaround for isPlayerOpen() Co-authored-by: Stypox <stypox@pm.me> * replaced workarounds (getType), corrected static access with getInstance * remove unused imports * changed method call to original, new method doesnt exist yet. * Use getter method instead of property access syntax. * improve conditional for play next entry Co-authored-by: Stypox <stypox@pm.me> * show play next btn in feed fragment Co-authored-by: Stypox <stypox@pm.me> * add play next to local playlist and statistics fragment Co-authored-by: Stypox <stypox@pm.me> * formating Co-authored-by: Stypox <stypox@pm.me> * correcting logic Co-authored-by: Stypox <stypox@pm.me> * remove 2 year old unused string, formating Co-authored-by: Stypox <stypox@pm.me> * correct enqueue (next) conditionals, default to background if no player is open. Dont generally default to background play. * remove player open checks from button long press enqueue actions * improve log msg * Rename next to enqueue_next * Refactor kotlin Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Co-authored-by: Stypox <stypox@pm.me>
2021-09-18 11:22:49 +02:00
if (PlayerHolder.getInstance().getQueueSize() > 1) {
entries.add(StreamDialogEntry.enqueue_next);
}
}
Add play next to long press menu & refactor enqueue methods (#6872) * added mvp play next button in long press menu; new intent handling, new long press dialog entry, new dialog functions, new strings * changed line length for checkstyle pass * cleaned comments, moved strings * Update app/src/main/res/values/strings.xml to make long press entry more descriptive Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> * Update app/src/main/res/values/strings.xml Co-authored-by: Stypox <stypox@pm.me> * replace redundant nextOnVideoPlayer methods Co-authored-by: Stypox <stypox@pm.me> * add enqueueNextOnPlayer and enqueueOnPlayer without selectOnAppend and RESUME_PLAYBACK/ deprecate enqueueNextOn*Player and enqueueOn*Player methods add getPlayerIntent, getPlayerEnqueueIntent and getPlayerEnqueueNextIntent without selectOnAppend and RESUME_PLAYBACK/ deprecate those with add section comments * removed deprecated methods removed redundant methods * removed deprecated methods removed redundant methods * replaced APPEND_ONLY, removed SELECT_ON_APPEND / replaced remaining enqueueOn*Player methods * now works with playlists * renamed dialog entry * checking for >1 items in the queue using the PlayerHolder * making enqueue*OnPlayer safe to call when no video is playing (defaulting to audio) * corrected strings * improve getQueueSize in PlayerHolder * long press to enqueue only if queue isnt empty * add Whitespace Co-authored-by: Stypox <stypox@pm.me> * clarify comments / add spaces * PlayerType as parameter of the enqueueOnPlayer method add Helper method * using the helper function everywhere (except for the background and popup long-press actions (also on playlists, history, ...)), so basically nowhere / passing checkstyle * assimilated the enqueue*OnPlayer methods * removed redundant comment, variable * simplify code line Co-authored-by: Stypox <stypox@pm.me> * move if * replace workaround for isPlayerOpen() Co-authored-by: Stypox <stypox@pm.me> * replaced workarounds (getType), corrected static access with getInstance * remove unused imports * changed method call to original, new method doesnt exist yet. * Use getter method instead of property access syntax. * improve conditional for play next entry Co-authored-by: Stypox <stypox@pm.me> * show play next btn in feed fragment Co-authored-by: Stypox <stypox@pm.me> * add play next to local playlist and statistics fragment Co-authored-by: Stypox <stypox@pm.me> * formating Co-authored-by: Stypox <stypox@pm.me> * correcting logic Co-authored-by: Stypox <stypox@pm.me> * remove 2 year old unused string, formating Co-authored-by: Stypox <stypox@pm.me> * correct enqueue (next) conditionals, default to background if no player is open. Dont generally default to background play. * remove player open checks from button long press enqueue actions * improve log msg * Rename next to enqueue_next * Refactor kotlin Co-authored-by: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Co-authored-by: Stypox <stypox@pm.me>
2021-09-18 11:22:49 +02:00
if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) {
entries.addAll(Arrays.asList(
StreamDialogEntry.start_here_on_background,
StreamDialogEntry.delete,
StreamDialogEntry.append_playlist,
StreamDialogEntry.share
));
} else {
entries.addAll(Arrays.asList(
StreamDialogEntry.start_here_on_background,
StreamDialogEntry.start_here_on_popup,
StreamDialogEntry.delete,
StreamDialogEntry.append_playlist,
StreamDialogEntry.share
));
}
entries.add(StreamDialogEntry.open_in_browser);
if (KoreUtils.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
2020-12-30 23:40:21 +01:00
entries.add(StreamDialogEntry.play_with_kodi);
}
entries.add(StreamDialogEntry.show_channel_details);
StreamDialogEntry.setEnabledEntries(entries);
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) ->
NavigationHelper
.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true));
StreamDialogEntry.delete.setCustomAction((fragment, infoItemDuplicate) ->
deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0)));
new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context),
(dialog, which) -> StreamDialogEntry.clickOn(which, this, infoItem)).show();
}
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())
2018-04-24 22:02:23 +02:00
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
() -> {
if (getView() != null) {
2018-04-29 13:15:52 +02:00
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")));
2018-04-28 15:37:27 +02:00
2018-04-29 13:15:52 +02:00
disposables.add(onDelete);
}
}
private PlayQueue getPlayQueue() {
return getPlayQueue(0);
}
private PlayQueue getPlayQueue(final int index) {
if (itemListAdapter == null) {
return new SinglePlayQueue(Collections.emptyList(), 0);
}
final List<LocalItem> infoItems = itemListAdapter.getItemsList();
2020-08-16 10:24:58 +02:00
final List<StreamInfoItem> streamInfoItems = new ArrayList<>(infoItems.size());
for (final LocalItem item : infoItems) {
if (item instanceof StreamStatisticsEntry) {
streamInfoItems.add(((StreamStatisticsEntry) item).toStreamInfoItem());
}
}
return new SinglePlayQueue(streamInfoItems, index);
}
private enum StatisticSortMode {
LAST_PLAYED,
MOST_PLAYED,
}
}