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
This commit is contained in:
		
					parent
					
						
							
								ca881af7c5
							
						
					
				
			
			
				commit
				
					
						28c1c90a98
					
				
			
		
					 10 changed files with 135 additions and 378 deletions
				
			
		|  | @ -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<Account> 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<Account> 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<Account> newAccounts) { | ||||
|         accountList = ListUtils.removeDuplicates(newAccounts); | ||||
|         notifyDataSetChanged(); | ||||
|     } | ||||
| 
 | ||||
|     public void addItems(List<Account> newAccounts, @Nullable String fromId) { | ||||
|         if (fromId != null) { | ||||
|             bottomId = fromId; | ||||
|         } | ||||
|     public void addItems(List<Account> 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<Account> 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; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 <http://www.gnu.org/licenses>. */ | ||||
| 
 | ||||
| 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; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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 <http://www.gnu.org/licenses>. */ | ||||
| 
 | ||||
| package com.keylesspalace.tusky.adapter | ||||
| 
 | ||||
| import android.support.v7.widget.RecyclerView | ||||
| import android.view.View | ||||
| 
 | ||||
| class LoadingFooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) | ||||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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<NotificationViewData> 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<NotificationViewData> 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(); | ||||
|  |  | |||
|  | @ -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<Relationship> callback = new Callback<Relationship>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull Call<Relationship> call, @NonNull Response<Relationship> 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<Relationship> cb = new Callback<Relationship>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull Call<Relationship> call, @NonNull Response<Relationship> 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<Relationship> callback = new Callback<Relationship>() { | ||||
|             @Override | ||||
|  | @ -349,44 +319,30 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi | |||
|         Log.e(TAG, message); | ||||
|     } | ||||
| 
 | ||||
|     private enum FetchEnd { | ||||
|         TOP, | ||||
|         BOTTOM | ||||
|     } | ||||
| 
 | ||||
|     private Call<List<Account>> getFetchCallByListType(Type type, String fromId, String uptoId) { | ||||
|     private Call<List<Account>> 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<List<Account>> cb = new Callback<List<Account>>() { | ||||
|  | @ -394,99 +350,55 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi | |||
|             public void onResponse(@NonNull Call<List<Account>> call, @NonNull Response<List<Account>> 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<List<Account>> call, @NonNull Throwable t) { | ||||
|                 onFetchAccountsFailure((Exception) t, fetchEnd); | ||||
|                 onFetchAccountsFailure((Exception) t); | ||||
|             } | ||||
|         }; | ||||
|         Call<List<Account>> listCall = getFetchCallByListType(type, fromId, uptoId); | ||||
|         Call<List<Account>> listCall = getFetchCallByListType(type, id); | ||||
|         callList.add(listCall); | ||||
|         listCall.enqueue(cb); | ||||
|     } | ||||
| 
 | ||||
|     private void onFetchAccountsSuccess(List<Account> accounts, String linkHeader, | ||||
|                                         FetchEnd fetchEnd) { | ||||
|     private void onFetchAccountsSuccess(List<Account> accounts, String linkHeader) { | ||||
|         adapter.setBottomLoading(false); | ||||
| 
 | ||||
| 
 | ||||
|         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"); | ||||
|                 } | ||||
|                 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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,24 +1,12 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:id="@+id/footer_container" | ||||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
|     android:layout_height="72dp"> | ||||
| 
 | ||||
|     <ProgressBar | ||||
|         android:id="@+id/footer_progress_bar" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_centerInParent="true" | ||||
|         android:layout_gravity="center" | ||||
|         android:indeterminate="true" /> | ||||
| 
 | ||||
|     <TextView | ||||
|         android:id="@+id/footer_end_message" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_centerInParent="true" | ||||
|         android:drawablePadding="32dp" | ||||
|         android:text="@string/footer_empty" | ||||
|         android:textAlignment="center" | ||||
|         android:textSize="?attr/status_text_medium" /> | ||||
| 
 | ||||
| </RelativeLayout> | ||||
| </FrameLayout> | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue