Refactor notifications adapter (#985)
* Fix unnecessary reloading of notifications This removes topId as it is not needed and just plainly uses status id if needed. During initial loading of notifications topId/bottomId are not set so we ended up reloading everything. * Refactor notifications adapter Use AsyncListDiffer for updating notifications just like in timelines. * Cleanup in NotificationsFragment
This commit is contained in:
parent
ebaa3b4fac
commit
47fa775f21
3 changed files with 182 additions and 98 deletions
|
@ -58,7 +58,6 @@ import com.keylesspalace.tusky.viewdata.NotificationViewData;
|
|||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
@ -70,11 +69,16 @@ import androidx.annotation.Nullable;
|
|||
import androidx.arch.core.util.Function;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig;
|
||||
import androidx.recyclerview.widget.AsyncListDiffer;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.ListUpdateCallback;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import at.connyduck.sparkbutton.helpers.Utils;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import kotlin.Unit;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
|
@ -94,6 +98,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
private static final String TAG = "NotificationF"; // logging tag
|
||||
|
||||
private static final int LOAD_AT_ONCE = 30;
|
||||
private int maxPlaceholderId = 0;
|
||||
|
||||
private enum FetchEnd {
|
||||
TOP,
|
||||
|
@ -106,13 +111,14 @@ public class NotificationsFragment extends SFragment implements
|
|||
* and reuse in different places as needed.
|
||||
*/
|
||||
private static final class Placeholder {
|
||||
private static final Placeholder INSTANCE = new Placeholder();
|
||||
final long id;
|
||||
|
||||
public static Placeholder getInstance() {
|
||||
return INSTANCE;
|
||||
public static Placeholder getInstance(long id) {
|
||||
return new Placeholder(id);
|
||||
}
|
||||
|
||||
private Placeholder() {
|
||||
private Placeholder(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,7 +161,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
alwaysShowSensitiveMedia
|
||||
);
|
||||
} else {
|
||||
return new NotificationViewData.Placeholder(false);
|
||||
return new NotificationViewData.Placeholder(input.asLeft().id, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -200,7 +206,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL));
|
||||
|
||||
adapter = new NotificationsAdapter(this, this);
|
||||
adapter = new NotificationsAdapter(dataSource, this, this);
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
alwaysShowSensitiveMedia = accountManager.getActiveAccount().getAlwaysShowSensitiveMedia();
|
||||
boolean mediaPreviewEnabled = accountManager.getActiveAccount().getMediaPreviewEnabled();
|
||||
|
@ -380,11 +386,9 @@ public class NotificationsFragment extends SFragment implements
|
|||
viewDataBuilder.createStatusViewData(), viewdata.isExpanded());
|
||||
|
||||
notifications.setPairedItem(position, newViewData);
|
||||
|
||||
adapter.updateItemWithNotify(position, newViewData, true);
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onFavourite(final boolean favourite, final int position) {
|
||||
final Notification notification = notifications.get(position).asRight();
|
||||
|
@ -417,8 +421,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
viewDataBuilder.createStatusViewData(), viewdata.isExpanded());
|
||||
|
||||
notifications.setPairedItem(position, newViewData);
|
||||
|
||||
adapter.updateItemWithNotify(position, newViewData, true);
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -457,7 +460,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
NotificationViewData notificationViewData = new NotificationViewData.Concrete(old.getType(),
|
||||
old.getId(), old.getAccount(), statusViewData, expanded);
|
||||
notifications.setPairedItem(position, notificationViewData);
|
||||
adapter.updateItemWithNotify(position, notificationViewData, false);
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -471,7 +474,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
NotificationViewData notificationViewData = new NotificationViewData.Concrete(old.getType(),
|
||||
old.getId(), old.getAccount(), statusViewData, old.isExpanded());
|
||||
notifications.setPairedItem(position, notificationViewData);
|
||||
adapter.updateItemWithNotify(position, notificationViewData, false);
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -485,10 +488,11 @@ public class NotificationsFragment extends SFragment implements
|
|||
return;
|
||||
}
|
||||
sendFetchNotificationsRequest(previous.getId(), next.getId(), FetchEnd.MIDDLE, position);
|
||||
Placeholder placeholder = notifications.get(position).asLeft();
|
||||
NotificationViewData notificationViewData =
|
||||
new NotificationViewData.Placeholder(true);
|
||||
new NotificationViewData.Placeholder(placeholder.id, true);
|
||||
notifications.setPairedItem(position, notificationViewData);
|
||||
adapter.updateItemWithNotify(position, notificationViewData, false);
|
||||
updateAdapter();
|
||||
} else {
|
||||
Log.d(TAG, "error loading more");
|
||||
}
|
||||
|
@ -526,7 +530,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
concreteNotification.isExpanded()
|
||||
);
|
||||
notifications.setPairedItem(position, updatedNotification);
|
||||
adapter.updateItemWithNotify(position, updatedNotification, false);
|
||||
updateAdapter();
|
||||
|
||||
// Since we cannot notify to the RecyclerView right away because it may be scrolling
|
||||
// we run this when the RecyclerView is done doing measurements and other calculations.
|
||||
|
@ -582,7 +586,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
@Override
|
||||
public void removeItem(int position) {
|
||||
notifications.remove(position);
|
||||
adapter.update(notifications.getPairedCopy());
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
private void removeAllByAccountId(String accountId) {
|
||||
|
@ -595,7 +599,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
iterator.remove();
|
||||
}
|
||||
}
|
||||
adapter.update(notifications.getPairedCopy());
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
private void onLoadMore() {
|
||||
|
@ -610,16 +614,24 @@ public class NotificationsFragment extends SFragment implements
|
|||
if (notifications.size() > 0) {
|
||||
Either<Placeholder, Notification> last = notifications.get(notifications.size() - 1);
|
||||
if (last.isRight()) {
|
||||
notifications.add(new Either.Left(Placeholder.getInstance()));
|
||||
NotificationViewData viewData = new NotificationViewData.Placeholder(true);
|
||||
final Placeholder placeholder = newPlaceholder();
|
||||
notifications.add(new Either.Left<>(placeholder));
|
||||
NotificationViewData viewData =
|
||||
new NotificationViewData.Placeholder(placeholder.id, true);
|
||||
notifications.setPairedItem(notifications.size() - 1, viewData);
|
||||
recyclerView.post(() -> adapter.addItems(Collections.singletonList(viewData)));
|
||||
updateAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
sendFetchNotificationsRequest(bottomId, null, FetchEnd.BOTTOM, -1);
|
||||
}
|
||||
|
||||
private Placeholder newPlaceholder() {
|
||||
Placeholder placeholder = Placeholder.getInstance(maxPlaceholderId);
|
||||
maxPlaceholderId--;
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
private void jumpToTop() {
|
||||
layoutManager.scrollToPosition(0);
|
||||
scrollListener.reset();
|
||||
|
@ -669,11 +681,6 @@ public class NotificationsFragment extends SFragment implements
|
|||
List<HttpHeaderLink> links = HttpHeaderLink.parse(linkHeader);
|
||||
switch (fetchEnd) {
|
||||
case TOP: {
|
||||
HttpHeaderLink previous = HttpHeaderLink.findByRelationType(links, "prev");
|
||||
String uptoId = null;
|
||||
if (previous != null) {
|
||||
uptoId = previous.uri.getQueryParameter("since_id");
|
||||
}
|
||||
update(notifications, null);
|
||||
break;
|
||||
}
|
||||
|
@ -691,20 +698,12 @@ public class NotificationsFragment extends SFragment implements
|
|||
if (!this.notifications.isEmpty()
|
||||
&& !this.notifications.get(this.notifications.size() - 1).isRight()) {
|
||||
this.notifications.remove(this.notifications.size() - 1);
|
||||
adapter.removeItemAndNotify(this.notifications.size());
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
if (adapter.getItemCount() > 0) {
|
||||
addItems(notifications, fromId);
|
||||
} else {
|
||||
/* If this is the first fetch, also save the id from the "previous" link and
|
||||
* treat this operation as a refresh so the scroll position doesn't get pushed
|
||||
* down to the end. */
|
||||
HttpHeaderLink previous = HttpHeaderLink.findByRelationType(links, "prev");
|
||||
String uptoId = null;
|
||||
if (previous != null) {
|
||||
uptoId = previous.uri.getQueryParameter("since_id");
|
||||
}
|
||||
update(notifications, fromId);
|
||||
}
|
||||
|
||||
|
@ -733,10 +732,11 @@ public class NotificationsFragment extends SFragment implements
|
|||
private void onFetchNotificationsFailure(Exception exception, FetchEnd fetchEnd, int position) {
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
if (fetchEnd == FetchEnd.MIDDLE && !notifications.get(position).isRight()) {
|
||||
Placeholder placeholder = notifications.get(position).asLeft();
|
||||
NotificationViewData placeholderVD =
|
||||
new NotificationViewData.Placeholder(false);
|
||||
new NotificationViewData.Placeholder(placeholder.id, false);
|
||||
notifications.setPairedItem(position, placeholderVD);
|
||||
adapter.updateItemWithNotify(position, placeholderVD, true);
|
||||
updateAdapter();
|
||||
} else if (this.notifications.isEmpty()) {
|
||||
this.statusView.setVisibility(View.VISIBLE);
|
||||
swipeRefreshLayout.setEnabled(false);
|
||||
|
@ -798,14 +798,14 @@ public class NotificationsFragment extends SFragment implements
|
|||
int newIndex = liftedNew.indexOf(notifications.get(0));
|
||||
if (newIndex == -1) {
|
||||
if (index == -1 && liftedNew.size() >= LOAD_AT_ONCE) {
|
||||
liftedNew.add(new Either.Left(Placeholder.getInstance()));
|
||||
liftedNew.add(new Either.Left<>(newPlaceholder()));
|
||||
}
|
||||
notifications.addAll(0, liftedNew);
|
||||
} else {
|
||||
notifications.addAll(0, liftedNew.subList(0, newIndex));
|
||||
}
|
||||
}
|
||||
adapter.update(notifications.getPairedCopy());
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
private void addItems(List<Notification> newNotifications, @Nullable String fromId) {
|
||||
|
@ -818,10 +818,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
Either<Placeholder, Notification> last = notifications.get(end - 1);
|
||||
if (last != null && liftedNew.indexOf(last) == -1) {
|
||||
notifications.addAll(liftedNew);
|
||||
List<NotificationViewData> newViewDatas = notifications.getPairedCopy()
|
||||
.subList(notifications.size() - newNotifications.size(),
|
||||
notifications.size());
|
||||
adapter.addItems(newViewDatas);
|
||||
updateAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -830,7 +827,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
notifications.remove(pos);
|
||||
|
||||
if (ListUtils.isEmpty(newNotifications)) {
|
||||
adapter.update(notifications.getPairedCopy());
|
||||
updateAdapter();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -840,11 +837,11 @@ public class NotificationsFragment extends SFragment implements
|
|||
// If we fetched at least as much it means that there are more posts to load and we should
|
||||
// insert new placeholder
|
||||
if (newNotifications.size() >= LOAD_AT_ONCE) {
|
||||
liftedNew.add(new Either.Left(Placeholder.getInstance()));
|
||||
liftedNew.add(new Either.Left<>(newPlaceholder()));
|
||||
}
|
||||
|
||||
notifications.addAll(pos, liftedNew);
|
||||
adapter.update(notifications.getPairedCopy());
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
private final Function<Notification, Either<Placeholder, Notification>> notificationLifter =
|
||||
|
@ -855,8 +852,8 @@ public class NotificationsFragment extends SFragment implements
|
|||
}
|
||||
|
||||
private void fullyRefresh() {
|
||||
adapter.clear();
|
||||
notifications.clear();
|
||||
updateAdapter();
|
||||
sendFetchNotificationsRequest(null, null, FetchEnd.TOP, -1);
|
||||
}
|
||||
|
||||
|
@ -875,4 +872,67 @@ public class NotificationsFragment extends SFragment implements
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void updateAdapter() {
|
||||
differ.submitList(notifications.getPairedCopy());
|
||||
}
|
||||
|
||||
private final ListUpdateCallback listUpdateCallback = new ListUpdateCallback() {
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
if (isAdded()) {
|
||||
adapter.notifyItemRangeInserted(position, count);
|
||||
Context context = getContext();
|
||||
if (position == 0 && context != null) {
|
||||
recyclerView.scrollBy(0, Utils.dpToPx(context, -30));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
adapter.notifyItemRangeRemoved(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
adapter.notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count, Object payload) {
|
||||
adapter.notifyItemRangeChanged(position, count, payload);
|
||||
}
|
||||
};
|
||||
|
||||
private final AsyncListDiffer<NotificationViewData>
|
||||
differ = new AsyncListDiffer<>(listUpdateCallback,
|
||||
new AsyncDifferConfig.Builder<>(diffCallback).build());
|
||||
|
||||
private final NotificationsAdapter.AdapterDataSource<NotificationViewData> dataSource =
|
||||
new NotificationsAdapter.AdapterDataSource<NotificationViewData>() {
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return differ.getCurrentList().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotificationViewData getItemAt(int pos) {
|
||||
return differ.getCurrentList().get(pos);
|
||||
}
|
||||
};
|
||||
|
||||
private static final DiffUtil.ItemCallback<NotificationViewData> diffCallback
|
||||
= new DiffUtil.ItemCallback<NotificationViewData>() {
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(NotificationViewData oldItem, NotificationViewData newItem) {
|
||||
return oldItem.getViewDataId() == newItem.getViewDataId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(NotificationViewData oldItem, NotificationViewData newItem) {
|
||||
return oldItem.deepEquals(newItem);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue