fix notification tab loading (#777)

* fix progressbars of footer and fragment overlapping

* add progressbar to bottom of notification list again

* fix bottom loading getting stuck sometimes
This commit is contained in:
Konrad Pozniak 2018-08-22 21:18:56 +02:00 committed by GitHub
parent 4d16514851
commit 4653b1e37b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 49 additions and 70 deletions

View File

@ -64,7 +64,6 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private List<NotificationViewData> notifications; private List<NotificationViewData> notifications;
private StatusActionListener statusListener; private StatusActionListener statusListener;
private NotificationActionListener notificationActionListener; private NotificationActionListener notificationActionListener;
private FooterViewHolder.State footerState;
private boolean mediaPreviewEnabled; private boolean mediaPreviewEnabled;
private BidiFormatter bidiFormatter; private BidiFormatter bidiFormatter;
@ -74,7 +73,6 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
notifications = new ArrayList<>(); notifications = new ArrayList<>();
this.statusListener = statusListener; this.statusListener = statusListener;
this.notificationActionListener = notificationActionListener; this.notificationActionListener = notificationActionListener;
footerState = FooterViewHolder.State.END;
mediaPreviewEnabled = true; mediaPreviewEnabled = true;
bidiFormatter = BidiFormatter.getInstance(); bidiFormatter = BidiFormatter.getInstance();
} }
@ -119,7 +117,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
if (notification instanceof NotificationViewData.Placeholder) { if (notification instanceof NotificationViewData.Placeholder) {
NotificationViewData.Placeholder placeholder = ((NotificationViewData.Placeholder) notification); NotificationViewData.Placeholder placeholder = ((NotificationViewData.Placeholder) notification);
PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder; PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder;
holder.setup(!placeholder.isLoading(), statusListener); holder.setup(statusListener, placeholder.isLoading());
return; return;
} }
NotificationViewData.Concrete concreteNotificaton = NotificationViewData.Concrete concreteNotificaton =
@ -164,15 +162,12 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
break; break;
} }
} }
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
} }
} }
@Override @Override
public int getItemCount() { public int getItemCount() {
return notifications.size() + 1; return notifications.size();
} }
@Override @Override
@ -224,19 +219,16 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
notifyItemRangeInserted(notifications.size(), newNotifications.size()); notifyItemRangeInserted(notifications.size(), newNotifications.size());
} }
public void removeItemAndNotify(int position) {
notifications.remove(position);
notifyItemRemoved(position);
}
public void clear() { public void clear() {
notifications.clear(); notifications.clear();
notifyDataSetChanged(); notifyDataSetChanged();
} }
public void setFooterState(FooterViewHolder.State newFooterState) {
FooterViewHolder.State oldValue = footerState;
footerState = newFooterState;
if (footerState != oldValue) {
notifyItemChanged(notifications.size());
}
}
public void setMediaPreviewEnabled(boolean enabled) { public void setMediaPreviewEnabled(boolean enabled) {
mediaPreviewEnabled = enabled; mediaPreviewEnabled = enabled;
} }

View File

@ -34,21 +34,16 @@ public final class PlaceholderViewHolder extends RecyclerView.ViewHolder {
progressBar = itemView.findViewById(R.id.progress_bar); progressBar = itemView.findViewById(R.id.progress_bar);
} }
public void setup(boolean enabled, final StatusActionListener listener) { public void setup(final StatusActionListener listener, boolean progress) {
this.setup(enabled, listener, false);
}
public void setup(boolean enabled, final StatusActionListener listener, boolean progress) {
loadMoreButton.setVisibility(progress ? View.GONE : View.VISIBLE); loadMoreButton.setVisibility(progress ? View.GONE : View.VISIBLE);
progressBar.setVisibility(progress ? View.VISIBLE : View.GONE); progressBar.setVisibility(progress ? View.VISIBLE : View.GONE);
loadMoreButton.setEnabled(enabled); loadMoreButton.setEnabled(true);
if (enabled) {
loadMoreButton.setOnClickListener(v -> { loadMoreButton.setOnClickListener(v -> {
loadMoreButton.setEnabled(false); loadMoreButton.setEnabled(false);
listener.onLoadMore(getAdapterPosition()); listener.onLoadMore(getAdapterPosition());
}); });
}
} }
} }

