1
0
mirror of https://github.com/TeamNewPipe/NewPipe.git synced 2024-11-22 19:12:45 +01:00

-Fixed database updates cause outdated record to overwrite reordered local playlist when fragment is active.

-Fixed save on exit causes empty list being saved after orientation changes on older devices.
-Fixed NPE on animating garbage collected views on local item fragments.
-Reduced drag speed from 15 to 12 items per second.
This commit is contained in:
John Zhen Mo 2018-01-31 11:51:47 -08:00
parent 268762166a
commit 3c3fe7bf83
3 changed files with 100 additions and 66 deletions

View File

@ -47,8 +47,8 @@ public abstract class PlaylistStreamDAO implements BasicDAO<PlaylistStreamEntity
@Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN " + @Query("SELECT * FROM " + STREAM_TABLE + " INNER JOIN " +
// get ids of streams of the given playlist // get ids of streams of the given playlist
"(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX + "(SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX +
" FROM " + PLAYLIST_STREAM_JOIN_TABLE + " WHERE " " FROM " + PLAYLIST_STREAM_JOIN_TABLE +
+ JOIN_PLAYLIST_ID + " = :playlistId)" + " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)" +
// then merge with the stream metadata // then merge with the stream metadata
" ON " + STREAM_ID + " = " + JOIN_STREAM_ID + " ON " + STREAM_ID + " = " + JOIN_STREAM_ID +

View File

@ -126,14 +126,14 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
@Override @Override
public void showLoading() { public void showLoading() {
super.showLoading(); super.showLoading();
animateView(itemsList, false, 200); if (itemsList != null) animateView(itemsList, false, 200);
if (headerRootView != null) animateView(headerRootView, false, 200); if (headerRootView != null) animateView(headerRootView, false, 200);
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
super.hideLoading(); super.hideLoading();
animateView(itemsList, true, 200); if (itemsList != null) animateView(itemsList, true, 200);
if (headerRootView != null) animateView(headerRootView, true, 200); if (headerRootView != null) animateView(headerRootView, true, 200);
} }
@ -142,7 +142,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
super.showError(message, showRetryButton); super.showError(message, showRetryButton);
showListFooter(false); showListFooter(false);
animateView(itemsList, false, 200); if (itemsList != null) animateView(itemsList, false, 200);
if (headerRootView != null) animateView(headerRootView, false, 200); if (headerRootView != null) animateView(headerRootView, false, 200);
} }

View File

@ -37,6 +37,7 @@ import org.schabi.newpipe.util.OnClickGesture;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import icepick.State; import icepick.State;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
@ -49,7 +50,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
// Save the list 10 seconds after the last change occurred // Save the list 10 seconds after the last change occurred
private static final long SAVE_DEBOUNCE_MILLIS = 10000; private static final long SAVE_DEBOUNCE_MILLIS = 10000;
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 15; private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
private View headerRootLayout; private View headerRootLayout;
private TextView headerTitleView; private TextView headerTitleView;
@ -75,6 +76,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private PublishSubject<Long> debouncedSaveSignal; private PublishSubject<Long> debouncedSaveSignal;
private Disposable debouncedSaver; private Disposable debouncedSaver;
/* Has the playlist been fully loaded from db */
private AtomicBoolean isLoadingComplete;
/* Has the playlist been modified (e.g. items reordered or deleted) */
private AtomicBoolean isModified;
public static LocalPlaylistFragment getInstance(long playlistId, String name) { public static LocalPlaylistFragment getInstance(long playlistId, String name) {
LocalPlaylistFragment instance = new LocalPlaylistFragment(); LocalPlaylistFragment instance = new LocalPlaylistFragment();
instance.setInitialData(playlistId, name); instance.setInitialData(playlistId, name);
@ -90,6 +96,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext())); playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(getContext()));
debouncedSaveSignal = PublishSubject.create(); debouncedSaveSignal = PublishSubject.create();
isLoadingComplete = new AtomicBoolean();
isModified = new AtomicBoolean();
} }
@Override @Override
@ -176,15 +185,15 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
@Override @Override
public void showLoading() { public void showLoading() {
super.showLoading(); super.showLoading();
animateView(headerRootLayout, false, 200); if (headerRootLayout != null) animateView(headerRootLayout, false, 200);
animateView(playlistControl, false, 200); if (playlistControl != null) animateView(playlistControl, false, 200);
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
super.hideLoading(); super.hideLoading();
animateView(headerRootLayout, true, 200); if (headerRootLayout != null) animateView(headerRootLayout, true, 200);
animateView(playlistControl, true, 200); if (playlistControl != null) animateView(playlistControl, true, 200);
} }
@Override @Override
@ -194,7 +203,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
if (debouncedSaver != null) debouncedSaver.dispose(); if (debouncedSaver != null) debouncedSaver.dispose();
debouncedSaver = getDebouncedSaver(); debouncedSaver = getDebouncedSaver();
isLoadingComplete.set(false);
isModified.set(false);
playlistManager.getPlaylistStreams(playlistId) playlistManager.getPlaylistStreams(playlistId)
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(getPlaylistObserver()); .subscribe(getPlaylistObserver());
} }
@ -207,12 +220,15 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
itemsListState = itemsList.getLayoutManager().onSaveInstanceState(); itemsListState = itemsList.getLayoutManager().onSaveInstanceState();
saveImmediate(); // Save on exit
// Save on exit
saveImmediate();
} }
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
if (databaseSubscription != null) databaseSubscription.cancel(); if (databaseSubscription != null) databaseSubscription.cancel();
if (debouncedSaver != null) debouncedSaver.dispose(); if (debouncedSaver != null) debouncedSaver.dispose();
@ -228,6 +244,9 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
debouncedSaveSignal = null; debouncedSaveSignal = null;
playlistManager = null; playlistManager = null;
isLoadingComplete = null;
isModified = null;
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -239,6 +258,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
@Override @Override
public void onSubscribe(Subscription s) { public void onSubscribe(Subscription s) {
showLoading(); showLoading();
isLoadingComplete.set(false);
if (databaseSubscription != null) databaseSubscription.cancel(); if (databaseSubscription != null) databaseSubscription.cancel();
databaseSubscription = s; databaseSubscription = s;
@ -247,10 +267,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
@Override @Override
public void onNext(List<PlaylistStreamEntry> streams) { public void onNext(List<PlaylistStreamEntry> streams) {
// Do not allow saving while the result is being updated // Skip handling the result after it has been modified
if (debouncedSaver != null) debouncedSaver.dispose(); if (isModified == null || !isModified.get()) {
handleResult(streams); handleResult(streams);
debouncedSaver = getDebouncedSaver(); isLoadingComplete.set(true);
}
if (databaseSubscription != null) databaseSubscription.request(1); if (databaseSubscription != null) databaseSubscription.request(1);
} }
@ -339,7 +360,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
this.name = name; this.name = name;
setTitle(name); setTitle(name);
Log.e(TAG, "Updating playlist id=[" + playlistId + Log.d(TAG, "Updating playlist id=[" + playlistId +
"] with new name=[" + name + "] items"); "] with new name=[" + name + "] items");
playlistManager.renamePlaylist(playlistId, name) playlistManager.renamePlaylist(playlistId, name)
@ -352,7 +373,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
R.string.playlist_thumbnail_change_success, R.string.playlist_thumbnail_change_success,
Toast.LENGTH_SHORT); Toast.LENGTH_SHORT);
Log.e(TAG, "Updating playlist id=[" + playlistId + Log.d(TAG, "Updating playlist id=[" + playlistId +
"] with new thumbnail url=[" + thumbnailUrl + "]"); "] with new thumbnail url=[" + thumbnailUrl + "]");
playlistManager.changePlaylistThumbnail(playlistId, thumbnailUrl) playlistManager.changePlaylistThumbnail(playlistId, thumbnailUrl)
@ -363,10 +384,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private void deleteItem(final PlaylistStreamEntry item) { private void deleteItem(final PlaylistStreamEntry item) {
itemListAdapter.removeItem(item); itemListAdapter.removeItem(item);
setVideoCount(itemListAdapter.getItemsList().size()); setVideoCount(itemListAdapter.getItemsList().size());
saveDebounced(); saveChanges();
} }
private void saveDebounced() { private void saveChanges() {
isModified.set(true);
debouncedSaveSignal.onNext(System.currentTimeMillis()); debouncedSaveSignal.onNext(System.currentTimeMillis());
} }
@ -374,10 +396,18 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
return debouncedSaveSignal return debouncedSaveSignal
.debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS) .debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(ignored -> saveImmediate()); .subscribe(ignored -> saveImmediate(), this::onError);
} }
private void saveImmediate() { private void saveImmediate() {
// List must be loaded and modified in order to save
if (isLoadingComplete == null || isModified == null ||
!isLoadingComplete.get() || !isModified.get()) {
Log.w(TAG, "Attempting to save playlist when local playlist " +
"is not loaded or not modified: playlist id=[" + playlistId + "]");
return;
}
final List<LocalItem> items = itemListAdapter.getItemsList(); final List<LocalItem> items = itemListAdapter.getItemsList();
List<Long> streamIds = new ArrayList<>(items.size()); List<Long> streamIds = new ArrayList<>(items.size());
for (final LocalItem item : items) { for (final LocalItem item : items) {
@ -386,12 +416,60 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
} }
} }
Log.e(TAG, "Updating playlist id=[" + playlistId + Log.d(TAG, "Updating playlist id=[" + playlistId +
"] with [" + streamIds.size() + "] items"); "] with [" + streamIds.size() + "] items");
playlistManager.updateJoin(playlistId, streamIds) playlistManager.updateJoin(playlistId, streamIds)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {/*Do nothing on success*/}, this::onError); .subscribe(
() -> { if (isModified != null) isModified.set(false); },
this::onError
);
}
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.ACTION_STATE_IDLE) {
@Override
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize,
int viewSizeOutOfBounds, int totalSize,
long msSinceStartScroll) {
final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize,
viewSizeOutOfBounds, totalSize, msSinceStartScroll);
final int minimumAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY,
Math.abs(standardSpeed));
return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source,
RecyclerView.ViewHolder target) {
if (source.getItemViewType() != target.getItemViewType() ||
itemListAdapter == null) {
return false;
}
final int sourceIndex = source.getAdapterPosition();
final int targetIndex = target.getAdapterPosition();
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
if (isSwapped) saveChanges();
return isSwapped;
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {}
};
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -449,50 +527,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
new InfoItemDialog(getActivity(), infoItem, commands, actions).show(); new InfoItemDialog(getActivity(), infoItem, commands, actions).show();
} }
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.ACTION_STATE_IDLE) {
@Override
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize,
int viewSizeOutOfBounds, int totalSize,
long msSinceStartScroll) {
final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize,
viewSizeOutOfBounds, totalSize, msSinceStartScroll);
final int minimumAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY,
Math.abs(standardSpeed));
return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source,
RecyclerView.ViewHolder target) {
if (source.getItemViewType() != target.getItemViewType() ||
itemListAdapter == null) {
return false;
}
final int sourceIndex = source.getAdapterPosition();
final int targetIndex = target.getAdapterPosition();
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
if (isSwapped) saveDebounced();
return isSwapped;
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {}
};
}
private void setInitialData(long playlistId, String name) { private void setInitialData(long playlistId, String name) {
this.playlistId = playlistId; this.playlistId = playlistId;
this.name = !TextUtils.isEmpty(name) ? name : ""; this.name = !TextUtils.isEmpty(name) ? name : "";