Remember scroll positions (#7801)

This commit is contained in:
ByteHamster 2025-05-05 22:09:23 +02:00 committed by GitHub
parent 6fe2d54935
commit b4de4548ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 91 additions and 47 deletions

View File

@ -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() {

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)));

View File

@ -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);
}
}