View File

@ -71,8 +71,7 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
StatusViewData status = dataSource.getItemAt(position); StatusViewData status = dataSource.getItemAt(position);
if (status instanceof StatusViewData.Placeholder) { if (status instanceof StatusViewData.Placeholder) {
PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder; PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder;
holder.setup(!((StatusViewData.Placeholder) status).isLoading(), holder.setup(statusListener, ((StatusViewData.Placeholder) status).isLoading());
statusListener, ((StatusViewData.Placeholder) status).isLoading());
} else { } else {
StatusViewHolder holder = (StatusViewHolder) viewHolder; StatusViewHolder holder = (StatusViewHolder) viewHolder;
holder.setupWithStatus((StatusViewData.Concrete) status, holder.setupWithStatus((StatusViewData.Concrete) status,

View File

@ -43,10 +43,9 @@ import android.widget.TextView;
import com.keylesspalace.tusky.MainActivity; import com.keylesspalace.tusky.MainActivity;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.FooterViewHolder;
import com.keylesspalace.tusky.adapter.NotificationsAdapter; import com.keylesspalace.tusky.adapter.NotificationsAdapter;
import com.keylesspalace.tusky.appstore.EventHub;
import com.keylesspalace.tusky.appstore.BlockEvent; import com.keylesspalace.tusky.appstore.BlockEvent;
import com.keylesspalace.tusky.appstore.EventHub;
import com.keylesspalace.tusky.appstore.FavoriteEvent; import com.keylesspalace.tusky.appstore.FavoriteEvent;
import com.keylesspalace.tusky.appstore.ReblogEvent; import com.keylesspalace.tusky.appstore.ReblogEvent;
import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountEntity;
@ -69,6 +68,7 @@ import com.keylesspalace.tusky.viewdata.NotificationViewData;
import com.keylesspalace.tusky.viewdata.StatusViewData; import com.keylesspalace.tusky.viewdata.StatusViewData;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -132,9 +132,7 @@ public class NotificationsFragment extends SFragment implements
private TabLayout.OnTabSelectedListener onTabSelectedListener; private TabLayout.OnTabSelectedListener onTabSelectedListener;
private boolean hideFab; private boolean hideFab;
private boolean topLoading; private boolean topLoading;
private int topFetches;
private boolean bottomLoading; private boolean bottomLoading;
private int bottomFetches;
private String bottomId; private String bottomId;
private String topId; private String topId;
private boolean alwaysShowSensitiveMedia; private boolean alwaysShowSensitiveMedia;
@ -202,15 +200,15 @@ public class NotificationsFragment extends SFragment implements
notifications.clear(); notifications.clear();
topLoading = false; topLoading = false;
topFetches = 0;
bottomLoading = false; bottomLoading = false;
bottomFetches = 0;
bottomId = null; bottomId = null;
topId = null; topId = null;
((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); ((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
setupNothingView(); setupNothingView();
sendFetchNotificationsRequest(null, topId, FetchEnd.TOP, -1);
return rootView; return rootView;
} }
@ -551,6 +549,14 @@ public class NotificationsFragment extends SFragment implements
} }
private void onLoadMore() { private void onLoadMore() {
Either<Placeholder, Notification> last = notifications.get(notifications.size() - 1);
if (last.isRight()) {
notifications.add(Either.left(Placeholder.getInstance()));
NotificationViewData viewData = new NotificationViewData.Placeholder(true);
notifications.setPairedItem(notifications.size() - 1, viewData);
recyclerView.post(() -> adapter.addItems(Collections.singletonList(viewData)));
}
sendFetchNotificationsRequest(bottomId, null, FetchEnd.BOTTOM, -1); sendFetchNotificationsRequest(bottomId, null, FetchEnd.BOTTOM, -1);
} }
@ -564,19 +570,16 @@ public class NotificationsFragment extends SFragment implements
/* If there is a fetch already ongoing, record however many fetches are requested and /* If there is a fetch already ongoing, record however many fetches are requested and
* fulfill them after it's complete. */ * fulfill them after it's complete. */
if (fetchEnd == FetchEnd.TOP && topLoading) { if (fetchEnd == FetchEnd.TOP && topLoading) {
topFetches++;
return; return;
} }
if (fetchEnd == FetchEnd.BOTTOM && bottomLoading) { if (fetchEnd == FetchEnd.BOTTOM && bottomLoading) {
bottomFetches++;
return; return;
} }
if(fetchEnd == FetchEnd.TOP) {
if (fromId != null || adapter.getItemCount() <= 1) { topLoading = true;
/* When this is called by the EndlessScrollListener it cannot refresh the footer state }
* using adapter.notifyItemChanged. So its necessary to postpone doing so until a if(fetchEnd == FetchEnd.BOTTOM) {
* convenient time for the UI thread using a Runnable. */ bottomLoading = true;
recyclerView.post(() -> adapter.setFooterState(FooterViewHolder.State.LOADING));
} }
Call<List<Notification>> call = mastodonApi.notifications(fromId, uptoId, LOAD_AT_ONCE); Call<List<Notification>> call = mastodonApi.notifications(fromId, uptoId, LOAD_AT_ONCE);
@ -624,6 +627,13 @@ public class NotificationsFragment extends SFragment implements
if (next != null) { if (next != null) {
fromId = next.uri.getQueryParameter("max_id"); fromId = next.uri.getQueryParameter("max_id");
} }
if (!this.notifications.isEmpty()
&& !this.notifications.get(this.notifications.size() - 1).isRight()) {
this.notifications.remove(this.notifications.size() - 1);
adapter.removeItemAndNotify(this.notifications.size());
}
if (adapter.getItemCount() > 1) { if (adapter.getItemCount() > 1) {
addItems(notifications, fromId); addItems(notifications, fromId);
} else { } else {
@ -644,11 +654,17 @@ public class NotificationsFragment extends SFragment implements
saveNewestNotificationId(notifications); saveNewestNotificationId(notifications);
fulfillAnyQueuedFetches(fetchEnd); if(fetchEnd == FetchEnd.TOP) {
if (notifications.size() == 0 && adapter.getItemCount() == 1) { topLoading = false;
adapter.setFooterState(FooterViewHolder.State.EMPTY); }
if(fetchEnd == FetchEnd.BOTTOM) {
bottomLoading = false;
}
if (notifications.size() == 0 && adapter.getItemCount() == 0) {
nothingMessageView.setVisibility(View.VISIBLE);
} else { } else {
adapter.setFooterState(FooterViewHolder.State.END); nothingMessageView.setVisibility(View.GONE);
} }
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
@ -663,7 +679,6 @@ public class NotificationsFragment extends SFragment implements
adapter.updateItemWithNotify(position, placeholderVD, true); adapter.updateItemWithNotify(position, placeholderVD, true);
} }
Log.e(TAG, "Fetch failure: " + exception.getMessage()); Log.e(TAG, "Fetch failure: " + exception.getMessage());
fulfillAnyQueuedFetches(fetchEnd);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
} }
@ -710,7 +725,6 @@ public class NotificationsFragment extends SFragment implements
notifications.remove(0); notifications.remove(0);
} }
int newIndex = liftedNew.indexOf(notifications.get(0)); int newIndex = liftedNew.indexOf(notifications.get(0));
if (newIndex == -1) { if (newIndex == -1) {
if (index == -1 && liftedNew.size() >= LOAD_AT_ONCE) { if (index == -1 && liftedNew.size() >= LOAD_AT_ONCE) {
@ -743,27 +757,6 @@ public class NotificationsFragment extends SFragment implements
} }
} }
private void fulfillAnyQueuedFetches(FetchEnd fetchEnd) {
switch (fetchEnd) {
case BOTTOM: {
bottomLoading = false;
if (bottomFetches > 0) {
bottomFetches--;
onLoadMore();
}
break;
}
case TOP: {
topLoading = false;
if (topFetches > 0) {
topFetches--;
onRefresh();
}
break;
}
}
}
private void replacePlaceholderWithNotifications(List<Notification> newNotifications, int pos) { private void replacePlaceholderWithNotifications(List<Notification> newNotifications, int pos) {
// Remove placeholder // Remove placeholder
notifications.remove(pos); notifications.remove(pos);