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:
parent
4d16514851
commit
4653b1e37b
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue