Merge branch 'master' into #142/SaveToots

# Conflicts:
#	app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java
This commit is contained in:
torrentcome 2017-07-07 13:28:31 +02:00
commit b6d4b388a5
33 changed files with 1097 additions and 584 deletions

View file

@ -22,16 +22,22 @@ import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public abstract class AccountAdapter extends RecyclerView.Adapter {
List<Account> accountList;
AccountActionListener accountActionListener;
FooterViewHolder.State footerState;
private String topId;
private String bottomId;
AccountAdapter(AccountActionListener accountActionListener) {
super();
accountList = new ArrayList<>();
this.accountActionListener = accountActionListener;
footerState = FooterViewHolder.State.END;
}
@Override
@ -39,12 +45,20 @@ public abstract class AccountAdapter extends RecyclerView.Adapter {
return accountList.size() + 1;
}
public void update(List<Account> newAccounts) {
public void update(@Nullable List<Account> newAccounts, @Nullable String fromId,
@Nullable String uptoId) {
if (newAccounts == null || newAccounts.isEmpty()) {
return;
}
if (fromId != null) {
bottomId = fromId;
}
if (uptoId != null) {
topId = uptoId;
}
if (accountList.isEmpty()) {
accountList = newAccounts;
// This construction removes duplicates.
accountList = new ArrayList<>(new HashSet<>(newAccounts));
} else {
int index = accountList.indexOf(newAccounts.get(newAccounts.size() - 1));
for (int i = 0; i < index; i++) {
@ -60,10 +74,25 @@ public abstract class AccountAdapter extends RecyclerView.Adapter {
notifyDataSetChanged();
}
public void addItems(List<Account> newAccounts) {
public void addItems(List<Account> newAccounts, @Nullable String fromId) {
if (fromId != null) {
bottomId = fromId;
}
int end = accountList.size();
accountList.addAll(newAccounts);
notifyItemRangeInserted(end, newAccounts.size());
Account last = accountList.get(end - 1);
if (last != null && !findAccount(newAccounts, last.id)) {
accountList.addAll(newAccounts);
notifyItemRangeInserted(end, newAccounts.size());
}
}
private static boolean findAccount(List<Account> accounts, String id) {
for (Account account : accounts) {
if (account.id.equals(id)) {
return true;
}
}
return false;
}
@Nullable
@ -84,10 +113,25 @@ 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;
}
}

View file

@ -17,7 +17,7 @@ class AccountViewHolder extends RecyclerView.ViewHolder {
private TextView username;
private TextView displayName;
private CircularImageView avatar;
private String id;
private String accountId;
AccountViewHolder(View itemView) {
super(itemView);
@ -28,7 +28,7 @@ class AccountViewHolder extends RecyclerView.ViewHolder {
}
void setupWithAccount(Account account) {
id = account.id;
accountId = account.id;
String format = username.getContext().getString(R.string.status_username_format);
String formattedUsername = String.format(format, account.username);
username.setText(formattedUsername);
@ -45,7 +45,7 @@ class AccountViewHolder extends RecyclerView.ViewHolder {
container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onViewAccount(id);
listener.onViewAccount(accountId);
}
});
}
@ -54,7 +54,7 @@ class AccountViewHolder extends RecyclerView.ViewHolder {
container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onViewAccount(id);
listener.onViewAccount(accountId);
}
});
}

View file

@ -59,6 +59,9 @@ public class BlocksAdapter extends AccountAdapter {
BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder;
holder.setupWithAccount(accountList.get(position));
holder.setupActionListener(accountActionListener, true);
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
}
}

View file

@ -55,6 +55,9 @@ public class FollowAdapter extends AccountAdapter {
AccountViewHolder holder = (AccountViewHolder) viewHolder;
holder.setupWithAccount(accountList.get(position));
holder.setupActionListener(accountActionListener);
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
}
}

View file

@ -59,6 +59,9 @@ public class FollowRequestsAdapter extends AccountAdapter {
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
holder.setupWithAccount(accountList.get(position));
holder.setupActionListener(accountActionListener);
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
}
}

View file

