From 28c1c90a98451a0c300f4508ff06df6f10b363ef Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Fri, 31 Aug 2018 21:52:09 +0200 Subject: [PATCH] fix account list loading and clean up a lot of code (#823) * fix account list loading and clean up a lot of code * remove ACCESS_COARSE_LOCATION for API levels 23+ * small improvements --- .../tusky/adapter/AccountAdapter.java | 85 ++++----- .../tusky/adapter/BlocksAdapter.java | 20 +- .../tusky/adapter/FollowAdapter.java | 17 +- .../tusky/adapter/FollowRequestsAdapter.java | 20 +- .../tusky/adapter/FooterViewHolder.java | 80 -------- .../tusky/adapter/LoadingFooterViewHolder.kt | 21 +++ .../tusky/adapter/MutesAdapter.java | 19 +- .../tusky/adapter/NotificationsAdapter.java | 57 +++--- .../tusky/fragment/AccountListFragment.java | 174 +++++------------- app/src/main/res/layout/item_footer.xml | 20 +- 10 files changed, 135 insertions(+), 378 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/adapter/FooterViewHolder.java create mode 100644 app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java index 21578ba2..14afe76f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java @@ -15,6 +15,7 @@ package com.keylesspalace.tusky.adapter; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; @@ -26,55 +27,40 @@ import java.util.ArrayList; import java.util.List; public abstract class AccountAdapter extends RecyclerView.Adapter { + static final int VIEW_TYPE_ACCOUNT = 0; + static final int VIEW_TYPE_FOOTER = 1; + + List accountList; AccountActionListener accountActionListener; - FooterViewHolder.State footerState; - - private String topId; - private String bottomId; + private boolean bottomLoading; AccountAdapter(AccountActionListener accountActionListener) { - super(); - accountList = new ArrayList<>(); + this.accountList = new ArrayList<>(); this.accountActionListener = accountActionListener; - footerState = FooterViewHolder.State.END; + bottomLoading = false; } @Override public int getItemCount() { - return accountList.size() + 1; + return accountList.size() + (bottomLoading ? 1 : 0); } - public void update(@Nullable List newAccounts, @Nullable String fromId, - @Nullable String uptoId) { - if (newAccounts == null || newAccounts.isEmpty()) { - return; - } - - bottomId = fromId; - topId = uptoId; - - if (accountList.isEmpty()) { - accountList = ListUtils.removeDuplicates(newAccounts); + @Override + public int getItemViewType(int position) { + if (position == accountList.size() && bottomLoading) { + return VIEW_TYPE_FOOTER; } else { - int index = accountList.indexOf(newAccounts.get(newAccounts.size() - 1)); - for (int i = 0; i < index; i++) { - accountList.remove(0); - } - int newIndex = newAccounts.indexOf(accountList.get(0)); - if (newIndex == -1) { - accountList.addAll(0, newAccounts); - } else { - accountList.addAll(0, newAccounts.subList(0, newIndex)); - } + return VIEW_TYPE_ACCOUNT; } + } + + public void update(@NonNull List newAccounts) { + accountList = ListUtils.removeDuplicates(newAccounts); notifyDataSetChanged(); } - public void addItems(List newAccounts, @Nullable String fromId) { - if (fromId != null) { - bottomId = fromId; - } + public void addItems(List newAccounts) { int end = accountList.size(); Account last = accountList.get(end - 1); if (last != null && !findAccount(newAccounts, last.getId())) { @@ -83,6 +69,19 @@ public abstract class AccountAdapter extends RecyclerView.Adapter { } } + public void setBottomLoading(boolean loading) { + boolean wasLoading = bottomLoading; + if(wasLoading == loading) { + return; + } + bottomLoading = loading; + if(loading) { + notifyItemInserted(accountList.size()); + } else { + notifyItemRemoved(accountList.size()); + } + } + private static boolean findAccount(List accounts, String id) { for (Account account : accounts) { if (account.getId().equals(id)) { @@ -110,25 +109,5 @@ public abstract class AccountAdapter extends RecyclerView.Adapter { notifyItemInserted(position); } - @Nullable - public Account getItem(int position) { - if (position >= 0 && position < accountList.size()) { - return accountList.get(position); - } - return null; - } - public void setFooterState(FooterViewHolder.State newFooterState) { - footerState = newFooterState; - } - - @Nullable - public String getBottomId() { - return bottomId; - } - - @Nullable - public String getTopId() { - return topId; - } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java index 21689e91..23354dba 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java @@ -31,8 +31,6 @@ import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.squareup.picasso.Picasso; public class BlocksAdapter extends AccountAdapter { - private static final int VIEW_TYPE_BLOCKED_USER = 0; - private static final int VIEW_TYPE_FOOTER = 1; public BlocksAdapter(AccountActionListener accountActionListener) { super(accountActionListener); @@ -43,7 +41,7 @@ public class BlocksAdapter extends AccountAdapter { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { switch (viewType) { default: - case VIEW_TYPE_BLOCKED_USER: { + case VIEW_TYPE_ACCOUNT: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_blocked_user, parent, false); return new BlockedUserViewHolder(view); @@ -51,29 +49,17 @@ public class BlocksAdapter extends AccountAdapter { case VIEW_TYPE_FOOTER: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_footer, parent, false); - return new FooterViewHolder(view); + return new LoadingFooterViewHolder(view); } } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { - if (position < accountList.size()) { + if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder; holder.setupWithAccount(accountList.get(position)); holder.setupActionListener(accountActionListener); - } else { - FooterViewHolder holder = (FooterViewHolder) viewHolder; - holder.setState(footerState); - } - } - - @Override - public int getItemViewType(int position) { - if (position == accountList.size()) { - return VIEW_TYPE_FOOTER; - } else { - return VIEW_TYPE_BLOCKED_USER; } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java index c52050de..8b7aab71 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java @@ -26,8 +26,6 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener; /** Both for follows and following lists. */ public class FollowAdapter extends AccountAdapter { - private static final int VIEW_TYPE_ACCOUNT = 0; - private static final int VIEW_TYPE_FOOTER = 1; public FollowAdapter(AccountActionListener accountActionListener) { super(accountActionListener); @@ -46,29 +44,18 @@ public class FollowAdapter extends AccountAdapter { case VIEW_TYPE_FOOTER: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_footer, parent, false); - return new FooterViewHolder(view); + return new LoadingFooterViewHolder(view); } } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { - if (position < accountList.size()) { + if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { AccountViewHolder holder = (AccountViewHolder) viewHolder; holder.setupWithAccount(accountList.get(position)); holder.setupActionListener(accountActionListener); - } else { - FooterViewHolder holder = (FooterViewHolder) viewHolder; - holder.setState(footerState); } } - @Override - public int getItemViewType(int position) { - if (position == accountList.size()) { - return VIEW_TYPE_FOOTER; - } else { - return VIEW_TYPE_ACCOUNT; - } - } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java index 31c19102..a3941c68 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java @@ -31,8 +31,6 @@ import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.squareup.picasso.Picasso; public class FollowRequestsAdapter extends AccountAdapter { - private static final int VIEW_TYPE_FOLLOW_REQUEST = 0; - private static final int VIEW_TYPE_FOOTER = 1; public FollowRequestsAdapter(AccountActionListener accountActionListener) { super(accountActionListener); @@ -43,7 +41,7 @@ public class FollowRequestsAdapter extends AccountAdapter { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { switch (viewType) { default: - case VIEW_TYPE_FOLLOW_REQUEST: { + case VIEW_TYPE_ACCOUNT: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_follow_request, parent, false); return new FollowRequestViewHolder(view); @@ -51,29 +49,17 @@ public class FollowRequestsAdapter extends AccountAdapter { case VIEW_TYPE_FOOTER: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_footer, parent, false); - return new FooterViewHolder(view); + return new LoadingFooterViewHolder(view); } } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { - if (position < accountList.size()) { + if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; holder.setupWithAccount(accountList.get(position)); holder.setupActionListener(accountActionListener); - } else { - FooterViewHolder holder = (FooterViewHolder) viewHolder; - holder.setState(footerState); - } - } - - @Override - public int getItemViewType(int position) { - if (position == accountList.size()) { - return VIEW_TYPE_FOOTER; - } else { - return VIEW_TYPE_FOLLOW_REQUEST; } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FooterViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FooterViewHolder.java deleted file mode 100644 index b4e2be14..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FooterViewHolder.java +++ /dev/null @@ -1,80 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is a part of Tusky. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Tusky; if not, - * see . */ - -package com.keylesspalace.tusky.adapter; - -import android.graphics.drawable.Drawable; -import android.support.v7.content.res.AppCompatResources; -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.support.v7.widget.RecyclerView.LayoutParams; - -import com.keylesspalace.tusky.R; - -public class FooterViewHolder extends RecyclerView.ViewHolder { - public enum State { - EMPTY, - END, - LOADING - } - - private View container; - private ProgressBar progressBar; - private TextView endMessage; - - FooterViewHolder(View itemView) { - super(itemView); - container = itemView.findViewById(R.id.footer_container); - progressBar = itemView.findViewById(R.id.footer_progress_bar); - endMessage = itemView.findViewById(R.id.footer_end_message); - Drawable top = AppCompatResources.getDrawable(itemView.getContext(), - R.drawable.elephant_friend_empty); - endMessage.setCompoundDrawablesWithIntrinsicBounds(null, top, null, null); - } - - public void setState(State state) { - switch (state) { - case LOADING: { - RecyclerView.LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT); - container.setLayoutParams(layoutParams); - container.setVisibility(View.VISIBLE); - progressBar.setVisibility(View.VISIBLE); - endMessage.setVisibility(View.GONE); - break; - } - case END: { - RecyclerView.LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT); - container.setLayoutParams(layoutParams); - container.setVisibility(View.GONE); - progressBar.setVisibility(View.GONE); - endMessage.setVisibility(View.GONE); - break; - } - case EMPTY: { - RecyclerView.LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT); - container.setLayoutParams(layoutParams); - container.setVisibility(View.VISIBLE); - progressBar.setVisibility(View.GONE); - endMessage.setVisibility(View.VISIBLE); - break; - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt new file mode 100644 index 00000000..f848c180 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt @@ -0,0 +1,21 @@ +/* Copyright 2018 Conny Duck + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.adapter + +import android.support.v7.widget.RecyclerView +import android.view.View + +class LoadingFooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java index a2318a74..7c64e874 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java @@ -16,8 +16,6 @@ import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.squareup.picasso.Picasso; public class MutesAdapter extends AccountAdapter { - private static final int VIEW_TYPE_MUTED_USER = 0; - private static final int VIEW_TYPE_FOOTER = 1; public MutesAdapter(AccountActionListener accountActionListener) { super(accountActionListener); @@ -28,7 +26,7 @@ public class MutesAdapter extends AccountAdapter { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { switch (viewType) { default: - case VIEW_TYPE_MUTED_USER: { + case VIEW_TYPE_ACCOUNT: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_muted_user, parent, false); return new MutesAdapter.MutedUserViewHolder(view); @@ -36,31 +34,20 @@ public class MutesAdapter extends AccountAdapter { case VIEW_TYPE_FOOTER: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_footer, parent, false); - return new FooterViewHolder(view); + return new LoadingFooterViewHolder(view); } } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { - if (position < accountList.size()) { + if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder; holder.setupWithAccount(accountList.get(position)); holder.setupActionListener(accountActionListener); - } else { - FooterViewHolder holder = (FooterViewHolder) viewHolder; - holder.setState(footerState); } } - @Override - public int getItemViewType(int position) { - if (position == accountList.size()) { - return VIEW_TYPE_FOOTER; - } else { - return VIEW_TYPE_MUTED_USER; - } - } static class MutedUserViewHolder extends RecyclerView.ViewHolder { private ImageView avatar; diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java index 52d20555..c6c9acd6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -56,10 +56,9 @@ import java.util.List; public class NotificationsAdapter extends RecyclerView.Adapter { private static final int VIEW_TYPE_MENTION = 0; - private static final int VIEW_TYPE_FOOTER = 1; - private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2; - private static final int VIEW_TYPE_FOLLOW = 3; - private static final int VIEW_TYPE_PLACEHOLDER = 4; + private static final int VIEW_TYPE_STATUS_NOTIFICATION = 1; + private static final int VIEW_TYPE_FOLLOW = 2; + private static final int VIEW_TYPE_PLACEHOLDER = 3; private List notifications; private StatusActionListener statusListener; @@ -87,11 +86,6 @@ public class NotificationsAdapter extends RecyclerView.Adapter { .inflate(R.layout.item_status, parent, false); return new StatusViewHolder(view); } - case VIEW_TYPE_FOOTER: { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_footer, parent, false); - return new FooterViewHolder(view); - } case VIEW_TYPE_STATUS_NOTIFICATION: { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_status_notification, parent, false); @@ -172,31 +166,28 @@ public class NotificationsAdapter extends RecyclerView.Adapter { @Override public int getItemViewType(int position) { - if (position == notifications.size()) { - return VIEW_TYPE_FOOTER; - } else { - NotificationViewData notification = notifications.get(position); - if (notification instanceof NotificationViewData.Concrete) { - NotificationViewData.Concrete concrete = ((NotificationViewData.Concrete) notification); - switch (concrete.getType()) { - default: - case MENTION: { - return VIEW_TYPE_MENTION; - } - case FAVOURITE: - case REBLOG: { - return VIEW_TYPE_STATUS_NOTIFICATION; - } - case FOLLOW: { - return VIEW_TYPE_FOLLOW; - } + NotificationViewData notification = notifications.get(position); + if (notification instanceof NotificationViewData.Concrete) { + NotificationViewData.Concrete concrete = ((NotificationViewData.Concrete) notification); + switch (concrete.getType()) { + default: + case MENTION: { + return VIEW_TYPE_MENTION; + } + case FAVOURITE: + case REBLOG: { + return VIEW_TYPE_STATUS_NOTIFICATION; + } + case FOLLOW: { + return VIEW_TYPE_FOLLOW; } - } else if (notification instanceof NotificationViewData.Placeholder) { - return VIEW_TYPE_PLACEHOLDER; - } else { - throw new AssertionError("Unknown notification type"); } + } else if (notification instanceof NotificationViewData.Placeholder) { + return VIEW_TYPE_PLACEHOLDER; + } else { + throw new AssertionError("Unknown notification type"); } + } public void update(@Nullable List newNotifications) { @@ -364,8 +355,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter { private void setCreatedAt(@Nullable Date createdAt) { // This is the visible timestampInfo. String readout; - /* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m" - * as 17 meters instead of minutes. */ + /* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m" + * as 17 meters instead of minutes. */ CharSequence readoutAloud; if (createdAt != null) { long then = createdAt.getTime(); diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java index 017bc52c..07ecdb36 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java @@ -36,7 +36,6 @@ import com.keylesspalace.tusky.adapter.AccountAdapter; import com.keylesspalace.tusky.adapter.BlocksAdapter; import com.keylesspalace.tusky.adapter.FollowAdapter; import com.keylesspalace.tusky.adapter.FollowRequestsAdapter; -import com.keylesspalace.tusky.adapter.FooterViewHolder; import com.keylesspalace.tusky.adapter.MutesAdapter; import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.Account; @@ -79,10 +78,8 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi private RecyclerView recyclerView; private EndlessOnScrollListener scrollListener; private AccountAdapter adapter; - private boolean bottomLoading; - private int bottomFetches; - private boolean topLoading; - private int topFetches; + private boolean fetching = false; + private String bottomId; public static AccountListFragment newInstance(Type type) { Bundle arguments = new Bundle(); @@ -140,10 +137,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi } recyclerView.setAdapter(adapter); - bottomLoading = false; - bottomFetches = 0; - topLoading = false; - topFetches = 0; return rootView; } @@ -155,13 +148,13 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi scrollListener = new EndlessOnScrollListener(layoutManager) { @Override public void onLoadMore(int totalItemsCount, RecyclerView view) { - AccountListFragment.this.onLoadMore(view); + AccountListFragment.this.onLoadMore(); } }; recyclerView.addOnScrollListener(scrollListener); - fetchAccounts(null, null, FetchEnd.BOTTOM); + fetchAccounts(null); } @@ -176,14 +169,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi @Override public void onMute(final boolean mute, final String id, final int position) { - if (api == null) { - /* If somehow an unmute button is clicked after onCreateView but before - * onActivityCreated, then this would get called with a null api object, so this eats - * that input. */ - Log.d(TAG, "MastodonApi isn't initialised so this mute can't occur."); - return; - } - Callback callback = new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { @@ -237,14 +222,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi @Override public void onBlock(final boolean block, final String id, final int position) { - if (api == null) { - /* If somehow an unblock button is clicked after onCreateView but before - * onActivityCreated, then this would get called with a null api object, so this eats - * that input. */ - Log.d(TAG, "MastodonApi isn't initialised so this block can't occur."); - return; - } - Callback cb = new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { @@ -299,13 +276,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi @Override public void onRespondToFollowRequest(final boolean accept, final String accountId, final int position) { - if (api == null) { - /* If somehow an response button is clicked after onCreateView but before - * onActivityCreated, then this would get called with a null api object, so this eats - * that input. */ - Log.d(TAG, "MastodonApi isn't initialised, so follow requests can't be responded to."); - return; - } Callback callback = new Callback() { @Override @@ -349,44 +319,30 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi Log.e(TAG, message); } - private enum FetchEnd { - TOP, - BOTTOM - } - - private Call> getFetchCallByListType(Type type, String fromId, String uptoId) { + private Call> getFetchCallByListType(Type type, String fromId) { switch (type) { default: case FOLLOWS: - return api.accountFollowing(accountId, fromId, uptoId, null); + return api.accountFollowing(accountId, fromId, null, null); case FOLLOWERS: - return api.accountFollowers(accountId, fromId, uptoId, null); + return api.accountFollowers(accountId, fromId, null, null); case BLOCKS: - return api.blocks(fromId, uptoId, null); + return api.blocks(fromId, null, null); case MUTES: - return api.mutes(fromId, uptoId, null); + return api.mutes(fromId, null, null); case FOLLOW_REQUESTS: - return api.followRequests(fromId, uptoId, null); + return api.followRequests(fromId, null, null); } } - private void fetchAccounts(String fromId, String uptoId, final FetchEnd fetchEnd) { - /* If there is a fetch already ongoing, record however many fetches are requested and - * fulfill them after it's complete. */ - if (fetchEnd == FetchEnd.TOP && topLoading) { - topFetches++; - return; - } - if (fetchEnd == FetchEnd.BOTTOM && bottomLoading) { - bottomFetches++; + private void fetchAccounts(String id) { + if (fetching) { return; } + fetching = true; - if (fromId != null || adapter.getItemCount() <= 1) { - /* 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 - * convenient time for the UI thread using a Runnable. */ - recyclerView.post(() -> adapter.setFooterState(FooterViewHolder.State.LOADING)); + if (id != null) { + recyclerView.post(() -> adapter.setBottomLoading(true)); } Callback> cb = new Callback>() { @@ -394,99 +350,55 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi public void onResponse(@NonNull Call> call, @NonNull Response> response) { if (response.isSuccessful()) { String linkHeader = response.headers().get("Link"); - onFetchAccountsSuccess(response.body(), linkHeader, fetchEnd); + onFetchAccountsSuccess(response.body(), linkHeader); } else { - onFetchAccountsFailure(new Exception(response.message()), fetchEnd); + onFetchAccountsFailure(new Exception(response.message())); } } @Override public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - onFetchAccountsFailure((Exception) t, fetchEnd); + onFetchAccountsFailure((Exception) t); } }; - Call> listCall = getFetchCallByListType(type, fromId, uptoId); + Call> listCall = getFetchCallByListType(type, id); callList.add(listCall); listCall.enqueue(cb); } - private void onFetchAccountsSuccess(List accounts, String linkHeader, - FetchEnd fetchEnd) { + private void onFetchAccountsSuccess(List accounts, String linkHeader) { + adapter.setBottomLoading(false); + + List 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"); - } - adapter.update(accounts, null, uptoId); - break; - } - case BOTTOM: { - HttpHeaderLink next = HttpHeaderLink.findByRelationType(links, "next"); - String fromId = null; - if (next != null) { - fromId = next.uri.getQueryParameter("max_id"); - } - if (adapter.getItemCount() > 1) { - adapter.addItems(accounts, 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"); - } - adapter.update(accounts, fromId, uptoId); - } - break; - } + HttpHeaderLink next = HttpHeaderLink.findByRelationType(links, "next"); + String fromId = null; + if (next != null) { + fromId = next.uri.getQueryParameter("max_id"); } - fulfillAnyQueuedFetches(fetchEnd); - if (accounts.size() == 0 && adapter.getItemCount() == 1) { - adapter.setFooterState(FooterViewHolder.State.EMPTY); + if (adapter.getItemCount() > 1) { + adapter.addItems(accounts); } else { - adapter.setFooterState(FooterViewHolder.State.END); + adapter.update(accounts); } + + bottomId = fromId; + + fetching = false; + + adapter.setBottomLoading(false); } - private void onFetchAccountsFailure(Exception exception, FetchEnd fetchEnd) { + private void onFetchAccountsFailure(Exception exception) { + fetching = false; Log.e(TAG, "Fetch failure: " + exception.getMessage()); - fulfillAnyQueuedFetches(fetchEnd); } - private void onRefresh() { - fetchAccounts(null, adapter.getTopId(), FetchEnd.TOP); - } - - private void onLoadMore(RecyclerView recyclerView) { - AccountAdapter adapter = (AccountAdapter) recyclerView.getAdapter(); - //if we do not have a bottom id, we know we do not need to load more - if (adapter.getBottomId() == null) return; - fetchAccounts(adapter.getBottomId(), null, FetchEnd.BOTTOM); - } - - private void fulfillAnyQueuedFetches(FetchEnd fetchEnd) { - switch (fetchEnd) { - case BOTTOM: { - bottomLoading = false; - if (bottomFetches > 0) { - bottomFetches--; - onLoadMore(recyclerView); - } - break; - } - case TOP: { - topLoading = false; - if (topFetches > 0) { - topFetches--; - onRefresh(); - } - break; - } + private void onLoadMore() { + if(bottomId == null) { + return; } + fetchAccounts(bottomId); } + } diff --git a/app/src/main/res/layout/item_footer.xml b/app/src/main/res/layout/item_footer.xml index 48fa72c5..bdf3bdde 100644 --- a/app/src/main/res/layout/item_footer.xml +++ b/app/src/main/res/layout/item_footer.xml @@ -1,24 +1,12 @@ - + android:layout_height="72dp"> - - - \ No newline at end of file + \ No newline at end of file