mirror of
https://github.com/AntennaPod/AntennaPod.git
synced 2025-10-29 11:49:33 +00:00
Remember scroll positions (#7801)
This commit is contained in:
parent
6fe2d54935
commit
b4de4548ae
@ -1,23 +1,19 @@
|
||||
package de.danoeh.antennapod.ui.episodeslist;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.view.ContextThemeWrapper;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import de.danoeh.antennapod.R;
|
||||
|
||||
public class EpisodeItemListRecyclerView extends RecyclerView {
|
||||
private static final String TAG = "EpisodeItemListRecyclerView";
|
||||
private static final String PREF_PREFIX_SCROLL_POSITION = "scroll_position_";
|
||||
private static final String PREF_PREFIX_SCROLL_OFFSET = "scroll_offset_";
|
||||
|
||||
private LinearLayoutManager layoutManager;
|
||||
|
||||
public EpisodeItemListRecyclerView(@NonNull Context context) {
|
||||
@ -51,29 +47,18 @@ public class EpisodeItemListRecyclerView extends RecyclerView {
|
||||
setPadding(horizontalSpacing, getPaddingTop(), horizontalSpacing, getPaddingBottom());
|
||||
}
|
||||
|
||||
public void saveScrollPosition(String tag) {
|
||||
public Pair<Integer, Integer> getScrollPosition() {
|
||||
int firstItem = layoutManager.findFirstVisibleItemPosition();
|
||||
View firstItemView = layoutManager.findViewByPosition(firstItem);
|
||||
float topOffset;
|
||||
if (firstItemView == null) {
|
||||
topOffset = 0;
|
||||
} else {
|
||||
topOffset = firstItemView.getTop();
|
||||
}
|
||||
|
||||
getContext().getSharedPreferences(TAG, Context.MODE_PRIVATE).edit()
|
||||
.putInt(PREF_PREFIX_SCROLL_POSITION + tag, firstItem)
|
||||
.putInt(PREF_PREFIX_SCROLL_OFFSET + tag, (int) topOffset)
|
||||
.apply();
|
||||
int topOffset = firstItemView == null ? 0 : firstItemView.getTop();
|
||||
return new Pair<>(firstItem, topOffset);
|
||||
}
|
||||
|
||||
public void restoreScrollPosition(String tag) {
|
||||
SharedPreferences prefs = getContext().getSharedPreferences(TAG, Context.MODE_PRIVATE);
|
||||
int position = prefs.getInt(PREF_PREFIX_SCROLL_POSITION + tag, 0);
|
||||
int offset = prefs.getInt(PREF_PREFIX_SCROLL_OFFSET + tag, 0);
|
||||
if (position > 0 || offset > 0) {
|
||||
layoutManager.scrollToPositionWithOffset(position, offset);
|
||||
public void restoreScrollPosition(Pair<Integer, Integer> scrollPosition) {
|
||||
if (scrollPosition == null || (scrollPosition.first == 0 && scrollPosition.second == 0)) {
|
||||
return;
|
||||
}
|
||||
layoutManager.scrollToPositionWithOffset(scrollPosition.first, scrollPosition.second);
|
||||
}
|
||||
|
||||
public boolean isScrolledToBottom() {
|
||||
|
||||
@ -98,7 +98,6 @@ public abstract class EpisodesListFragment extends Fragment
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
recyclerView.saveScrollPosition(getPrefName());
|
||||
unregisterForContextMenu(recyclerView);
|
||||
}
|
||||
|
||||
@ -406,15 +405,15 @@ public abstract class EpisodesListFragment extends Fragment
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
data -> {
|
||||
final boolean restoreScrollPosition = episodes.isEmpty();
|
||||
final boolean firstLoaded = episodes.isEmpty();
|
||||
episodes = data.first;
|
||||
hasMoreItems = !(page == 1 && episodes.size() < EPISODES_PER_PAGE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
listAdapter.setDummyViews(0);
|
||||
listAdapter.updateItems(episodes);
|
||||
listAdapter.setTotalNumberOfItems(data.second);
|
||||
if (restoreScrollPosition) {
|
||||
recyclerView.restoreScrollPosition(getPrefName());
|
||||
if (firstLoaded) {
|
||||
onItemsFirstLoaded();
|
||||
}
|
||||
updateToolbar();
|
||||
}, error -> {
|
||||
@ -436,11 +435,12 @@ public abstract class EpisodesListFragment extends Fragment
|
||||
|
||||
protected abstract String getFragmentTag();
|
||||
|
||||
protected abstract String getPrefName();
|
||||
|
||||
protected void updateToolbar() {
|
||||
}
|
||||
|
||||
protected void onItemsFirstLoaded() {
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(FeedUpdateRunningEvent event) {
|
||||
swipeRefreshLayout.setRefreshing(event.isFeedUpdateRunning);
|
||||
|
||||
@ -7,6 +7,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.storage.database.DBReader;
|
||||
import de.danoeh.antennapod.ui.AllEpisodesFilterDialog;
|
||||
@ -31,6 +33,7 @@ import java.util.List;
|
||||
public class AllEpisodesFragment extends EpisodesListFragment {
|
||||
public static final String TAG = "EpisodesFragment";
|
||||
public static final String PREF_NAME = "PrefAllEpisodesFragment";
|
||||
private static Pair<Integer, Integer> scrollPosition = null;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@ -85,8 +88,14 @@ public class AllEpisodesFragment extends EpisodesListFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPrefName() {
|
||||
return PREF_NAME;
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
scrollPosition = recyclerView.getScrollPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onItemsFirstLoaded() {
|
||||
recyclerView.restoreScrollPosition(scrollPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -10,6 +10,8 @@ import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.event.MessageEvent;
|
||||
@ -35,6 +37,7 @@ public class InboxFragment extends EpisodesListFragment {
|
||||
private static final String PREF_NAME = "PrefNewEpisodesFragment";
|
||||
private static final String PREF_DO_NOT_PROMPT_REMOVE_ALL_FROM_INBOX = "prefDoNotPromptRemovalAllFromInbox";
|
||||
private SharedPreferences prefs;
|
||||
private static Pair<Integer, Integer> scrollPosition = null;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@ -61,8 +64,14 @@ public class InboxFragment extends EpisodesListFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPrefName() {
|
||||
return PREF_NAME;
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
scrollPosition = recyclerView.getScrollPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onItemsFirstLoaded() {
|
||||
recyclerView.restoreScrollPosition(scrollPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -7,6 +7,8 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.ui.common.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.storage.database.DBReader;
|
||||
@ -25,6 +27,7 @@ public class PlaybackHistoryFragment extends EpisodesListFragment {
|
||||
public static final String TAG = "PlaybackHistoryFragment";
|
||||
private static final FeedItemFilter FILTER_HISTORY = new FeedItemFilter(
|
||||
FeedItemFilter.IS_IN_HISTORY, FeedItemFilter.INCLUDE_NOT_SUBSCRIBED);
|
||||
private static Pair<Integer, Integer> scrollPosition = null;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@ -50,8 +53,14 @@ public class PlaybackHistoryFragment extends EpisodesListFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPrefName() {
|
||||
return TAG;
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
scrollPosition = recyclerView.getScrollPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onItemsFirstLoaded() {
|
||||
recyclerView.restoreScrollPosition(scrollPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -81,6 +81,8 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
|
||||
EpisodeItemListAdapter.OnSelectModeListener {
|
||||
public static final String TAG = "QueueFragment";
|
||||
private static final String KEY_UP_ARROW = "up_arrow";
|
||||
private static final String SCROLL_POSITION_KEY = "scroll_position";
|
||||
private static final String SCROLL_OFFSET_KEY = "scroll_offset";
|
||||
|
||||
private TextView infoBar;
|
||||
private EpisodeItemListRecyclerView recyclerView;
|
||||
@ -111,17 +113,16 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (queue != null) {
|
||||
recyclerView.restoreScrollPosition(QueueFragment.TAG);
|
||||
}
|
||||
loadItems(true);
|
||||
loadItems();
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
recyclerView.saveScrollPosition(QueueFragment.TAG);
|
||||
Pair<Integer, Integer> scrollPosition = recyclerView.getScrollPosition();
|
||||
prefs.edit().putInt(SCROLL_POSITION_KEY, scrollPosition.first)
|
||||
.putInt(SCROLL_OFFSET_KEY, scrollPosition.second).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -139,7 +140,7 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
|
||||
if (queue == null) {
|
||||
return;
|
||||
} else if (recyclerAdapter == null) {
|
||||
loadItems(true);
|
||||
loadItems();
|
||||
return;
|
||||
}
|
||||
int position;
|
||||
@ -173,7 +174,6 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
|
||||
}
|
||||
recyclerAdapter.updateDragDropEnabled();
|
||||
refreshToolbarState();
|
||||
recyclerView.saveScrollPosition(QueueFragment.TAG);
|
||||
refreshInfoBar();
|
||||
}
|
||||
|
||||
@ -183,7 +183,7 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
|
||||
if (queue == null) {
|
||||
return;
|
||||
} else if (recyclerAdapter == null) {
|
||||
loadItems(true);
|
||||
loadItems();
|
||||
return;
|
||||
}
|
||||
for (int i = 0, size = event.items.size(); i < size; i++) {
|
||||
@ -227,14 +227,14 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onPlayerStatusChanged(PlayerStatusEvent event) {
|
||||
loadItems(false);
|
||||
loadItems();
|
||||
refreshToolbarState();
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) {
|
||||
// Sent when playback position is reset
|
||||
loadItems(false);
|
||||
loadItems();
|
||||
refreshToolbarState();
|
||||
}
|
||||
|
||||
@ -521,7 +521,7 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
|
||||
}
|
||||
}
|
||||
|
||||
private void loadItems(final boolean restoreScrollPosition) {
|
||||
private void loadItems() {
|
||||
Log.d(TAG, "loadItems()");
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
@ -536,6 +536,7 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(itemsAndDisplayButton -> {
|
||||
final boolean restoreScrollPosition = queue == null || queue.isEmpty();
|
||||
queue = itemsAndDisplayButton.first;
|
||||
if (itemsAndDisplayButton.second) {
|
||||
emptyView.setMessage(R.string.no_queue_items_inbox_has_items_label);
|
||||
@ -548,7 +549,9 @@ public class QueueFragment extends Fragment implements MaterialToolbar.OnMenuIte
|
||||
recyclerAdapter.setDummyViews(0);
|
||||
recyclerAdapter.updateItems(queue);
|
||||
if (restoreScrollPosition) {
|
||||
recyclerView.restoreScrollPosition(QueueFragment.TAG);
|
||||
Pair<Integer, Integer> scrollPosition = new Pair<>(
|
||||
prefs.getInt(SCROLL_POSITION_KEY, 0), prefs.getInt(SCROLL_OFFSET_KEY, 0));
|
||||
recyclerView.restoreScrollPosition(scrollPosition);
|
||||
}
|
||||
refreshInfoBar();
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
|
||||
@ -13,8 +13,10 @@ import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
@ -87,6 +89,7 @@ public class SubscriptionFragment extends Fragment
|
||||
|
||||
private Disposable disposable;
|
||||
private SharedPreferences prefs;
|
||||
private static Pair<Integer, Integer> scrollPosition = null;
|
||||
|
||||
private FloatingActionButton subscriptionAddButton;
|
||||
private FloatingSelectMenu floatingSelectMenu;
|
||||
@ -282,6 +285,12 @@ public class SubscriptionFragment extends Fragment
|
||||
loadSubscriptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
scrollPosition = getScrollPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
@ -316,6 +325,7 @@ public class SubscriptionFragment extends Fragment
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
result -> {
|
||||
final boolean firstLoaded = listItems == null || listItems.isEmpty();
|
||||
if (listItems != null && listItems.size() > result.size()) {
|
||||
// We have fewer items. This can result in items being selected that are no longer visible.
|
||||
subscriptionAdapter.endSelectMode();
|
||||
@ -323,6 +333,9 @@ public class SubscriptionFragment extends Fragment
|
||||
listItems = result;
|
||||
progressBar.setVisibility(View.GONE);
|
||||
subscriptionAdapter.setItems(result);
|
||||
if (firstLoaded) {
|
||||
restoreScrollPosition(scrollPosition);
|
||||
}
|
||||
emptyView.updateVisibility();
|
||||
}, error -> {
|
||||
Log.e(TAG, Log.getStackTraceString(error));
|
||||
@ -416,4 +429,20 @@ public class SubscriptionFragment extends Fragment
|
||||
subscriptionAddButton.setVisibility(View.GONE);
|
||||
updateFilterVisibility();
|
||||
}
|
||||
|
||||
public Pair<Integer, Integer> getScrollPosition() {
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager) subscriptionRecycler.getLayoutManager();
|
||||
int firstItem = layoutManager.findFirstVisibleItemPosition();
|
||||
View firstItemView = layoutManager.findViewByPosition(firstItem);
|
||||
int topOffset = firstItemView == null ? 0 : firstItemView.getTop();
|
||||
return new Pair<>(firstItem, topOffset);
|
||||
}
|
||||
|
||||
public void restoreScrollPosition(Pair<Integer, Integer> scrollPosition) {
|
||||
if (scrollPosition == null || (scrollPosition.first == 0 && scrollPosition.second == 0)) {
|
||||
return;
|
||||
}
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager) subscriptionRecycler.getLayoutManager();
|
||||
layoutManager.scrollToPositionWithOffset(scrollPosition.first, scrollPosition.second);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user