@ -15,18 +15,69 @@
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;
class FooterViewHolder extends RecyclerView.ViewHolder {
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);
ProgressBar progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar);
if (progressBar != null) {
progressBar.setIndeterminate(true);
container = itemView.findViewById(R.id.footer_container);
progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar);
endMessage = (TextView) itemView.findViewById(R.id.footer_end_message);
Drawable top = AppCompatResources.getDrawable(itemView.getContext(),
R.drawable.elephant_friend);
if (top != null) {
top.setBounds(0, 0, top.getIntrinsicWidth() / 2, top.getIntrinsicHeight() / 2);
}
endMessage.setCompoundDrawables(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;
}
}
}
}

View file

@ -44,6 +44,9 @@ public class MutesAdapter extends AccountAdapter {
MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder;
holder.setupWithAccount(accountList.get(position));
holder.setupActionListener(accountActionListener, true, position);
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
}
}

View file

@ -37,6 +37,7 @@ import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
@ -45,17 +46,13 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2;
private static final int VIEW_TYPE_FOLLOW = 3;
public enum FooterState {
EMPTY,
END,
LOADING
}
private List<Notification> notifications;
private StatusActionListener statusListener;
private NotificationActionListener notificationActionListener;
private FooterState footerState = FooterState.END;
private FooterViewHolder.State footerState;
private boolean mediaPreviewEnabled;
private String bottomId;
private String topId;
public NotificationsAdapter(StatusActionListener statusListener,
NotificationActionListener notificationActionListener) {
@ -63,6 +60,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
notifications = new ArrayList<>();
this.statusListener = statusListener;
this.notificationActionListener = notificationActionListener;
footerState = FooterViewHolder.State.END;
mediaPreviewEnabled = true;
}
@ -76,24 +74,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
return new StatusViewHolder(view);
}
case VIEW_TYPE_FOOTER: {
View view;
switch (footerState) {
default:
case LOADING:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_footer, parent, false);
break;
case END: {
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_footer_end, parent, false);
break;
}
case EMPTY: {
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_footer_empty, parent, false);
break;
}
}
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_footer, parent, false);
return new FooterViewHolder(view);
}
case VIEW_TYPE_STATUS_NOTIFICATION: {
@ -137,6 +119,9 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
break;
}
}
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
}
}
@ -186,19 +171,28 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
}
}
public @Nullable Notification getItem(int position) {
@Nullable
public Notification getItem(int position) {
if (position >= 0 && position < notifications.size()) {
return notifications.get(position);
}
return null;
}
public void update(List<Notification> newNotifications) {
public void update(@Nullable List<Notification> newNotifications, @Nullable String fromId,
@Nullable String uptoId) {
if (newNotifications == null || newNotifications.isEmpty()) {
return;
}
if (fromId != null) {
bottomId = fromId;
}
if (uptoId != null) {
topId = uptoId;
}
if (notifications.isEmpty()) {
notifications = newNotifications;
// This construction removes duplicates.
notifications = new ArrayList<>(new HashSet<>(newNotifications));
} else {
int index = notifications.indexOf(newNotifications.get(newNotifications.size() - 1));
for (int i = 0; i < index; i++) {
@ -214,10 +208,25 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
notifyDataSetChanged();
}
public void addItems(List<Notification> new_notifications) {
public void addItems(List<Notification> newNotifications, @Nullable String fromId) {
if (fromId != null) {
bottomId = fromId;
}
int end = notifications.size();
notifications.addAll(new_notifications);
notifyItemRangeInserted(end, new_notifications.size());
Notification last = notifications.get(end - 1);
if (last != null && !findNotification(newNotifications, last.id)) {
notifications.addAll(newNotifications);
notifyItemRangeInserted(end, newNotifications.size());
}
}
private static boolean findNotification(List<Notification> notifications, String id) {
for (Notification notification : notifications) {
if (notification.id.equals(id)) {
return true;
}
}
return false;
}
public void clear() {
@ -225,12 +234,18 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
notifyDataSetChanged();
}
public void setFooterState(FooterState newFooterState) {
FooterState oldValue = footerState;
public void setFooterState(FooterViewHolder.State newFooterState) {
footerState = newFooterState;
if (footerState != oldValue) {
notifyItemChanged(notifications.size());
}
}
@Nullable
public String getBottomId() {
return bottomId;
}
@Nullable
public String getTopId() {
return topId;
}
public void setMediaPreviewEnabled(boolean enabled) {

View file

@ -425,8 +425,8 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
container.setOnClickListener(viewThreadListener);
}
void setupWithStatus(Status status, StatusActionListener listener,
boolean mediaPreviewEnabled) {
void setupWithStatus(Status status, final StatusActionListener listener,
boolean mediaPreviewEnabled) {
Status realStatus = status.getActionableStatus();
setDisplayName(realStatus.account.getDisplayName());
@ -474,5 +474,15 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
} else {
setSpoilerText(realStatus.spoilerText);
}
// I think it's not efficient to create new object every time we bind a holder.
// More efficient approach would be creating View.OnClickListener during holder creation
// and storing StatusActionListener in a variable after binding.
rebloggedBar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onOpenReblog(getAdapterPosition());
}
});
}
}

View file

@ -103,7 +103,7 @@ public class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRe
// In case of refresh, remove old ancestors and descendants first. We'll remove all blindly,
// as we have no guarantee on their order to be the same as before
int oldSize = statuses.size();
if (oldSize > 0) {
if (oldSize > 1) {
mainStatus = statuses.get(statusIndex);
statuses.clear();
notifyItemRangeRemoved(0, oldSize);

View file

@ -27,27 +27,25 @@ import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.entity.Status;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
private static final int VIEW_TYPE_STATUS = 0;
private static final int VIEW_TYPE_FOOTER = 1;
public enum FooterState {
EMPTY,
END,
LOADING
}
private List<Status> statuses;
private StatusActionListener statusListener;
private FooterState footerState = FooterState.END;
private FooterViewHolder.State footerState;
private boolean mediaPreviewEnabled;
private String topId;
private String bottomId;
public TimelineAdapter(StatusActionListener statusListener) {
super();
statuses = new ArrayList<>();
this.statusListener = statusListener;
footerState = FooterViewHolder.State.END;
mediaPreviewEnabled = true;
}
@ -61,24 +59,8 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
return new StatusViewHolder(view);
}
case VIEW_TYPE_FOOTER: {
View view;
switch (footerState) {
default:
case LOADING:
view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_footer, viewGroup, false);
break;
case END: {
view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_footer_end, viewGroup, false);
break;
}
case EMPTY: {
view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_footer_empty, viewGroup, false);
break;
}
}
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_footer, viewGroup, false);
return new FooterViewHolder(view);
}
}
@ -90,6 +72,9 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
StatusViewHolder holder = (StatusViewHolder) viewHolder;
Status status = statuses.get(position);
holder.setupWithStatus(status, statusListener, mediaPreviewEnabled);
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
}
}
@ -126,12 +111,20 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
}
}
public void update(List<Status> newStatuses) {
public void update(@Nullable List<Status> newStatuses, @Nullable String fromId,
@Nullable String uptoId) {
if (newStatuses == null || newStatuses.isEmpty()) {
return;
}
if (fromId != null) {
bottomId = fromId;
}
if (uptoId != null) {
topId = uptoId;
}
if (statuses.isEmpty()) {
statuses = newStatuses;
// This construction removes duplicates.
statuses = new ArrayList<>(new HashSet<>(newStatuses));
} else {
int index = statuses.indexOf(newStatuses.get(newStatuses.size() - 1));
for (int i = 0; i < index; i++) {
@ -147,10 +140,25 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
notifyDataSetChanged();
}
public void addItems(List<Status> newStatuses) {
public void addItems(List<Status> newStatuses, @Nullable String fromId) {
if (fromId != null) {
bottomId = fromId;
}
int end = statuses.size();
statuses.addAll(newStatuses);
notifyItemRangeInserted(end, newStatuses.size());
Status last = statuses.get(end - 1);
if (last != null && !findStatus(newStatuses, last.id)) {
statuses.addAll(newStatuses);
notifyItemRangeInserted(end, newStatuses.size());
}
}
private static boolean findStatus(List<Status> statuses, String id) {
for (Status status : statuses) {
if (status.id.equals(id)) {
return true;
}
}
return false;
}
public void clear() {
@ -166,8 +174,8 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
return null;
}
public void setFooterState(FooterState newFooterState) {
FooterState oldValue = footerState;
public void setFooterState(FooterViewHolder.State newFooterState) {
FooterViewHolder.State oldValue = footerState;
footerState = newFooterState;
if (footerState != oldValue) {
notifyItemChanged(statuses.size());
@ -177,4 +185,14 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
public void setMediaPreviewEnabled(boolean enabled) {
mediaPreviewEnabled = enabled;
}
@Nullable
public String getBottomId() {
return bottomId;
}
@Nullable
public String getTopId() {
return topId;
}
}