Reuse DebounceSaver
This commit is contained in:
parent
bd1aae8d66
commit
bb5390d63a
5 changed files with 142 additions and 103 deletions
|
@ -17,15 +17,15 @@ public interface PlaylistLocalItem extends LocalItem {
|
||||||
final List<PlaylistMetadataEntry> localPlaylists,
|
final List<PlaylistMetadataEntry> localPlaylists,
|
||||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||||
|
|
||||||
// Merge localPlaylists and remotePlaylists by displayIndex.
|
// Merge localPlaylists and remotePlaylists by display index.
|
||||||
// If two items have the same displayIndex, sort them in CASE_INSENSITIVE_ORDER.
|
// If two items have the same display index, sort them in CASE_INSENSITIVE_ORDER.
|
||||||
// This algorithm is similar to the merge operation in merge sort.
|
// This algorithm is similar to the merge operation in merge sort.
|
||||||
|
|
||||||
final List<PlaylistLocalItem> result = new ArrayList<>(
|
final List<PlaylistLocalItem> result = new ArrayList<>(
|
||||||
localPlaylists.size() + remotePlaylists.size());
|
localPlaylists.size() + remotePlaylists.size());
|
||||||
final List<PlaylistLocalItem> itemsWithSameIndex = new ArrayList<>();
|
final List<PlaylistLocalItem> itemsWithSameIndex = new ArrayList<>();
|
||||||
|
|
||||||
// The data from database may not be in the displayIndex order
|
// The data from database may not be in the display index order
|
||||||
Collections.sort(localPlaylists,
|
Collections.sort(localPlaylists,
|
||||||
Comparator.comparingLong(PlaylistMetadataEntry::getDisplayIndex));
|
Comparator.comparingLong(PlaylistMetadataEntry::getDisplayIndex));
|
||||||
Collections.sort(remotePlaylists,
|
Collections.sort(remotePlaylists,
|
||||||
|
@ -58,7 +58,7 @@ public interface PlaylistLocalItem extends LocalItem {
|
||||||
final List<PlaylistLocalItem> itemsWithSameIndex) {
|
final List<PlaylistLocalItem> itemsWithSameIndex) {
|
||||||
if (!itemsWithSameIndex.isEmpty()
|
if (!itemsWithSameIndex.isEmpty()
|
||||||
&& itemsWithSameIndex.get(0).getDisplayIndex() != item.getDisplayIndex()) {
|
&& itemsWithSameIndex.get(0).getDisplayIndex() != item.getDisplayIndex()) {
|
||||||
// The new item has a different displayIndex, add previous items with same
|
// The new item has a different display index, add previous items with same
|
||||||
// index to the result.
|
// index to the result.
|
||||||
addItemsWithSameIndex(result, itemsWithSameIndex);
|
addItemsWithSameIndex(result, itemsWithSameIndex);
|
||||||
itemsWithSameIndex.clear();
|
itemsWithSameIndex.clear();
|
||||||
|
|
|
@ -35,6 +35,8 @@ import org.schabi.newpipe.local.holder.LocalBookmarkPlaylistItemHolder;
|
||||||
import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
|
import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||||
|
import org.schabi.newpipe.util.DebounceSavable;
|
||||||
|
import org.schabi.newpipe.util.DebounceSaver;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.OnClickGesture;
|
import org.schabi.newpipe.util.OnClickGesture;
|
||||||
|
|
||||||
|
@ -42,7 +44,6 @@ import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
|
@ -50,12 +51,10 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.rxjava3.core.Flowable;
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
|
||||||
|
|
||||||
public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
|
public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void>
|
||||||
|
implements DebounceSavable {
|
||||||
|
|
||||||
// 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;
|
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
|
||||||
@State
|
@State
|
||||||
protected Parcelable itemsListState;
|
protected Parcelable itemsListState;
|
||||||
|
@ -66,12 +65,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
private RemotePlaylistManager remotePlaylistManager;
|
private RemotePlaylistManager remotePlaylistManager;
|
||||||
private ItemTouchHelper itemTouchHelper;
|
private ItemTouchHelper itemTouchHelper;
|
||||||
|
|
||||||
private PublishSubject<Long> debouncedSaveSignal;
|
/* Have the bookmarked playlists been fully loaded from db */
|
||||||
|
|
||||||
/* Has the playlist been fully loaded from db */
|
|
||||||
private AtomicBoolean isLoadingComplete;
|
private AtomicBoolean isLoadingComplete;
|
||||||
/* Has the playlist been modified (e.g. items reordered or deleted) */
|
|
||||||
private AtomicBoolean isModified;
|
private DebounceSaver debounceSaver;
|
||||||
|
|
||||||
// Map from (uid, local/remote item) to the saved display index in the database.
|
// Map from (uid, local/remote item) to the saved display index in the database.
|
||||||
private Map<Pair<Long, LocalItem.LocalItemType>, Long> displayIndexInDatabase;
|
private Map<Pair<Long, LocalItem.LocalItemType>, Long> displayIndexInDatabase;
|
||||||
|
@ -91,9 +88,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
remotePlaylistManager = new RemotePlaylistManager(database);
|
remotePlaylistManager = new RemotePlaylistManager(database);
|
||||||
disposables = new CompositeDisposable();
|
disposables = new CompositeDisposable();
|
||||||
|
|
||||||
debouncedSaveSignal = PublishSubject.create();
|
|
||||||
isLoadingComplete = new AtomicBoolean();
|
isLoadingComplete = new AtomicBoolean();
|
||||||
isModified = new AtomicBoolean();
|
debounceSaver = new DebounceSaver(10000, this);
|
||||||
|
|
||||||
displayIndexInDatabase = new HashMap<>();
|
displayIndexInDatabase = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
@ -183,9 +179,11 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
public void startLoading(final boolean forceLoad) {
|
public void startLoading(final boolean forceLoad) {
|
||||||
super.startLoading(forceLoad);
|
super.startLoading(forceLoad);
|
||||||
|
|
||||||
disposables.add(getDebouncedSaver());
|
if (debounceSaver != null) {
|
||||||
|
disposables.add(debounceSaver.getDebouncedSaver());
|
||||||
|
debounceSaver.setIsModified(false);
|
||||||
|
}
|
||||||
isLoadingComplete.set(false);
|
isLoadingComplete.set(false);
|
||||||
isModified.set(false);
|
|
||||||
|
|
||||||
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
|
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
|
||||||
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
|
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
|
||||||
|
@ -225,21 +223,20 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (debouncedSaveSignal != null) {
|
if (debounceSaver != null) {
|
||||||
debouncedSaveSignal.onComplete();
|
debounceSaver.getDebouncedSaveSignal().onComplete();
|
||||||
}
|
}
|
||||||
if (disposables != null) {
|
if (disposables != null) {
|
||||||
disposables.dispose();
|
disposables.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
debouncedSaveSignal = null;
|
debounceSaver = null;
|
||||||
disposables = null;
|
disposables = null;
|
||||||
localPlaylistManager = null;
|
localPlaylistManager = null;
|
||||||
remotePlaylistManager = null;
|
remotePlaylistManager = null;
|
||||||
itemsListState = null;
|
itemsListState = null;
|
||||||
|
|
||||||
isLoadingComplete = null;
|
isLoadingComplete = null;
|
||||||
isModified = null;
|
|
||||||
displayIndexInDatabase = null;
|
displayIndexInDatabase = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,7 +260,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNext(final List<PlaylistLocalItem> subscriptions) {
|
public void onNext(final List<PlaylistLocalItem> subscriptions) {
|
||||||
if (isModified == null || !isModified.get()) {
|
if (debounceSaver == null || !debounceSaver.getIsModified()) {
|
||||||
checkDisplayIndexModified(subscriptions);
|
checkDisplayIndexModified(subscriptions);
|
||||||
handleResult(subscriptions);
|
handleResult(subscriptions);
|
||||||
isLoadingComplete.set(true);
|
isLoadingComplete.set(true);
|
||||||
|
@ -346,11 +343,11 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
}
|
}
|
||||||
itemListAdapter.removeItem(item);
|
itemListAdapter.removeItem(item);
|
||||||
|
|
||||||
saveChanges();
|
debounceSaver.saveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkDisplayIndexModified(@NonNull final List<PlaylistLocalItem> result) {
|
private void checkDisplayIndexModified(@NonNull final List<PlaylistLocalItem> result) {
|
||||||
if (isModified != null && isModified.get()) {
|
if (debounceSaver != null && debounceSaver.getIsModified()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,8 +355,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
|
|
||||||
// If the display index does not match actual index in the list, update the display index.
|
// If the display index does not match actual index in the list, update the display index.
|
||||||
// This may happen when a new list is created
|
// This may happen when a new list is created
|
||||||
// or on the first run after database update
|
// or on the first run after database migration
|
||||||
// or displayIndex is not continuous for some reason.
|
// or display index is not continuous for some reason
|
||||||
|
// or the user changes the display index.
|
||||||
boolean isDisplayIndexModified = false;
|
boolean isDisplayIndexModified = false;
|
||||||
for (int i = 0; i < result.size(); i++) {
|
for (int i = 0; i < result.size(); i++) {
|
||||||
final PlaylistLocalItem item = result.get(i);
|
final PlaylistLocalItem item = result.get(i);
|
||||||
|
@ -388,40 +386,19 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDisplayIndexModified) {
|
if (isDisplayIndexModified) {
|
||||||
saveChanges();
|
debounceSaver.saveChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveChanges() {
|
@Override
|
||||||
if (isModified == null || debouncedSaveSignal == null) {
|
public void saveImmediate() {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isModified.set(true);
|
|
||||||
debouncedSaveSignal.onNext(System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Disposable getDebouncedSaver() {
|
|
||||||
if (debouncedSaveSignal == null) {
|
|
||||||
return Disposable.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return debouncedSaveSignal
|
|
||||||
.debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(ignored -> saveImmediate(), throwable ->
|
|
||||||
showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE,
|
|
||||||
"Debounced saver")));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveImmediate() {
|
|
||||||
if (itemListAdapter == null) {
|
if (itemListAdapter == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// List must be loaded and modified in order to save
|
// List must be loaded and modified in order to save
|
||||||
if (isLoadingComplete == null || isModified == null
|
if (isLoadingComplete == null || debounceSaver == null
|
||||||
|| !isLoadingComplete.get() || !isModified.get()) {
|
|| !isLoadingComplete.get() || !debounceSaver.getIsModified()) {
|
||||||
Log.w(TAG, "Attempting to save playlists in bookmark when bookmark "
|
Log.w(TAG, "Attempting to save playlists in bookmark when bookmark "
|
||||||
+ "is not loaded or playlists not modified");
|
+ "is not loaded or playlists not modified");
|
||||||
return;
|
return;
|
||||||
|
@ -485,8 +462,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
remoteItemsUpdate, remoteItemsDeleteUid)
|
remoteItemsUpdate, remoteItemsDeleteUid)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(() -> {
|
.subscribe(() -> {
|
||||||
if (isModified != null) {
|
if (debounceSaver != null) {
|
||||||
isModified.set(false);
|
debounceSaver.setIsModified(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
throwable -> showError(new ErrorInfo(throwable,
|
throwable -> showError(new ErrorInfo(throwable,
|
||||||
|
@ -544,7 +521,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
final int targetIndex = target.getBindingAdapterPosition();
|
final int targetIndex = target.getBindingAdapterPosition();
|
||||||
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
||||||
if (isSwapped) {
|
if (isSwapped) {
|
||||||
saveChanges();
|
debounceSaver.saveChanges();
|
||||||
}
|
}
|
||||||
return isSwapped;
|
return isSwapped;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.player.MainPlayer.PlayerType;
|
import org.schabi.newpipe.player.MainPlayer.PlayerType;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
|
import org.schabi.newpipe.util.DebounceSavable;
|
||||||
|
import org.schabi.newpipe.util.DebounceSaver;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.OnClickGesture;
|
import org.schabi.newpipe.util.OnClickGesture;
|
||||||
|
@ -55,7 +57,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
|
@ -64,11 +65,9 @@ import io.reactivex.rxjava3.core.Flowable;
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
|
||||||
|
|
||||||
public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void> {
|
public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void>
|
||||||
// Save the list 10 seconds after the last change occurred
|
implements DebounceSavable {
|
||||||
private static final long SAVE_DEBOUNCE_MILLIS = 10000;
|
|
||||||
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
|
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
|
||||||
@State
|
@State
|
||||||
protected Long playlistId;
|
protected Long playlistId;
|
||||||
|
@ -85,13 +84,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
private LocalPlaylistManager playlistManager;
|
private LocalPlaylistManager playlistManager;
|
||||||
private Subscription databaseSubscription;
|
private Subscription databaseSubscription;
|
||||||
|
|
||||||
private PublishSubject<Long> debouncedSaveSignal;
|
|
||||||
private CompositeDisposable disposables;
|
private CompositeDisposable disposables;
|
||||||
|
|
||||||
/* Has the playlist been fully loaded from db */
|
/* Has the playlist been fully loaded from db */
|
||||||
private AtomicBoolean isLoadingComplete;
|
private AtomicBoolean isLoadingComplete;
|
||||||
/* Has the playlist been modified (e.g. items reordered or deleted) */
|
|
||||||
private AtomicBoolean isModified;
|
private DebounceSaver debounceSaver;
|
||||||
|
|
||||||
/* Is the playlist currently being processed to remove watched videos */
|
/* Is the playlist currently being processed to remove watched videos */
|
||||||
private boolean isRemovingWatched = false;
|
private boolean isRemovingWatched = false;
|
||||||
|
|
||||||
|
@ -109,12 +108,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
public void onCreate(final Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
|
playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
|
||||||
debouncedSaveSignal = PublishSubject.create();
|
|
||||||
|
|
||||||
disposables = new CompositeDisposable();
|
disposables = new CompositeDisposable();
|
||||||
|
|
||||||
isLoadingComplete = new AtomicBoolean();
|
isLoadingComplete = new AtomicBoolean();
|
||||||
isModified = new AtomicBoolean();
|
debounceSaver = new DebounceSaver(10000, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -220,10 +218,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
if (disposables != null) {
|
if (disposables != null) {
|
||||||
disposables.clear();
|
disposables.clear();
|
||||||
}
|
}
|
||||||
disposables.add(getDebouncedSaver());
|
|
||||||
|
if (debounceSaver != null) {
|
||||||
|
disposables.add(debounceSaver.getDebouncedSaver());
|
||||||
|
debounceSaver.setIsModified(false);
|
||||||
|
}
|
||||||
|
|
||||||
isLoadingComplete.set(false);
|
isLoadingComplete.set(false);
|
||||||
isModified.set(false);
|
|
||||||
|
|
||||||
playlistManager.getPlaylistStreams(playlistId)
|
playlistManager.getPlaylistStreams(playlistId)
|
||||||
.onBackpressureLatest()
|
.onBackpressureLatest()
|
||||||
|
@ -285,19 +286,18 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (debouncedSaveSignal != null) {
|
if (debounceSaver != null) {
|
||||||
debouncedSaveSignal.onComplete();
|
debounceSaver.getDebouncedSaveSignal().onComplete();
|
||||||
}
|
}
|
||||||
if (disposables != null) {
|
if (disposables != null) {
|
||||||
disposables.dispose();
|
disposables.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
debouncedSaveSignal = null;
|
debounceSaver = null;
|
||||||
playlistManager = null;
|
playlistManager = null;
|
||||||
disposables = null;
|
disposables = null;
|
||||||
|
|
||||||
isLoadingComplete = null;
|
isLoadingComplete = null;
|
||||||
isModified = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -321,7 +321,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
@Override
|
@Override
|
||||||
public void onNext(final List<PlaylistStreamEntry> streams) {
|
public void onNext(final List<PlaylistStreamEntry> streams) {
|
||||||
// Skip handling the result after it has been modified
|
// Skip handling the result after it has been modified
|
||||||
if (isModified == null || !isModified.get()) {
|
if (debounceSaver == null || !debounceSaver.getIsModified()) {
|
||||||
handleResult(streams);
|
handleResult(streams);
|
||||||
isLoadingComplete.set(true);
|
isLoadingComplete.set(true);
|
||||||
}
|
}
|
||||||
|
@ -441,7 +441,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
|
|
||||||
itemListAdapter.clearStreamItemList();
|
itemListAdapter.clearStreamItemList();
|
||||||
itemListAdapter.addItems(notWatchedItems);
|
itemListAdapter.addItems(notWatchedItems);
|
||||||
saveChanges();
|
debounceSaver.saveChanges();
|
||||||
|
|
||||||
|
|
||||||
if (thumbnailVideoRemoved) {
|
if (thumbnailVideoRemoved) {
|
||||||
|
@ -609,39 +609,18 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
}
|
}
|
||||||
|
|
||||||
setVideoCount(itemListAdapter.getItemsList().size());
|
setVideoCount(itemListAdapter.getItemsList().size());
|
||||||
saveChanges();
|
debounceSaver.saveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveChanges() {
|
@Override
|
||||||
if (isModified == null || debouncedSaveSignal == null) {
|
public void saveImmediate() {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isModified.set(true);
|
|
||||||
debouncedSaveSignal.onNext(System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Disposable getDebouncedSaver() {
|
|
||||||
if (debouncedSaveSignal == null) {
|
|
||||||
return Disposable.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return debouncedSaveSignal
|
|
||||||
.debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(ignored -> saveImmediate(), throwable ->
|
|
||||||
showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE,
|
|
||||||
"Debounced saver")));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveImmediate() {
|
|
||||||
if (playlistManager == null || itemListAdapter == null) {
|
if (playlistManager == null || itemListAdapter == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// List must be loaded and modified in order to save
|
// List must be loaded and modified in order to save
|
||||||
if (isLoadingComplete == null || isModified == null
|
if (isLoadingComplete == null || debounceSaver == null
|
||||||
|| !isLoadingComplete.get() || !isModified.get()) {
|
|| !isLoadingComplete.get() || !debounceSaver.getIsModified()) {
|
||||||
Log.w(TAG, "Attempting to save playlist when local playlist "
|
Log.w(TAG, "Attempting to save playlist when local playlist "
|
||||||
+ "is not loaded or not modified: playlist id=[" + playlistId + "]");
|
+ "is not loaded or not modified: playlist id=[" + playlistId + "]");
|
||||||
return;
|
return;
|
||||||
|
@ -664,8 +643,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
() -> {
|
() -> {
|
||||||
if (isModified != null) {
|
if (debounceSaver != null) {
|
||||||
isModified.set(false);
|
debounceSaver.setIsModified(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
throwable -> showError(new ErrorInfo(throwable,
|
throwable -> showError(new ErrorInfo(throwable,
|
||||||
|
@ -708,7 +687,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
final int targetIndex = target.getBindingAdapterPosition();
|
final int targetIndex = target.getBindingAdapterPosition();
|
||||||
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
||||||
if (isSwapped) {
|
if (isSwapped) {
|
||||||
saveChanges();
|
debounceSaver.saveChanges();
|
||||||
}
|
}
|
||||||
return isSwapped;
|
return isSwapped;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
|
||||||
|
public interface DebounceSavable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute operations to save the data. <br>
|
||||||
|
* Must set {@link DebounceSaver#setIsModified(boolean)} false in this method manually
|
||||||
|
* after the data has been saved.
|
||||||
|
*/
|
||||||
|
void saveImmediate();
|
||||||
|
|
||||||
|
void showError(ErrorInfo errorInfo);
|
||||||
|
}
|
68
app/src/main/java/org/schabi/newpipe/util/DebounceSaver.java
Normal file
68
app/src/main/java/org/schabi/newpipe/util/DebounceSaver.java
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||||
|
|
||||||
|
public class DebounceSaver {
|
||||||
|
|
||||||
|
private final long saveDebounceMillis;
|
||||||
|
|
||||||
|
private final PublishSubject<Long> debouncedSaveSignal;
|
||||||
|
|
||||||
|
private final DebounceSavable debounceSavable;
|
||||||
|
|
||||||
|
// Has the object been modified
|
||||||
|
private final AtomicBoolean isModified;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code DebounceSaver}.
|
||||||
|
*
|
||||||
|
* @param saveDebounceMillis Save the object milliseconds later after the last change
|
||||||
|
* occurred.
|
||||||
|
* @param debounceSavable The object containing data to be saved.
|
||||||
|
*/
|
||||||
|
public DebounceSaver(final long saveDebounceMillis, final DebounceSavable debounceSavable) {
|
||||||
|
this.saveDebounceMillis = saveDebounceMillis;
|
||||||
|
debouncedSaveSignal = PublishSubject.create();
|
||||||
|
this.debounceSavable = debounceSavable;
|
||||||
|
this.isModified = new AtomicBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getIsModified() {
|
||||||
|
return isModified.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsModified(final boolean isModified) {
|
||||||
|
this.isModified.set(isModified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublishSubject<Long> getDebouncedSaveSignal() {
|
||||||
|
return debouncedSaveSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Disposable getDebouncedSaver() {
|
||||||
|
return debouncedSaveSignal
|
||||||
|
.debounce(saveDebounceMillis, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(ignored -> debounceSavable.saveImmediate(), throwable ->
|
||||||
|
debounceSavable.showError(new ErrorInfo(throwable,
|
||||||
|
UserAction.SOMETHING_ELSE, "Debounced saver")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveChanges() {
|
||||||
|
if (isModified == null || debouncedSaveSignal == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isModified.set(true);
|
||||||
|
debouncedSaveSignal.onNext(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue