mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 23:03:00 +00:00 
			
		
		
		
	Reuse DebounceSaver
This commit is contained in:
		| @@ -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()); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 GGAutomaton
					GGAutomaton