diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
index e74a6f2ba..3cd06f3d6 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
@@ -1,16 +1,14 @@
package org.schabi.newpipe.info_list;
import android.app.Activity;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import org.schabi.newpipe.R;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
@@ -29,16 +27,11 @@ import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder;
import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder;
-import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.FallbackViewHolder;
import org.schabi.newpipe.util.OnClickGesture;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
-
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.disposables.CompositeDisposable;
/*
* Created by Christian Schabesberger on 01.08.16.
@@ -60,7 +53,7 @@ import io.reactivex.disposables.CompositeDisposable;
* along with NewPipe. If not, see .
*/
-public class InfoListAdapter extends RecyclerView.Adapter {
+public class InfoListAdapter extends StateObjectsListAdapter {
private static final String TAG = InfoListAdapter.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -80,10 +73,7 @@ public class InfoListAdapter extends RecyclerView.Adapter infoItemList;
- private final ArrayList states;
- private final CompositeDisposable stateLoaders;
private boolean useMiniVariant = false;
private boolean useGridVariant = false;
private boolean showFooter = false;
@@ -100,11 +90,9 @@ public class InfoListAdapter extends RecyclerView.Adapter();
- states = new ArrayList<>();
- stateLoaders = new CompositeDisposable();
}
public void setOnStreamSelectedListener(OnClickGesture listener) {
@@ -131,107 +119,64 @@ public class InfoListAdapter extends RecyclerView.Adapter data) {
- if (isPlaybackStatesVisible()) {
- stateLoaders.add(
- historyRecordManager.loadStreamStateBatch(data)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(streamStateEntities -> {
- addInfoItemList(data, streamStateEntities);
- })
- );
- } else {
- final ArrayList states = new ArrayList<>(data.size());
- for (int i = data.size(); i > 0; i--) states.add(null);
- addInfoItemList(data, states);
- }
- }
-
- private void addInfoItemList(List data, List statesEntities) {
+ public void addInfoItemList(@Nullable final List data) {
if (data != null) {
- if (DEBUG) {
- Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + infoItemList.size() + ", data.size() = " + data.size());
- }
-
- int offsetStart = sizeConsideringHeaderOffset();
- infoItemList.addAll(data);
- states.addAll(statesEntities);
-
- if (DEBUG) {
- Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
- }
-
- notifyItemRangeInserted(offsetStart, data.size());
-
- if (footer != null && showFooter) {
- int footerNow = sizeConsideringHeaderOffset();
- notifyItemMoved(offsetStart, footerNow);
-
- if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart + " to " + footerNow);
- }
+ loadStates(data, infoItemList.size(), () -> addInfoItemListImpl(data));
}
}
- public void addInfoItem(InfoItem data) {
- if (isPlaybackStatesVisible()) {
- stateLoaders.add(
- historyRecordManager.loadStreamState(data)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(streamStateEntity -> {
- addInfoItem(data, streamStateEntity[0]);
- })
- );
- } else {
- addInfoItem(data, null);
+ private void addInfoItemListImpl(@NonNull List data) {
+ if (DEBUG) {
+ Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + infoItemList.size() + ", data.size() = " + data.size());
+ }
+
+ int offsetStart = sizeConsideringHeaderOffset();
+ infoItemList.addAll(data);
+
+ if (DEBUG) {
+ Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
+ }
+
+ notifyItemRangeInserted(offsetStart, data.size());
+
+ if (footer != null && showFooter) {
+ int footerNow = sizeConsideringHeaderOffset();
+ notifyItemMoved(offsetStart, footerNow);
+
+ if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart + " to " + footerNow);
}
}
- private void addInfoItem(InfoItem data, StreamStateEntity state) {
+ public void addInfoItem(@Nullable InfoItem data) {
if (data != null) {
- if (DEBUG) {
- Log.d(TAG, "addInfoItem() before > infoItemList.size() = " + infoItemList.size() + ", thread = " + Thread.currentThread());
- }
+ loadState(data, infoItemList.size(), () -> addInfoItemImpl(data));
+ }
+ }
- int positionInserted = sizeConsideringHeaderOffset();
- infoItemList.add(data);
- states.add(state);
+ private void addInfoItemImpl(@NonNull InfoItem data) {
+ if (DEBUG) {
+ Log.d(TAG, "addInfoItem() before > infoItemList.size() = " + infoItemList.size() + ", thread = " + Thread.currentThread());
+ }
- if (DEBUG) {
- Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
- }
- notifyItemInserted(positionInserted);
+ int positionInserted = sizeConsideringHeaderOffset();
+ infoItemList.add(data);
- if (footer != null && showFooter) {
- int footerNow = sizeConsideringHeaderOffset();
- notifyItemMoved(positionInserted, footerNow);
+ if (DEBUG) {
+ Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter);
+ }
+ notifyItemInserted(positionInserted);
- if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted + " to " + footerNow);
- }
+ if (footer != null && showFooter) {
+ int footerNow = sizeConsideringHeaderOffset();
+ notifyItemMoved(positionInserted, footerNow);
+
+ if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted + " to " + footerNow);
}
}
public void updateStates() {
- if (infoItemList.isEmpty()) {
- return;
- }
- if (isPlaybackStatesVisible()) {
- stateLoaders.add(
- historyRecordManager.loadStreamStateBatch(infoItemList)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe((streamStateEntities) -> {
- if (streamStateEntities.size() == states.size()) {
- for (int i = 0; i < states.size(); i++) {
- final StreamStateEntity newState = streamStateEntities.get(i);
- if (!Objects.equals(states.get(i), newState)) {
- states.set(i, newState);
- notifyItemChanged(header == null ? i : i + 1);
- }
- }
- } else {
- //oops, something is wrong
- }
- })
- );
+ if (!infoItemList.isEmpty()) {
+ updateAllStates(infoItemList);
}
}
@@ -240,7 +185,7 @@ public class InfoListAdapter extends RecyclerView.Adapter {
+
+ private final SparseArray states;
+ private final HistoryRecordManager recordManager;
+ private final CompositeDisposable stateLoaders;
+ private final Context context;
+
+ public StateObjectsListAdapter(Context context) {
+ this.states = new SparseArray<>();
+ this.recordManager = new HistoryRecordManager(context);
+ this.context = context;
+ this.stateLoaders = new CompositeDisposable();
+ }
+
+ @Nullable
+ public StreamStateEntity getState(int position) {
+ return states.get(position);
+ }
+
+ protected void clearStates() {
+ states.clear();
+ }
+
+ private void appendStates(List statesEntities, int offset) {
+ for (int i = 0; i < statesEntities.size(); i++) {
+ final StreamStateEntity state = statesEntities.get(i);
+ if (state != null) {
+ states.append(offset + i, state);
+ }
+ }
+ }
+
+ private void appendState(StreamStateEntity statesEntity, int offset) {
+ if (statesEntity != null) {
+ states.append(offset, statesEntity);
+ }
+ }
+
+ protected void removeState(int index) {
+ states.remove(index);
+ }
+
+ protected void moveState(int from, int to) {
+ final StreamStateEntity item = states.get(from);
+ if (from < to) {
+ SparseArrayUtils.shiftItemsDown(states, from, to);
+ } else {
+ SparseArrayUtils.shiftItemsUp(states, to, from);
+ }
+ states.put(to, item);
+ }
+
+ protected void loadStates(List list, int offset, Runnable callback) {
+ if (isPlaybackStatesVisible()) {
+ stateLoaders.add(
+ recordManager.loadStreamStateBatch(list)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(streamStateEntities -> {
+ appendStates(streamStateEntities, offset);
+ callback.run();
+ }, throwable -> {
+ if (BuildConfig.DEBUG) throwable.printStackTrace();
+ callback.run();
+ })
+ );
+ } else {
+ callback.run();
+ }
+ }
+
+ protected void loadState(InfoItem item, int offset, Runnable callback) {
+ if (isPlaybackStatesVisible()) {
+ stateLoaders.add(
+ recordManager.loadStreamState(item)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(streamStateEntities -> {
+ appendState(streamStateEntities[0], offset);
+ callback.run();
+ }, throwable -> {
+ if (BuildConfig.DEBUG) throwable.printStackTrace();
+ callback.run();
+ })
+ );
+ } else {
+ callback.run();
+ }
+ }
+
+ protected void loadStatesForLocal(List extends LocalItem> list, int offset, Runnable callback) {
+ if (isPlaybackStatesVisible()) {
+ stateLoaders.add(
+ recordManager.loadLocalStreamStateBatch(list)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(streamStateEntities -> {
+ appendStates(streamStateEntities, offset);
+ callback.run();
+ }, throwable -> {
+ if (BuildConfig.DEBUG) throwable.printStackTrace();
+ callback.run();
+ })
+ );
+ } else {
+ callback.run();
+ }
+ }
+
+ private void processStatesUpdates(List streamStateEntities) {
+ for (int i = 0; i < streamStateEntities.size(); i++) {
+ final StreamStateEntity newState = streamStateEntities.get(i);
+ if (!Objects.equals(states.get(i), newState)) {
+ if (newState == null) {
+ states.remove(i);
+ } else {
+ states.put(i, newState);
+ }
+ onItemStateChanged(i, newState);
+ }
+ }
+ }
+
+ protected void updateAllStates(List list) {
+ if (isPlaybackStatesVisible()) {
+ stateLoaders.add(
+ recordManager.loadStreamStateBatch(list)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::processStatesUpdates, throwable -> {
+ if (BuildConfig.DEBUG) throwable.printStackTrace();
+ })
+ );
+ } else {
+ final int[] positions = SparseArrayUtils.getKeys(states);
+ states.clear();
+ for (int pos : positions) onItemStateChanged(pos, null);
+ }
+ }
+
+ protected void updateAllLocalStates(List extends LocalItem> list) {
+ if (isPlaybackStatesVisible()) {
+ stateLoaders.add(
+ recordManager.loadLocalStreamStateBatch(list)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::processStatesUpdates, throwable -> {
+ if (BuildConfig.DEBUG) throwable.printStackTrace();
+ })
+ );
+ } else {
+ final int[] positions = SparseArrayUtils.getKeys(states);
+ states.clear();
+ for (int pos : positions) onItemStateChanged(pos, null);
+ }
+ }
+
+ public void dispose() {
+ stateLoaders.dispose();
+ }
+
+ protected boolean isPlaybackStatesVisible() {
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ return prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)
+ && prefs.getBoolean(context.getString(R.string.enable_playback_resume_key), true)
+ && prefs.getBoolean(context.getString(R.string.enable_playback_state_lists_key), true);
+ }
+
+ protected abstract void onItemStateChanged(int position, @Nullable StreamStateEntity state);
+
+}
diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
index 6dddcb714..903712af2 100644
--- a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java
@@ -1,19 +1,17 @@
package org.schabi.newpipe.local;
import android.app.Activity;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
-import org.schabi.newpipe.local.history.HistoryRecordManager;
+import org.schabi.newpipe.info_list.StateObjectsListAdapter;
import org.schabi.newpipe.local.holder.LocalItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder;
import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder;
@@ -30,10 +28,6 @@ import org.schabi.newpipe.util.OnClickGesture;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
-
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.disposables.CompositeDisposable;
/*
* Created by Christian Schabesberger on 01.08.16.
@@ -55,7 +49,7 @@ import io.reactivex.disposables.CompositeDisposable;
* along with NewPipe. If not, see .
*/
-public class LocalItemListAdapter extends RecyclerView.Adapter {
+public class LocalItemListAdapter extends StateObjectsListAdapter {
private static final String TAG = LocalItemListAdapter.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -73,10 +67,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter localItems;
- private final ArrayList states;
- private final CompositeDisposable stateLoaders;
private final DateFormat dateFormat;
private boolean showFooter = false;
@@ -85,13 +76,11 @@ public class LocalItemListAdapter extends RecyclerView.Adapter();
dateFormat = DateFormat.getDateInstance(DateFormat.SHORT,
Localization.getPreferredLocale(activity));
- states = new ArrayList<>();
- stateLoaders = new CompositeDisposable();
}
public void setSelectedListener(OnClickGesture listener) {
@@ -102,76 +91,49 @@ public class LocalItemListAdapter extends RecyclerView.Adapter data) {
- if (isPlaybackStatesVisible()) {
- stateLoaders.add(
- historyRecordManager.loadLocalStreamStateBatch(data)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(streamStateEntities ->
- addItems(data, streamStateEntities))
- );
- } else {
- final ArrayList states = new ArrayList<>(data.size());
- for (int i = data.size(); i > 0; i--) states.add(null);
- addItems(data, states);
+ public void addItems(@Nullable List extends LocalItem> data) {
+ if (data != null) {
+ loadStatesForLocal(data, localItems.size(), () -> addItemsImpl(data));
}
}
- private void addItems(List extends LocalItem> data, List streamStates) {
- if (data != null) {
- if (DEBUG) {
- Log.d(TAG, "addItems() before > localItems.size() = " +
- localItems.size() + ", data.size() = " + data.size());
- }
+ private void addItemsImpl(@NonNull List extends LocalItem> data) {
+ if (DEBUG) {
+ Log.d(TAG, "addItems() before > localItems.size() = " +
+ localItems.size() + ", data.size() = " + data.size());
+ }
- int offsetStart = sizeConsideringHeader();
- localItems.addAll(data);
- states.addAll(streamStates);
+ int offsetStart = sizeConsideringHeader();
+ localItems.addAll(data);
- if (DEBUG) {
- Log.d(TAG, "addItems() after > offsetStart = " + offsetStart +
- ", localItems.size() = " + localItems.size() +
- ", header = " + header + ", footer = " + footer +
- ", showFooter = " + showFooter);
- }
+ if (DEBUG) {
+ Log.d(TAG, "addItems() after > offsetStart = " + offsetStart +
+ ", localItems.size() = " + localItems.size() +
+ ", header = " + header + ", footer = " + footer +
+ ", showFooter = " + showFooter);
+ }
- notifyItemRangeInserted(offsetStart, data.size());
+ notifyItemRangeInserted(offsetStart, data.size());
- if (footer != null && showFooter) {
- int footerNow = sizeConsideringHeader();
- notifyItemMoved(offsetStart, footerNow);
+ if (footer != null && showFooter) {
+ int footerNow = sizeConsideringHeader();
+ notifyItemMoved(offsetStart, footerNow);
- if (DEBUG) Log.d(TAG, "addItems() footer from " + offsetStart +
- " to " + footerNow);
- }
+ if (DEBUG) Log.d(TAG, "addItems() footer from " + offsetStart +
+ " to " + footerNow);
}
}
public void updateStates() {
- if (localItems.isEmpty() || !isPlaybackStatesVisible()) return;
- stateLoaders.add(
- historyRecordManager.loadLocalStreamStateBatch(localItems)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe((streamStateEntities) -> {
- if (streamStateEntities.size() == states.size()) {
- for (int i = 0; i < states.size(); i++) {
- final StreamStateEntity newState = streamStateEntities.get(i);
- if (!Objects.equals(states.get(i), newState)) {
- states.set(i, newState);
- notifyItemChanged(header == null ? i : i + 1);
- }
- }
- } else {
- //oops, something is wrong
- }
- })
- );
+ if (!localItems.isEmpty()) {
+ updateAllLocalStates(localItems);
+ }
}
public void removeItem(final LocalItem data) {
final int index = localItems.indexOf(data);
-
localItems.remove(index);
+ removeState(index);
notifyItemRemoved(index + (header != null ? 1 : 0));
}
@@ -183,7 +145,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter= localItems.size() || actualTo >= localItems.size()) return false;
localItems.add(actualTo, localItems.remove(actualFrom));
- states.add(actualTo, states.remove(actualFrom));
+ moveState(actualFrom, actualTo);
notifyItemMoved(fromAdapterPosition, toAdapterPosition);
return true;
}
@@ -193,6 +155,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter void shiftItemsDown(SparseArray sparseArray, int lower, int upper) {
+ for (int i = lower + 1; i <= upper; i++) {
+ final T o = sparseArray.get(i);
+ sparseArray.put(i - 1, o);
+ sparseArray.remove(i);
+ }
+ }
+
+ public static void shiftItemsUp(SparseArray sparseArray, int lower, int upper) {
+ for (int i = upper - 1; i >= lower; i--) {
+ final T o = sparseArray.get(i);
+ sparseArray.put(i + 1, o);
+ sparseArray.remove(i);
+ }
+ }
+
+ public static int[] getKeys(SparseArray sparseArray) {
+ final int[] result = new int[sparseArray.size()];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = sparseArray.keyAt(i);
+ }
+ return result;
+ }
+}