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
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky.adapter;
|
package com.keylesspalace.tusky.adapter;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
|
||||||
|
@ -26,55 +27,40 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class AccountAdapter extends RecyclerView.Adapter {
|
public abstract class AccountAdapter extends RecyclerView.Adapter {
|
||||||
|
static final int VIEW_TYPE_ACCOUNT = 0;
|
||||||
|
static final int VIEW_TYPE_FOOTER = 1;
|
||||||
|
|
||||||
|
|
||||||
List<Account> accountList;
|
List<Account> accountList;
|
||||||
AccountActionListener accountActionListener;
|
AccountActionListener accountActionListener;
|
||||||
FooterViewHolder.State footerState;
|
private boolean bottomLoading;
|
||||||
|
|
||||||
private String topId;
|
|
||||||
private String bottomId;
|
|
||||||
|
|
||||||
AccountAdapter(AccountActionListener accountActionListener) {
|
AccountAdapter(AccountActionListener accountActionListener) {
|
||||||
super();
|
this.accountList = new ArrayList<>();
|
||||||
accountList = new ArrayList<>();
|
|
||||||
this.accountActionListener = accountActionListener;
|
this.accountActionListener = accountActionListener;
|
||||||
footerState = FooterViewHolder.State.END;
|
bottomLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return accountList.size() + 1;
|
return accountList.size() + (bottomLoading ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(@Nullable List<Account> newAccounts, @Nullable String fromId,
|
@Override
|
||||||
@Nullable String uptoId) {
|
public int getItemViewType(int position) {
|
||||||
if (newAccounts == null || newAccounts.isEmpty()) {
|
if (position == accountList.size() && bottomLoading) {
|
||||||
return;
|
return VIEW_TYPE_FOOTER;
|
||||||
}
|
|
||||||
|
|
||||||
bottomId = fromId;
|
|
||||||
topId = uptoId;
|
|
||||||
|
|
||||||
if (accountList.isEmpty()) {
|
|
||||||
accountList = ListUtils.removeDuplicates(newAccounts);
|
|
||||||
} else {
|
} else {
|
||||||
int index = accountList.indexOf(newAccounts.get(newAccounts.size() - 1));
|
return VIEW_TYPE_ACCOUNT;
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(@NonNull List<Account> newAccounts) {
|
||||||
|
accountList = ListUtils.removeDuplicates(newAccounts);
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addItems(List<Account> newAccounts, @Nullable String fromId) {
|
public void addItems(List<Account> newAccounts) {
|
||||||
if (fromId != null) {
|
|
||||||
bottomId = fromId;
|
|
||||||
}
|
|
||||||
int end = accountList.size();
|
int end = accountList.size();
|
||||||
Account last = accountList.get(end - 1);
|
Account last = accountList.get(end - 1);
|
||||||
if (last != null && !findAccount(newAccounts, last.getId())) {
|
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) {
|
private static boolean findAccount(List<Account> accounts, String id) {
|
||||||
for (Account account : accounts) {
|
for (Account account : accounts) {
|
||||||
if (account.getId().equals(id)) {
|
if (account.getId().equals(id)) {
|
||||||
|
@ -110,25 +109,5 @@ public abstract class AccountAdapter extends RecyclerView.Adapter {
|
||||||
notifyItemInserted(position);
|
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;
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
public class BlocksAdapter extends AccountAdapter {
|
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) {
|
public BlocksAdapter(AccountActionListener accountActionListener) {
|
||||||
super(accountActionListener);
|
super(accountActionListener);
|
||||||
|
@ -43,7 +41,7 @@ public class BlocksAdapter extends AccountAdapter {
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
switch (viewType) {
|
switch (viewType) {
|
||||||
default:
|
default:
|
||||||
case VIEW_TYPE_BLOCKED_USER: {
|
case VIEW_TYPE_ACCOUNT: {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.item_blocked_user, parent, false);
|
.inflate(R.layout.item_blocked_user, parent, false);
|
||||||
return new BlockedUserViewHolder(view);
|
return new BlockedUserViewHolder(view);
|
||||||
|
@ -51,29 +49,17 @@ public class BlocksAdapter extends AccountAdapter {
|
||||||
case VIEW_TYPE_FOOTER: {
|
case VIEW_TYPE_FOOTER: {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.item_footer, parent, false);
|
.inflate(R.layout.item_footer, parent, false);
|
||||||
return new FooterViewHolder(view);
|
return new LoadingFooterViewHolder(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
||||||
if (position < accountList.size()) {
|
if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) {
|
||||||
BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder;
|
BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder;
|
||||||
holder.setupWithAccount(accountList.get(position));
|
holder.setupWithAccount(accountList.get(position));
|
||||||
holder.setupActionListener(accountActionListener);
|
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. */
|
/** Both for follows and following lists. */
|
||||||
public class FollowAdapter extends AccountAdapter {
|
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) {
|
public FollowAdapter(AccountActionListener accountActionListener) {
|
||||||
super(accountActionListener);
|
super(accountActionListener);
|
||||||
|
@ -46,29 +44,18 @@ public class FollowAdapter extends AccountAdapter {
|
||||||
case VIEW_TYPE_FOOTER: {
|
case VIEW_TYPE_FOOTER: {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.item_footer, parent, false);
|
.inflate(R.layout.item_footer, parent, false);
|
||||||
return new FooterViewHolder(view);
|
return new LoadingFooterViewHolder(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
||||||
if (position < accountList.size()) {
|
if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) {
|
||||||
AccountViewHolder holder = (AccountViewHolder) viewHolder;
|
AccountViewHolder holder = (AccountViewHolder) viewHolder;
|
||||||
holder.setupWithAccount(accountList.get(position));
|
holder.setupWithAccount(accountList.get(position));
|
||||||
holder.setupActionListener(accountActionListener);
|
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;
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
public class FollowRequestsAdapter extends AccountAdapter {
|
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) {
|
public FollowRequestsAdapter(AccountActionListener accountActionListener) {
|
||||||
super(accountActionListener);
|
super(accountActionListener);
|
||||||
|
@ -43,7 +41,7 @@ public class FollowRequestsAdapter extends AccountAdapter {
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
switch (viewType) {
|
switch (viewType) {
|
||||||
default:
|
default:
|
||||||
case VIEW_TYPE_FOLLOW_REQUEST: {
|
case VIEW_TYPE_ACCOUNT: {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.item_follow_request, parent, false);
|
.inflate(R.layout.item_follow_request, parent, false);
|
||||||
return new FollowRequestViewHolder(view);
|
return new FollowRequestViewHolder(view);
|
||||||
|
@ -51,29 +49,17 @@ public class FollowRequestsAdapter extends AccountAdapter {
|
||||||
case VIEW_TYPE_FOOTER: {
|
case VIEW_TYPE_FOOTER: {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.item_footer, parent, false);
|
.inflate(R.layout.item_footer, parent, false);
|
||||||
return new FooterViewHolder(view);
|
return new LoadingFooterViewHolder(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
||||||
if (position < accountList.size()) {
|
if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) {
|
||||||
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
|
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
|
||||||
holder.setupWithAccount(accountList.get(position));
|
holder.setupWithAccount(accountList.get(position));
|
||||||
holder.setupActionListener(accountActionListener);
|
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;
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
public class MutesAdapter extends AccountAdapter {
|
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) {
|
public MutesAdapter(AccountActionListener accountActionListener) {
|
||||||
super(accountActionListener);
|
super(accountActionListener);
|
||||||
|
@ -28,7 +26,7 @@ public class MutesAdapter extends AccountAdapter {
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
switch (viewType) {
|
switch (viewType) {
|
||||||
default:
|
default:
|
||||||
case VIEW_TYPE_MUTED_USER: {
|
case VIEW_TYPE_ACCOUNT: {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.item_muted_user, parent, false);
|
.inflate(R.layout.item_muted_user, parent, false);
|
||||||
return new MutesAdapter.MutedUserViewHolder(view);
|
return new MutesAdapter.MutedUserViewHolder(view);
|
||||||
|
@ -36,31 +34,20 @@ public class MutesAdapter extends AccountAdapter {
|
||||||
case VIEW_TYPE_FOOTER: {
|
case VIEW_TYPE_FOOTER: {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.item_footer, parent, false);
|
.inflate(R.layout.item_footer, parent, false);
|
||||||
return new FooterViewHolder(view);
|
return new LoadingFooterViewHolder(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
|
||||||
if (position < accountList.size()) {
|
if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) {
|
||||||
MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder;
|
MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder;
|
||||||
holder.setupWithAccount(accountList.get(position));
|
holder.setupWithAccount(accountList.get(position));
|
||||||
holder.setupActionListener(accountActionListener);
|
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 {
|
static class MutedUserViewHolder extends RecyclerView.ViewHolder {
|
||||||
private ImageView avatar;
|
private ImageView avatar;
|
||||||
|
|
|
@ -56,10 +56,9 @@ import java.util.List;
|
||||||
|
|
||||||
public class NotificationsAdapter extends RecyclerView.Adapter {
|
public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
private static final int VIEW_TYPE_MENTION = 0;
|
private static final int VIEW_TYPE_MENTION = 0;
|
||||||
private static final int VIEW_TYPE_FOOTER = 1;
|
private static final int VIEW_TYPE_STATUS_NOTIFICATION = 1;
|
||||||
private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2;
|
private static final int VIEW_TYPE_FOLLOW = 2;
|
||||||
private static final int VIEW_TYPE_FOLLOW = 3;
|
private static final int VIEW_TYPE_PLACEHOLDER = 3;
|
||||||
private static final int VIEW_TYPE_PLACEHOLDER = 4;
|
|
||||||
|
|
||||||
private List<NotificationViewData> notifications;
|
private List<NotificationViewData> notifications;
|
||||||
private StatusActionListener statusListener;
|
private StatusActionListener statusListener;
|
||||||
|
@ -87,11 +86,6 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
.inflate(R.layout.item_status, parent, false);
|
.inflate(R.layout.item_status, parent, false);
|
||||||
return new StatusViewHolder(view);
|
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: {
|
case VIEW_TYPE_STATUS_NOTIFICATION: {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.item_status_notification, parent, false);
|
.inflate(R.layout.item_status_notification, parent, false);
|
||||||
|
@ -172,31 +166,28 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemViewType(int position) {
|
public int getItemViewType(int position) {
|
||||||
if (position == notifications.size()) {
|
NotificationViewData notification = notifications.get(position);
|
||||||
return VIEW_TYPE_FOOTER;
|
if (notification instanceof NotificationViewData.Concrete) {
|
||||||
} else {
|
NotificationViewData.Concrete concrete = ((NotificationViewData.Concrete) notification);
|
||||||
NotificationViewData notification = notifications.get(position);
|
switch (concrete.getType()) {
|
||||||
if (notification instanceof NotificationViewData.Concrete) {
|
default:
|
||||||
NotificationViewData.Concrete concrete = ((NotificationViewData.Concrete) notification);
|
case MENTION: {
|
||||||
switch (concrete.getType()) {
|
return VIEW_TYPE_MENTION;
|
||||||
default:
|
}
|
||||||
case MENTION: {
|
case FAVOURITE:
|
||||||
return VIEW_TYPE_MENTION;
|
case REBLOG: {
|
||||||
}
|
return VIEW_TYPE_STATUS_NOTIFICATION;
|
||||||
case FAVOURITE:
|
}
|
||||||
case REBLOG: {
|
case FOLLOW: {
|
||||||
return VIEW_TYPE_STATUS_NOTIFICATION;
|
return VIEW_TYPE_FOLLOW;
|
||||||
}
|
|
||||||
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) {
|
public void update(@Nullable List<NotificationViewData> newNotifications) {
|
||||||
|
@ -364,8 +355,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
private void setCreatedAt(@Nullable Date createdAt) {
|
private void setCreatedAt(@Nullable Date createdAt) {
|
||||||
// This is the visible timestampInfo.
|
// This is the visible timestampInfo.
|
||||||
String readout;
|
String readout;
|
||||||
/* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m"
|
/* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m"
|
||||||
* as 17 meters instead of minutes. */
|
* as 17 meters instead of minutes. */
|
||||||
CharSequence readoutAloud;
|
CharSequence readoutAloud;
|
||||||
if (createdAt != null) {
|
if (createdAt != null) {
|
||||||
long then = createdAt.getTime();
|
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.BlocksAdapter;
|
||||||
import com.keylesspalace.tusky.adapter.FollowAdapter;
|
import com.keylesspalace.tusky.adapter.FollowAdapter;
|
||||||
import com.keylesspalace.tusky.adapter.FollowRequestsAdapter;
|
import com.keylesspalace.tusky.adapter.FollowRequestsAdapter;
|
||||||
import com.keylesspalace.tusky.adapter.FooterViewHolder;
|
|
||||||
import com.keylesspalace.tusky.adapter.MutesAdapter;
|
import com.keylesspalace.tusky.adapter.MutesAdapter;
|
||||||
import com.keylesspalace.tusky.di.Injectable;
|
import com.keylesspalace.tusky.di.Injectable;
|
||||||
import com.keylesspalace.tusky.entity.Account;
|
import com.keylesspalace.tusky.entity.Account;
|
||||||
|
@ -79,10 +78,8 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
|
||||||
private RecyclerView recyclerView;
|
private RecyclerView recyclerView;
|
||||||
private EndlessOnScrollListener scrollListener;
|
private EndlessOnScrollListener scrollListener;
|
||||||
private AccountAdapter adapter;
|
private AccountAdapter adapter;
|
||||||
private boolean bottomLoading;
|
private boolean fetching = false;
|
||||||
private int bottomFetches;
|
private String bottomId;
|
||||||
private boolean topLoading;
|
|
||||||
private int topFetches;
|
|
||||||
|
|
||||||
public static AccountListFragment newInstance(Type type) {
|
public static AccountListFragment newInstance(Type type) {
|
||||||
Bundle arguments = new Bundle();
|
Bundle arguments = new Bundle();
|
||||||
|
@ -140,10 +137,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
|
||||||
}
|
}
|
||||||
recyclerView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
bottomLoading = false;
|
|
||||||
bottomFetches = 0;
|
|
||||||
topLoading = false;
|
|
||||||
topFetches = 0;
|
|
||||||
|
|
||||||
return rootView;
|
return rootView;
|
||||||
}
|
}
|
||||||
|
@ -155,13 +148,13 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
|
||||||
scrollListener = new EndlessOnScrollListener(layoutManager) {
|
scrollListener = new EndlessOnScrollListener(layoutManager) {
|
||||||
@Override
|
@Override
|
||||||
public void onLoadMore(int totalItemsCount, RecyclerView view) {
|
public void onLoadMore(int totalItemsCount, RecyclerView view) {
|
||||||
AccountListFragment.this.onLoadMore(view);
|
AccountListFragment.this.onLoadMore();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
recyclerView.addOnScrollListener(scrollListener);
|
recyclerView.addOnScrollListener(scrollListener);
|
||||||
|
|
||||||
fetchAccounts(null, null, FetchEnd.BOTTOM);
|
fetchAccounts(null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,14 +169,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMute(final boolean mute, final String id, final int position) {
|
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>() {
|
Callback<Relationship> callback = new Callback<Relationship>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<Relationship> call, @NonNull Response<Relationship> response) {
|
public void onResponse(@NonNull Call<Relationship> call, @NonNull Response<Relationship> response) {
|
||||||
|
@ -237,14 +222,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBlock(final boolean block, final String id, final int position) {
|
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>() {
|
Callback<Relationship> cb = new Callback<Relationship>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<Relationship> call, @NonNull Response<Relationship> response) {
|
public void onResponse(@NonNull Call<Relationship> call, @NonNull Response<Relationship> response) {
|
||||||
|
@ -299,13 +276,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
|
||||||
@Override
|
@Override
|
||||||
public void onRespondToFollowRequest(final boolean accept, final String accountId,
|
public void onRespondToFollowRequest(final boolean accept, final String accountId,
|
||||||
final int position) {
|
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>() {
|
Callback<Relationship> callback = new Callback<Relationship>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -349,44 +319,30 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
|
||||||
Log.e(TAG, message);
|
Log.e(TAG, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum FetchEnd {
|
private Call<List<Account>> getFetchCallByListType(Type type, String fromId) {
|
||||||
TOP,
|
|
||||||
BOTTOM
|
|
||||||
}
|
|
||||||
|
|
||||||
private Call<List<Account>> getFetchCallByListType(Type type, String fromId, String uptoId) {
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
default:
|
default:
|
||||||
case FOLLOWS:
|
case FOLLOWS:
|
||||||
return api.accountFollowing(accountId, fromId, uptoId, null);
|
return api.accountFollowing(accountId, fromId, null, null);
|
||||||
case FOLLOWERS:
|
case FOLLOWERS:
|
||||||
return api.accountFollowers(accountId, fromId, uptoId, null);
|
return api.accountFollowers(accountId, fromId, null, null);
|
||||||
case BLOCKS:
|
case BLOCKS:
|
||||||
return api.blocks(fromId, uptoId, null);
|
return api.blocks(fromId, null, null);
|
||||||
case MUTES:
|
case MUTES:
|
||||||
return api.mutes(fromId, uptoId, null);
|
return api.mutes(fromId, null, null);
|
||||||
case FOLLOW_REQUESTS:
|
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) {
|
private void fetchAccounts(String id) {
|
||||||
/* If there is a fetch already ongoing, record however many fetches are requested and
|
if (fetching) {
|
||||||
* fulfill them after it's complete. */
|
|
||||||
if (fetchEnd == FetchEnd.TOP && topLoading) {
|
|
||||||
topFetches++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fetchEnd == FetchEnd.BOTTOM && bottomLoading) {
|
|
||||||
bottomFetches++;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
fetching = true;
|
||||||
|
|
||||||
if (fromId != null || adapter.getItemCount() <= 1) {
|
if (id != null) {
|
||||||
/* When this is called by the EndlessScrollListener it cannot refresh the footer state
|
recyclerView.post(() -> adapter.setBottomLoading(true));
|
||||||
* 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Callback<List<Account>> cb = new Callback<List<Account>>() {
|
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) {
|
public void onResponse(@NonNull Call<List<Account>> call, @NonNull Response<List<Account>> response) {
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
String linkHeader = response.headers().get("Link");
|
String linkHeader = response.headers().get("Link");
|
||||||
onFetchAccountsSuccess(response.body(), linkHeader, fetchEnd);
|
onFetchAccountsSuccess(response.body(), linkHeader);
|
||||||
} else {
|
} else {
|
||||||
onFetchAccountsFailure(new Exception(response.message()), fetchEnd);
|
onFetchAccountsFailure(new Exception(response.message()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull Call<List<Account>> call, @NonNull Throwable t) {
|
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);
|
callList.add(listCall);
|
||||||
listCall.enqueue(cb);
|
listCall.enqueue(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFetchAccountsSuccess(List<Account> accounts, String linkHeader,
|
private void onFetchAccountsSuccess(List<Account> accounts, String linkHeader) {
|
||||||
FetchEnd fetchEnd) {
|
adapter.setBottomLoading(false);
|
||||||
|
|
||||||
|
|
||||||
List<HttpHeaderLink> links = HttpHeaderLink.parse(linkHeader);
|
List<HttpHeaderLink> links = HttpHeaderLink.parse(linkHeader);
|
||||||
switch (fetchEnd) {
|
HttpHeaderLink next = HttpHeaderLink.findByRelationType(links, "next");
|
||||||
case TOP: {
|
String fromId = null;
|
||||||
HttpHeaderLink previous = HttpHeaderLink.findByRelationType(links, "prev");
|
if (next != null) {
|
||||||
String uptoId = null;
|
fromId = next.uri.getQueryParameter("max_id");
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fulfillAnyQueuedFetches(fetchEnd);
|
if (adapter.getItemCount() > 1) {
|
||||||
if (accounts.size() == 0 && adapter.getItemCount() == 1) {
|
adapter.addItems(accounts);
|
||||||
adapter.setFooterState(FooterViewHolder.State.EMPTY);
|
|
||||||
} else {
|
} 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());
|
Log.e(TAG, "Fetch failure: " + exception.getMessage());
|
||||||
fulfillAnyQueuedFetches(fetchEnd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRefresh() {
|
private void onLoadMore() {
|
||||||
fetchAccounts(null, adapter.getTopId(), FetchEnd.TOP);
|
if(bottomId == null) {
|
||||||
}
|
return;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
fetchAccounts(bottomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/footer_container"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="72dp">
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/footer_progress_bar"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerInParent="true"
|
android:layout_gravity="center"
|
||||||
android:indeterminate="true" />
|
android:indeterminate="true" />
|
||||||
|
|
||||||
<TextView
|
</FrameLayout>
|
||||||
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>
|
|
Loading…
Reference in New Issue