diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java index 2d645e793..dd2994d29 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java @@ -63,6 +63,7 @@ public abstract class PlaylistStreamDAO implements BasicDAO> getPlaylistMetadata(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index fc4913114..4512e316f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -229,10 +229,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte ChannelFragment fragment = ChannelFragment.getInstance(serviceId, url, name); fragment.useAsFrontPage(true); return fragment; - } else if (setMainPage.equals(getString(R.string.bookmark_page_key))) { - final BookmarkFragment fragment = new BookmarkFragment(); - fragment.useAsFrontPage(true); - return fragment; } else { return new BlankFragment(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/local/BaseLocalListFragment.java index afc67aa68..7db54db6c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/BaseLocalListFragment.java @@ -1,8 +1,7 @@ package org.schabi.newpipe.fragments.local; -import android.content.Context; import android.os.Bundle; -import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; import android.support.v7.app.ActionBar; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -12,86 +11,44 @@ import android.view.MenuInflater; import android.view.View; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.list.ListViewContract; -import org.schabi.newpipe.util.StateSaver; - -import java.util.List; -import java.util.Queue; import static org.schabi.newpipe.util.AnimationUtils.animateView; +/** + * This fragment is design to be used with persistent data such as + * {@link org.schabi.newpipe.database.LocalItem}, and does not cache the data contained + * in the list adapter to avoid extra writes when the it exits or re-enters its lifecycle. + * + * This fragment destroys its adapter and views when {@link Fragment#onDestroyView()} is + * called and is memory efficient when in backstack. + * */ public abstract class BaseLocalListFragment extends BaseStateFragment - implements ListViewContract, StateSaver.WriteRead { + implements ListViewContract { /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ + protected View headerRootView; + protected View footerRootView; + protected LocalItemListAdapter itemListAdapter; protected RecyclerView itemsList; /*////////////////////////////////////////////////////////////////////////// - // LifeCycle + // Lifecycle - Creation //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onAttach(Context context) { - super.onAttach(context); - itemListAdapter = new LocalItemListAdapter(activity); - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); } - @Override - public void onDestroy() { - super.onDestroy(); - StateSaver.onDestroy(savedState); - } - /*////////////////////////////////////////////////////////////////////////// - // State Saving - //////////////////////////////////////////////////////////////////////////*/ - - protected StateSaver.SavedState savedState; - - @Override - public String generateSuffix() { - // Naive solution, but it's good for now (the items don't change) - return "." + itemListAdapter.getItemsList().size() + ".list"; - } - - @Override - public void writeTo(Queue objectsToSave) { - objectsToSave.add(itemListAdapter.getItemsList()); - } - - @Override - @SuppressWarnings("unchecked") - public void readFrom(@NonNull Queue savedObjects) throws Exception { - itemListAdapter.getItemsList().clear(); - itemListAdapter.getItemsList().addAll((List) savedObjects.poll()); - } - - @Override - public void onSaveInstanceState(Bundle bundle) { - super.onSaveInstanceState(bundle); - savedState = StateSaver.tryToSave(activity.isChangingConfigurations(), savedState, bundle, this); - } - - @Override - protected void onRestoreInstanceState(@NonNull Bundle bundle) { - super.onRestoreInstanceState(bundle); - savedState = StateSaver.tryToRestore(bundle, this); - } - - /*////////////////////////////////////////////////////////////////////////// - // Init + // Lifecycle - View //////////////////////////////////////////////////////////////////////////*/ protected View getListHeader() { @@ -113,8 +70,9 @@ public abstract class BaseLocalListFragment extends BaseStateFragment itemsList = rootView.findViewById(R.id.items_list); itemsList.setLayoutManager(getListLayoutManager()); - itemListAdapter.setFooter(getListFooter()); - itemListAdapter.setHeader(getListHeader()); + itemListAdapter = new LocalItemListAdapter(activity); + itemListAdapter.setHeader(headerRootView = getListHeader()); + itemListAdapter.setFooter(footerRootView = getListFooter()); itemsList.setAdapter(itemListAdapter); } @@ -125,12 +83,13 @@ public abstract class BaseLocalListFragment extends BaseStateFragment } /*////////////////////////////////////////////////////////////////////////// - // Menu + // Lifecycle - Menu //////////////////////////////////////////////////////////////////////////*/ @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]"); + if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + + "], inflater = [" + inflater + "]"); super.onCreateOptionsMenu(menu, inflater); ActionBar supportActionBar = activity.getSupportActionBar(); if (supportActionBar != null) { @@ -143,27 +102,48 @@ public abstract class BaseLocalListFragment extends BaseStateFragment } } + /*////////////////////////////////////////////////////////////////////////// + // Lifecycle - Destruction + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onDestroyView() { + super.onDestroyView(); + itemsList = null; + itemListAdapter = null; + } + /*////////////////////////////////////////////////////////////////////////// // Contract //////////////////////////////////////////////////////////////////////////*/ + @Override + public void startLoading(boolean forceLoad) { + super.startLoading(forceLoad); + resetFragment(); + } + @Override public void showLoading() { super.showLoading(); - // animateView(itemsList, false, 400); + animateView(itemsList, false, 200); + if (headerRootView != null) animateView(headerRootView, false, 200); } @Override public void hideLoading() { super.hideLoading(); - animateView(itemsList, true, 300); + animateView(itemsList, true, 200); + if (headerRootView != null) animateView(headerRootView, true, 200); } @Override public void showError(String message, boolean showRetryButton) { super.showError(message, showRetryButton); showListFooter(false); + animateView(itemsList, false, 200); + if (headerRootView != null) animateView(headerRootView, false, 200); } @Override @@ -181,4 +161,18 @@ public abstract class BaseLocalListFragment extends BaseStateFragment public void handleNextItems(N result) { isLoading.set(false); } + + /*////////////////////////////////////////////////////////////////////////// + // Error handling + //////////////////////////////////////////////////////////////////////////*/ + + protected void resetFragment() { + if (itemListAdapter != null) itemListAdapter.clearStreamItemList(); + } + + @Override + protected boolean onError(Throwable exception) { + resetFragment(); + return super.onError(exception); + } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/local/LocalPlaylistFragment.java index 6528b8923..7ec24337a 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/LocalPlaylistFragment.java @@ -11,6 +11,7 @@ import android.support.v7.app.AlertDialog; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.text.TextUtils; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -38,7 +39,6 @@ import java.util.concurrent.TimeUnit; import icepick.State; import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; import io.reactivex.subjects.PublishSubject; @@ -51,8 +51,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment debouncedSaveSignal; private Disposable debouncedSaver; @@ -81,13 +79,14 @@ public class LocalPlaylistFragment extends BaseLocalListFragment createRenameDialog()); + itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); itemTouchHelper.attachToRecyclerView(itemsList); @@ -192,9 +164,236 @@ public class LocalPlaylistFragment extends BaseLocalListFragment createRenameDialog()); } + /////////////////////////////////////////////////////////////////////////// + // Fragment Lifecycle - Loading + /////////////////////////////////////////////////////////////////////////// + + @Override + public void showLoading() { + super.showLoading(); + animateView(headerRootLayout, false, 200); + animateView(playlistControl, false, 200); + } + + @Override + public void hideLoading() { + super.hideLoading(); + animateView(headerRootLayout, true, 200); + animateView(playlistControl, true, 200); + } + + @Override + public void startLoading(boolean forceLoad) { + super.startLoading(forceLoad); + + if (debouncedSaver != null) debouncedSaver.dispose(); + debouncedSaver = getDebouncedSaver(); + + playlistManager.getPlaylistStreams(playlistId) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getPlaylistObserver()); + } + + /////////////////////////////////////////////////////////////////////////// + // Fragment Lifecycle - Destruction + /////////////////////////////////////////////////////////////////////////// + + @Override + public void onPause() { + super.onPause(); + itemsListState = itemsList.getLayoutManager().onSaveInstanceState(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (databaseSubscription != null) databaseSubscription.cancel(); + if (debouncedSaver != null) debouncedSaver.dispose(); + + databaseSubscription = null; + debouncedSaver = null; + itemTouchHelper = null; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (debouncedSaveSignal != null) debouncedSaveSignal.onComplete(); + + debouncedSaveSignal = null; + playlistManager = null; + } + + /////////////////////////////////////////////////////////////////////////// + // Playlist Stream Loader + /////////////////////////////////////////////////////////////////////////// + + private Subscriber> getPlaylistObserver() { + return new Subscriber>() { + @Override + public void onSubscribe(Subscription s) { + showLoading(); + + if (databaseSubscription != null) databaseSubscription.cancel(); + databaseSubscription = s; + databaseSubscription.request(1); + } + + @Override + public void onNext(List streams) { + // Do not allow saving while the result is being updated + if (debouncedSaver != null) debouncedSaver.dispose(); + handleResult(streams); + debouncedSaver = getDebouncedSaver(); + + if (databaseSubscription != null) databaseSubscription.request(1); + } + + @Override + public void onError(Throwable exception) { + LocalPlaylistFragment.this.onError(exception); + } + + @Override + public void onComplete() {} + }; + } + + @Override + public void handleResult(@NonNull List result) { + super.handleResult(result); + itemListAdapter.clearStreamItemList(); + + if (result.isEmpty()) { + showEmptyState(); + return; + } + + itemListAdapter.addItems(result); + if (itemsListState != null) { + itemsList.getLayoutManager().onRestoreInstanceState(itemsListState); + itemsListState = null; + } + setVideoCount(itemListAdapter.getItemsList().size()); + + headerPlayAllButton.setOnClickListener(view -> + NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); + headerPopupButton.setOnClickListener(view -> + NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); + headerBackgroundButton.setOnClickListener(view -> + NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); + + hideLoading(); + } + + /////////////////////////////////////////////////////////////////////////// + // Fragment Error Handling + /////////////////////////////////////////////////////////////////////////// + + @Override + protected void resetFragment() { + super.resetFragment(); + if (databaseSubscription != null) databaseSubscription.cancel(); + } + + @Override + protected boolean onError(Throwable exception) { + if (super.onError(exception)) return true; + + onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, + "none", "Local Playlist", R.string.general_error); + return true; + } + + /*////////////////////////////////////////////////////////////////////////// + // Playlist Metadata/Streams Manipulation + //////////////////////////////////////////////////////////////////////////*/ + + private void createRenameDialog() { + if (playlistId == null || name == null || getContext() == null) return; + + final View dialogView = View.inflate(getContext(), R.layout.dialog_playlist_name, null); + EditText nameEdit = dialogView.findViewById(R.id.playlist_name); + nameEdit.setText(name); + nameEdit.setSelection(nameEdit.getText().length()); + + final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext()) + .setTitle(R.string.rename_playlist) + .setView(dialogView) + .setCancelable(true) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.create, (dialogInterface, i) -> + changePlaylistName(nameEdit.getText().toString()) + ); + + dialogBuilder.show(); + } + + private void changePlaylistName(final String name) { + this.name = name; + setTitle(name); + + Log.e(TAG, "Updating playlist id=[" + playlistId + + "] with new name=[" + name + "] items"); + + playlistManager.renamePlaylist(playlistId, name) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(longs -> {/*Do nothing on success*/}, this::onError); + } + + private void changeThumbnailUrl(final String thumbnailUrl) { + final Toast successToast = Toast.makeText(getActivity(), + R.string.playlist_thumbnail_change_success, + Toast.LENGTH_SHORT); + + Log.e(TAG, "Updating playlist id=[" + playlistId + + "] with new thumbnail url=[" + thumbnailUrl + "]"); + + playlistManager.changePlaylistThumbnail(playlistId, thumbnailUrl) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ignore -> successToast.show(), this::onError); + } + + private void deleteItem(final PlaylistStreamEntry item) { + itemListAdapter.removeItem(item); + setVideoCount(itemListAdapter.getItemsList().size()); + saveDebounced(); + } + + private void saveDebounced() { + debouncedSaveSignal.onNext(System.currentTimeMillis()); + } + + private Disposable getDebouncedSaver() { + return debouncedSaveSignal + .debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ignored -> saveJoin()); + } + + private void saveJoin() { + final List items = itemListAdapter.getItemsList(); + List streamIds = new ArrayList<>(items.size()); + for (final LocalItem item : items) { + if (item instanceof PlaylistStreamEntry) { + streamIds.add(((PlaylistStreamEntry) item).streamId); + } + } + + Log.e(TAG, "Updating playlist id=[" + playlistId + + "] with [" + streamIds.size() + "] items"); + + playlistManager.updateJoin(playlistId, streamIds) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> {/*Do nothing on success*/}, this::onError); + } + + /*////////////////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////////////////*/ + protected void showStreamDialog(final PlaylistStreamEntry item) { final Context context = getContext(); final Activity activity = getActivity(); @@ -236,9 +435,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment> getPlaylistObserver() { - return new Subscriber>() { - @Override - public void onSubscribe(Subscription s) { - showLoading(); - - if (databaseSubscription != null) databaseSubscription.cancel(); - databaseSubscription = s; - databaseSubscription.request(1); - } - - @Override - public void onNext(List streams) { - handleResult(streams); - if (databaseSubscription != null) databaseSubscription.request(1); - } - - @Override - public void onError(Throwable exception) { - LocalPlaylistFragment.this.onError(exception); - } - - @Override - public void onComplete() { - } - }; - } - - @Override - public void handleResult(@NonNull List result) { - super.handleResult(result); - itemListAdapter.clearStreamItemList(); - - if (result.isEmpty()) { - showEmptyState(); - return; - } - - animateView(headerRootLayout, true, 100); - animateView(itemsList, true, 300); - - itemListAdapter.addItems(result); - if (itemsListState != null) { - itemsList.getLayoutManager().onRestoreInstanceState(itemsListState); - itemsListState = null; - } - setVideoCount(itemListAdapter.getItemsList().size()); - - playlistControl.setVisibility(View.VISIBLE); - - headerPlayAllButton.setOnClickListener(view -> - NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); - headerPopupButton.setOnClickListener(view -> - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); - headerBackgroundButton.setOnClickListener(view -> - NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); - hideLoading(); - } - - /////////////////////////////////////////////////////////////////////////// - // Fragment Error Handling - /////////////////////////////////////////////////////////////////////////// - - @Override - protected boolean onError(Throwable exception) { - resetFragment(); - if (super.onError(exception)) return true; - - onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, - "none", "Local Playlist", R.string.general_error); - return true; - } - - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - private void setInitialData(long playlistId, String name) { this.playlistId = playlistId; this.name = !TextUtils.isEmpty(name) ? name : ""; } - private void setFragmentTitle(final String title) { - if (activity != null && activity.getSupportActionBar() != null) { - activity.getSupportActionBar().setTitle(title); - } - if (headerTitleView != null) { - headerTitleView.setText(title); - } - } - private void setVideoCount(final long count) { if (activity != null && headerStreamCount != null) { headerStreamCount.setText(Localization.localizeStreamCount(activity, count)); @@ -419,71 +503,5 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { - name = nameEdit.getText().toString(); - setFragmentTitle(name); - - final LocalPlaylistManager playlistManager = - new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext())); - final Toast successToast = Toast.makeText(getActivity(), - R.string.playlist_rename_success, - Toast.LENGTH_SHORT); - - playlistManager.renamePlaylist(playlistId, name) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(longs -> successToast.show()); - }); - - dialogBuilder.show(); - } - - private void changeThumbnailUrl(final String thumbnailUrl) { - final Toast successToast = Toast.makeText(getActivity(), - R.string.playlist_thumbnail_change_success, - Toast.LENGTH_SHORT); - - playlistManager.changePlaylistThumbnail(playlistId, thumbnailUrl) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ignore -> successToast.show()); - } - - private void saveDebounced() { - debouncedSaveSignal.onNext(System.currentTimeMillis()); - } - - private Disposable getDebouncedSaver() { - return debouncedSaveSignal - .debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ignored -> saveJoin()); - } - - private void saveJoin() { - final List items = itemListAdapter.getItemsList(); - List streamIds = new ArrayList<>(items.size()); - for (final LocalItem item : items) { - if (item instanceof PlaylistStreamEntry) { - streamIds.add(((PlaylistStreamEntry) item).streamId); - } - } - - playlistManager.updateJoin(playlistId, streamIds) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(); - } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/fragments/local/PlaylistAppendDialog.java index 7145d91d7..b01dcabeb 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/PlaylistAppendDialog.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/PlaylistAppendDialog.java @@ -1,7 +1,6 @@ package org.schabi.newpipe.fragments.local; import android.annotation.SuppressLint; -import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -25,6 +24,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import javax.annotation.Nonnull; + import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -63,26 +64,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog { } /*////////////////////////////////////////////////////////////////////////// - // LifeCycle - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public void onAttach(Context context) { - super.onAttach(context); - playlistAdapter = new LocalItemListAdapter(getActivity()); - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (playlistReactor != null) playlistReactor.dispose(); - playlistReactor = null; - playlistRecyclerView = null; - playlistAdapter = null; - } - - /*////////////////////////////////////////////////////////////////////////// - // Views + // LifeCycle - Creation //////////////////////////////////////////////////////////////////////////*/ @Override @@ -95,52 +77,44 @@ public final class PlaylistAppendDialog extends PlaylistDialog { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - final View newPlaylistButton = view.findViewById(R.id.newPlaylist); - playlistRecyclerView = view.findViewById(R.id.playlist_list); - playlistRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - playlistRecyclerView.setAdapter(playlistAdapter); - final LocalPlaylistManager playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext())); - newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog()); - + playlistAdapter = new LocalItemListAdapter(getActivity()); playlistAdapter.setSelectedListener(new OnLocalItemGesture() { @Override public void selected(LocalItem selectedItem) { if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null) return; - - final long playlistId = ((PlaylistMetadataEntry) selectedItem).uid; - @SuppressLint("ShowToast") - final Toast successToast = Toast.makeText(getContext(), R.string.playlist_add_stream_success, - Toast.LENGTH_SHORT); - - playlistManager.appendToPlaylist(playlistId, getStreams()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnDispose(successToast::show) - .subscribe(ignored -> {}); - - getDialog().dismiss(); + onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem, + getStreams()); } }); + playlistRecyclerView = view.findViewById(R.id.playlist_list); + playlistRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + playlistRecyclerView.setAdapter(playlistAdapter); + + final View newPlaylistButton = view.findViewById(R.id.newPlaylist); + newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog()); + playlistReactor = playlistManager.getPlaylists() .observeOn(AndroidSchedulers.mainThread()) - .subscribe(metadataEntries -> { - if (metadataEntries.isEmpty()) { - openCreatePlaylistDialog(); - return; - } + .subscribe(this::onPlaylistsReceived); + } - if (playlistAdapter != null) { - playlistAdapter.clearStreamItemList(); - playlistAdapter.addItems(metadataEntries); - } - if (playlistRecyclerView != null) { - playlistRecyclerView.setVisibility(View.VISIBLE); - } - }); + /*////////////////////////////////////////////////////////////////////////// + // LifeCycle - Destruction + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (playlistReactor != null) playlistReactor.dispose(); + + playlistReactor = null; + playlistRecyclerView = null; + playlistAdapter = null; } /*////////////////////////////////////////////////////////////////////////// @@ -153,4 +127,33 @@ public final class PlaylistAppendDialog extends PlaylistDialog { PlaylistCreationDialog.newInstance(getStreams()).show(getFragmentManager(), TAG); getDialog().dismiss(); } + + private void onPlaylistsReceived(@NonNull final List playlists) { + if (playlists.isEmpty()) { + openCreatePlaylistDialog(); + return; + } + + if (playlistAdapter != null && playlistRecyclerView != null) { + playlistAdapter.clearStreamItemList(); + playlistAdapter.addItems(playlists); + playlistRecyclerView.setVisibility(View.VISIBLE); + } + } + + private void onPlaylistSelected(@NonNull LocalPlaylistManager manager, + @NonNull PlaylistMetadataEntry playlist, + @Nonnull List streams) { + if (getStreams() == null) return; + + @SuppressLint("ShowToast") + final Toast successToast = Toast.makeText(getContext(), + R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); + + manager.appendToPlaylist(playlist.uid, streams) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ignored -> successToast.show()); + + getDialog().dismiss(); + } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BookmarkFragment.java index 613e77cfe..393e128f7 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BookmarkFragment.java @@ -1,17 +1,14 @@ package org.schabi.newpipe.fragments.local.bookmark; import android.app.AlertDialog; -import android.content.Context; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Toast; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; @@ -19,29 +16,25 @@ import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; -import org.schabi.newpipe.fragments.BaseStateFragment; -import org.schabi.newpipe.fragments.local.LocalItemListAdapter; +import org.schabi.newpipe.fragments.local.BaseLocalListFragment; import org.schabi.newpipe.fragments.local.LocalPlaylistManager; import org.schabi.newpipe.fragments.local.OnLocalItemGesture; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.NavigationHelper; -import java.util.Collections; import java.util.List; import icepick.State; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; -import static org.schabi.newpipe.util.AnimationUtils.animateView; +public final class BookmarkFragment + extends BaseLocalListFragment, Void> { -public class BookmarkFragment extends BaseStateFragment> { private View watchHistoryButton; private View mostWatchedButton; - private LocalItemListAdapter itemListAdapter; - private RecyclerView itemsList; - @State protected Parcelable itemsListState; @@ -50,23 +43,14 @@ public class BookmarkFragment extends BaseStateFragment { - final Toast deleteSuccessful = Toast.makeText(getContext(), - R.string.playlist_delete_success, Toast.LENGTH_SHORT); - disposables.add(localPlaylistManager.deletePlaylist(item.uid) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ignored -> deleteSuccessful.show())); - }) - .setNegativeButton(R.string.cancel, null) - .show(); - } - - private void resetFragment() { - if (disposables != null) disposables.clear(); - if (itemListAdapter != null) itemListAdapter.clearStreamItemList(); - } - /////////////////////////////////////////////////////////////////////////// - // Subscriptions Loader + // Fragment LifeCycle - Loading /////////////////////////////////////////////////////////////////////////// @Override public void startLoading(boolean forceLoad) { super.startLoading(forceLoad); - resetFragment(); - localPlaylistManager.getPlaylists() .observeOn(AndroidSchedulers.mainThread()) .subscribe(getSubscriptionSubscriber()); } + /////////////////////////////////////////////////////////////////////////// + // Fragment LifeCycle - Destruction + /////////////////////////////////////////////////////////////////////////// + + @Override + public void onPause() { + super.onPause(); + itemsListState = itemsList.getLayoutManager().onSaveInstanceState(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (disposables != null) disposables.clear(); + if (databaseSubscription != null) databaseSubscription.cancel(); + + databaseSubscription = null; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (disposables != null) disposables.dispose(); + + disposables = null; + localPlaylistManager = null; + itemsListState = null; + } + + /////////////////////////////////////////////////////////////////////////// + // Subscriptions Loader + /////////////////////////////////////////////////////////////////////////// + private Subscriber> getSubscriptionSubscriber() { return new Subscriber>() { @Override @@ -238,55 +207,58 @@ public class BookmarkFragment extends BaseStateFragment infoItemsOf(List playlists) { - Collections.sort(playlists, (o1, o2) -> o1.name.compareToIgnoreCase(o2.name)); - return playlists; - } - - /*////////////////////////////////////////////////////////////////////////// - // Contract - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public void showLoading() { - super.showLoading(); - animateView(itemsList, false, 100); - } - - @Override - public void hideLoading() { - super.hideLoading(); - animateView(itemsList, true, 200); - } - - @Override - public void showEmptyState() { - super.showEmptyState(); - } - /////////////////////////////////////////////////////////////////////////// // Fragment Error Handling /////////////////////////////////////////////////////////////////////////// @Override protected boolean onError(Throwable exception) { - resetFragment(); if (super.onError(exception)) return true; onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "Bookmark", R.string.general_error); return true; } + + @Override + protected void resetFragment() { + super.resetFragment(); + if (disposables != null) disposables.clear(); + } + + /////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////// + + private void showDeleteDialog(final PlaylistMetadataEntry item) { + new AlertDialog.Builder(activity) + .setTitle(item.name) + .setMessage(R.string.delete_playlist_prompt) + .setCancelable(true) + .setPositiveButton(R.string.delete, (dialog, i) -> + disposables.add(deletePlaylist(item.uid)) + ) + .setNegativeButton(R.string.cancel, null) + .show(); + } + + private Disposable deletePlaylist(final long playlistId) { + return localPlaylistManager.deletePlaylist(playlistId) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ignored -> {/*Do nothing on success*/}, + throwable -> Log.e(TAG, "Playlist deletion failed, id=[" + + playlistId + "]") + ); + } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/MostPlayedFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/MostPlayedFragment.java index ed0d903a8..cba9e9c64 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/MostPlayedFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/MostPlayedFragment.java @@ -6,7 +6,7 @@ import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import java.util.Collections; import java.util.List; -public class MostPlayedFragment extends StatisticsPlaylistFragment { +public final class MostPlayedFragment extends StatisticsPlaylistFragment { @Override protected String getName() { return getString(R.string.title_most_played); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/StatisticsPlaylistFragment.java index 78038c3de..4843034eb 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/StatisticsPlaylistFragment.java @@ -32,13 +32,9 @@ import java.util.List; import icepick.State; import io.reactivex.android.schedulers.AndroidSchedulers; -import static org.schabi.newpipe.util.AnimationUtils.animateView; - public abstract class StatisticsPlaylistFragment extends BaseLocalListFragment, Void> { - private View headerRootLayout; - private View playlistControl; private View headerPlayAllButton; private View headerPopupButton; private View headerBackgroundButton; @@ -59,13 +55,13 @@ public abstract class StatisticsPlaylistFragment protected abstract List processResult(final List results); /////////////////////////////////////////////////////////////////////////// - // Fragment LifeCycle + // Fragment LifeCycle - Creation /////////////////////////////////////////////////////////////////////////// @Override - public void onAttach(Context context) { - super.onAttach(context); - recordManager = new HistoryRecordManager(context); + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + recordManager = new HistoryRecordManager(getContext()); } @Override @@ -75,46 +71,23 @@ public abstract class StatisticsPlaylistFragment return inflater.inflate(R.layout.fragment_playlist, container, false); } - @Override - public void onPause() { - super.onPause(); - itemsListState = itemsList.getLayoutManager().onSaveInstanceState(); - } - - @Override - public void onDestroyView() { - if (databaseSubscription != null) databaseSubscription.cancel(); - super.onDestroyView(); - } - - @Override - public void onDestroy() { - if (databaseSubscription != null) databaseSubscription.cancel(); - databaseSubscription = null; - recordManager = null; - - super.onDestroy(); - } - /////////////////////////////////////////////////////////////////////////// - // Fragment Views + // Fragment LifeCycle - Views /////////////////////////////////////////////////////////////////////////// @Override protected void initViews(View rootView, Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); - setFragmentTitle(getName()); + setTitle(getName()); } @Override protected View getListHeader() { - headerRootLayout = activity.getLayoutInflater().inflate(R.layout.playlist_control, + final View headerRootLayout = activity.getLayoutInflater().inflate(R.layout.playlist_control, itemsList, false); - playlistControl = headerRootLayout.findViewById(R.id.playlist_control); headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button); headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button); headerBackgroundButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_bg_button); - return headerRootLayout; } @@ -139,9 +112,124 @@ public abstract class StatisticsPlaylistFragment } } }); - } + /////////////////////////////////////////////////////////////////////////// + // Fragment LifeCycle - Loading + /////////////////////////////////////////////////////////////////////////// + + @Override + public void startLoading(boolean forceLoad) { + super.startLoading(forceLoad); + recordManager.getStreamStatistics() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getHistoryObserver()); + } + + /////////////////////////////////////////////////////////////////////////// + // Fragment LifeCycle - Destruction + /////////////////////////////////////////////////////////////////////////// + + @Override + public void onPause() { + super.onPause(); + itemsListState = itemsList.getLayoutManager().onSaveInstanceState(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (databaseSubscription != null) databaseSubscription.cancel(); + databaseSubscription = null; + } + + @Override + public void onDestroy() { + super.onDestroy(); + recordManager = null; + itemsListState = null; + } + + /////////////////////////////////////////////////////////////////////////// + // Statistics Loader + /////////////////////////////////////////////////////////////////////////// + + private Subscriber> getHistoryObserver() { + return new Subscriber>() { + @Override + public void onSubscribe(Subscription s) { + showLoading(); + + if (databaseSubscription != null) databaseSubscription.cancel(); + databaseSubscription = s; + databaseSubscription.request(1); + } + + @Override + public void onNext(List streams) { + handleResult(streams); + if (databaseSubscription != null) databaseSubscription.request(1); + } + + @Override + public void onError(Throwable exception) { + StatisticsPlaylistFragment.this.onError(exception); + } + + @Override + public void onComplete() { + } + }; + } + + @Override + public void handleResult(@NonNull List result) { + super.handleResult(result); + itemListAdapter.clearStreamItemList(); + + if (result.isEmpty()) { + showEmptyState(); + return; + } + + itemListAdapter.addItems(processResult(result)); + if (itemsListState != null) { + itemsList.getLayoutManager().onRestoreInstanceState(itemsListState); + itemsListState = null; + } + + headerPlayAllButton.setOnClickListener(view -> + NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); + headerPopupButton.setOnClickListener(view -> + NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); + headerBackgroundButton.setOnClickListener(view -> + NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); + + hideLoading(); + } + /////////////////////////////////////////////////////////////////////////// + // Fragment Error Handling + /////////////////////////////////////////////////////////////////////////// + + @Override + protected void resetFragment() { + super.resetFragment(); + if (databaseSubscription != null) databaseSubscription.cancel(); + } + + @Override + protected boolean onError(Throwable exception) { + if (super.onError(exception)) return true; + + onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, + "none", "History Statistics", R.string.general_error); + return true; + } + + /*////////////////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////////////////*/ + private void showStreamDialog(final StreamStatisticsEntry item) { final Context context = getContext(); final Activity activity = getActivity(); @@ -182,113 +270,6 @@ public abstract class StatisticsPlaylistFragment new InfoItemDialog(getActivity(), infoItem, commands, actions).show(); } - private void resetFragment() { - if (databaseSubscription != null) databaseSubscription.cancel(); - if (itemListAdapter != null) itemListAdapter.clearStreamItemList(); - } - - /////////////////////////////////////////////////////////////////////////// - // Loader - /////////////////////////////////////////////////////////////////////////// - - @Override - public void showLoading() { - super.showLoading(); - animateView(headerRootLayout, false, 200); - animateView(itemsList, false, 100); - } - - @Override - public void startLoading(boolean forceLoad) { - super.startLoading(forceLoad); - resetFragment(); - - recordManager.getStreamStatistics() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getHistoryObserver()); - } - - private Subscriber> getHistoryObserver() { - return new Subscriber>() { - @Override - public void onSubscribe(Subscription s) { - showLoading(); - - if (databaseSubscription != null) databaseSubscription.cancel(); - databaseSubscription = s; - databaseSubscription.request(1); - } - - @Override - public void onNext(List streams) { - handleResult(streams); - if (databaseSubscription != null) databaseSubscription.request(1); - } - - @Override - public void onError(Throwable exception) { - StatisticsPlaylistFragment.this.onError(exception); - } - - @Override - public void onComplete() { - } - }; - } - - @Override - public void handleResult(@NonNull List result) { - super.handleResult(result); - itemListAdapter.clearStreamItemList(); - - if (result.isEmpty()) { - showEmptyState(); - return; - } - - animateView(headerRootLayout, true, 100); - animateView(itemsList, true, 300); - - itemListAdapter.addItems(processResult(result)); - if (itemsListState != null) { - itemsList.getLayoutManager().onRestoreInstanceState(itemsListState); - itemsListState = null; - } - - playlistControl.setVisibility(View.VISIBLE); - headerPlayAllButton.setOnClickListener(view -> - NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); - headerPopupButton.setOnClickListener(view -> - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); - headerBackgroundButton.setOnClickListener(view -> - NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); - hideLoading(); - } - - /////////////////////////////////////////////////////////////////////////// - // Fragment Error Handling - /////////////////////////////////////////////////////////////////////////// - - @Override - protected boolean onError(Throwable exception) { - resetFragment(); - if (super.onError(exception)) return true; - - onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, - "none", "History Statistics", R.string.general_error); - return true; - } - - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - - protected void setFragmentTitle(final String title) { - if (activity.getSupportActionBar() != null) { - activity.getSupportActionBar().setTitle(title); - } - } - private PlayQueue getPlayQueue() { return getPlayQueue(0); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/WatchHistoryFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/WatchHistoryFragment.java index 853029ae6..84126ad4b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/WatchHistoryFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/WatchHistoryFragment.java @@ -6,7 +6,7 @@ import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import java.util.Collections; import java.util.List; -public class WatchHistoryFragment extends StatisticsPlaylistFragment { +public final class WatchHistoryFragment extends StatisticsPlaylistFragment { @Override protected String getName() { return getString(R.string.title_watch_history); diff --git a/app/src/main/java/org/schabi/newpipe/history/HistoryFragment.java b/app/src/main/java/org/schabi/newpipe/history/HistoryFragment.java index 3fa8076f3..14bd93c57 100644 --- a/app/src/main/java/org/schabi/newpipe/history/HistoryFragment.java +++ b/app/src/main/java/org/schabi/newpipe/history/HistoryFragment.java @@ -14,6 +14,7 @@ import android.support.design.widget.Snackbar; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -173,10 +174,19 @@ public abstract class HistoryFragment extends BaseFragment final Disposable deletion = delete(itemsToDelete) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(); + .subscribe( + ignored -> Log.d(TAG, "Clear history deleted [" + + itemsToDelete.size() + "] items."), + error -> Log.e(TAG, "Clear history delete step failed", error) + ); + final Disposable cleanUp = historyRecordManager.removeOrphanedRecords() .observeOn(AndroidSchedulers.mainThread()) - .subscribe(); + .subscribe( + ignored -> Log.d(TAG, "Clear history deleted orphaned stream records"), + error -> Log.e(TAG, "Clear history remove orphaned records failed", error) + ); + disposables.addAll(deletion, cleanUp); makeSnackbar(R.string.history_cleared); diff --git a/app/src/main/java/org/schabi/newpipe/history/SearchHistoryFragment.java b/app/src/main/java/org/schabi/newpipe/history/SearchHistoryFragment.java index e40a79368..25098fac8 100644 --- a/app/src/main/java/org/schabi/newpipe/history/SearchHistoryFragment.java +++ b/app/src/main/java/org/schabi/newpipe/history/SearchHistoryFragment.java @@ -7,6 +7,7 @@ import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.v7.app.AlertDialog; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -83,17 +84,25 @@ public class SearchHistoryFragment extends HistoryFragment { .setCancelable(true) .setNeutralButton(R.string.cancel, null) .setPositiveButton(R.string.delete_one, (dialog, i) -> { - final Single onDelete = historyRecordManager + final Disposable onDelete = historyRecordManager .deleteSearches(Collections.singleton(item)) - .observeOn(AndroidSchedulers.mainThread()); - disposables.add(onDelete.subscribe()); + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + ignored -> {/*successful*/}, + error -> Log.e(TAG, "Search history Delete One failed:", error) + ); + disposables.add(onDelete); makeSnackbar(R.string.item_deleted); }) .setNegativeButton(R.string.delete_all, (dialog, i) -> { - final Single onDeleteAll = historyRecordManager + final Disposable onDeleteAll = historyRecordManager .deleteSearchHistory(item.getSearch()) - .observeOn(AndroidSchedulers.mainThread()); - disposables.add(onDeleteAll.subscribe()); + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + ignored -> {/*successful*/}, + error -> Log.e(TAG, "Search history Delete All failed:", error) + ); + disposables.add(onDeleteAll); makeSnackbar(R.string.item_deleted); }) .show(); @@ -112,8 +121,7 @@ public class SearchHistoryFragment extends HistoryFragment { protected class SearchHistoryAdapter extends HistoryEntryAdapter { - - public SearchHistoryAdapter(Context context) { + SearchHistoryAdapter(Context context) { super(context); } diff --git a/app/src/main/java/org/schabi/newpipe/history/WatchedHistoryFragment.java b/app/src/main/java/org/schabi/newpipe/history/WatchedHistoryFragment.java index 7913c9a28..0fabc594a 100644 --- a/app/src/main/java/org/schabi/newpipe/history/WatchedHistoryFragment.java +++ b/app/src/main/java/org/schabi/newpipe/history/WatchedHistoryFragment.java @@ -8,6 +8,7 @@ import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.v7.app.AlertDialog; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -29,6 +30,7 @@ import java.util.List; import io.reactivex.Flowable; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; public class WatchedHistoryFragment extends HistoryFragment { @@ -85,17 +87,25 @@ public class WatchedHistoryFragment extends HistoryFragment .setCancelable(true) .setNeutralButton(R.string.cancel, null) .setPositiveButton(R.string.delete_one, (dialog, i) -> { - final Single onDelete = historyRecordManager + final Disposable onDelete = historyRecordManager .deleteStreamHistory(Collections.singleton(item)) - .observeOn(AndroidSchedulers.mainThread()); - disposables.add(onDelete.subscribe()); + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + ignored -> {/*successful*/}, + error -> Log.e(TAG, "Watch history Delete One failed:", error) + ); + disposables.add(onDelete); makeSnackbar(R.string.item_deleted); }) .setNegativeButton(R.string.delete_all, (dialog, i) -> { - final Single onDeleteAll = historyRecordManager + final Disposable onDeleteAll = historyRecordManager .deleteStreamHistory(item.streamId) - .observeOn(AndroidSchedulers.mainThread()); - disposables.add(onDeleteAll.subscribe()); + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + ignored -> {/*successful*/}, + error -> Log.e(TAG, "Watch history Delete All failed:", error) + ); + disposables.add(onDeleteAll); makeSnackbar(R.string.item_deleted); }) .show(); diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 369b15509..7558f1375 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -676,7 +676,11 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen } // TODO: update exoplayer to 2.6.x in order to register view count on repeated streams - databaseUpdateReactor.add(recordManager.onViewed(currentInfo).subscribe()); + databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete() + .subscribe( + ignored -> {/* successful */}, + error -> Log.e(TAG, "Player onViewed() failure: ", error) + )); initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url); } @@ -844,7 +848,10 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen final Disposable stateSaver = recordManager.saveStreamState(info, progress) .observeOn(AndroidSchedulers.mainThread()) .onErrorComplete() - .subscribe(); + .subscribe( + ignored -> {/* successful */}, + error -> Log.e(TAG, "savePlaybackState() failure: ", error) + ); databaseUpdateReactor.add(stateSaver); } diff --git a/app/src/main/res/layout/local_playlist_header.xml b/app/src/main/res/layout/local_playlist_header.xml index 4ba611681..4d686c515 100644 --- a/app/src/main/res/layout/local_playlist_header.xml +++ b/app/src/main/res/layout/local_playlist_header.xml @@ -11,9 +11,12 @@ android:id="@+id/playlist_title_view" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="8dp" - android:layout_marginRight="8dp" - android:layout_marginTop="6dp" + android:paddingTop="6dp" + android:paddingBottom="6dp" + android:paddingLeft="12dp" + android:paddingRight="12dp" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" android:layout_toLeftOf="@id/playlist_stream_count" android:layout_toStartOf="@id/playlist_stream_count" android:background="?attr/selectableItemBackground" @@ -26,7 +29,7 @@ android:singleLine="true" android:textAppearance="?android:attr/textAppearanceLarge" android:textSize="@dimen/playlist_detail_title_text_size" - tools:text="Mix musics #23 title Lorem ipsum dolor sit amet, consectetur..." /> + tools:text="Mix musics #23 title Lorem ipsum dolor sit amet, consectetur..."/> subscription_page_key kiosk_page channel_page - bookmark_page @string/blank_page_key @string/kiosk_page_key @string/feed_page_key @string/subscription_page_key @string/channel_page_key - @string/bookmark_page_key main_page_selected_service main_page_selected_channel_name diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1e6d3e641..577d85ce5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -390,6 +390,5 @@ Playlist successfully created Added to playlist Playlist thumbnail changed - Playlist renamed - Playlist deleted + Failed to delete playlist